@kaoruisaac/pedelec 0.2.11 → 0.3.0
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 +32 -13
- package/dist/index.d.ts +118 -28
- package/dist/index.js +516 -114
- package/dist/index.type-test.js +23 -8
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +1 -1
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 = {}) {
|
|
@@ -141,18 +146,18 @@ export class Pedelec {
|
|
|
141
146
|
provider: resolvedInput.provider,
|
|
142
147
|
effortLevel: resolvedInput.effortLevel,
|
|
143
148
|
skills: resolvedInput.skills,
|
|
144
|
-
|
|
149
|
+
workspace: resolvedInput.workspace,
|
|
145
150
|
autoEndOnDisconnect: resolvedInput.autoEndOnDisconnect,
|
|
146
151
|
},
|
|
147
152
|
});
|
|
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
|
-
async
|
|
154
|
-
const result = await this.requestWithOptions("
|
|
155
|
-
return
|
|
158
|
+
async workspaceFolderPicker() {
|
|
159
|
+
const result = await this.requestWithOptions("pick_workspace_folder", {}, { timeoutMs: null });
|
|
160
|
+
return normalizeWorkspaceFolderPickerResponse(result);
|
|
156
161
|
}
|
|
157
162
|
async listProviders() {
|
|
158
163
|
return normalizeProviderInfoList(await this.request("list_providers"));
|
|
@@ -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
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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() : "";
|
|
@@ -258,15 +347,15 @@ export class Pedelec {
|
|
|
258
347
|
}
|
|
259
348
|
const autoEndOnDisconnect = raw.autoEndOnDisconnect !== false;
|
|
260
349
|
const normalizedSkills = normalizeSkillsInput(raw.skills);
|
|
261
|
-
const
|
|
350
|
+
const workspace = normalizeCreateSessionWorkspaceInput(raw.workspace);
|
|
262
351
|
if (!hasProvider) {
|
|
263
|
-
return this.resolveDefaultCreateSessionInput(effortLevel, normalizedSkills,
|
|
352
|
+
return this.resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, workspace, autoEndOnDisconnect);
|
|
264
353
|
}
|
|
265
354
|
return {
|
|
266
355
|
provider: provider,
|
|
267
356
|
effortLevel,
|
|
268
357
|
skills: normalizedSkills.manifest,
|
|
269
|
-
|
|
358
|
+
workspace,
|
|
270
359
|
inlineToolHandlers: normalizedSkills.handlers,
|
|
271
360
|
autoEndOnDisconnect,
|
|
272
361
|
};
|
|
@@ -323,7 +412,7 @@ export class Pedelec {
|
|
|
323
412
|
};
|
|
324
413
|
}
|
|
325
414
|
}
|
|
326
|
-
async resolveDefaultCreateSessionInput(effortLevel, normalizedSkills,
|
|
415
|
+
async resolveDefaultCreateSessionInput(effortLevel, normalizedSkills, workspace, autoEndOnDisconnect) {
|
|
327
416
|
const settings = await this.getSettings();
|
|
328
417
|
if (!settings.defaultProvider) {
|
|
329
418
|
throw makeError("DEFAULT_PROVIDER_NOT_SET", "Default provider is not set. Open the Pedelec desktop app Settings page and choose a default provider.");
|
|
@@ -333,7 +422,7 @@ export class Pedelec {
|
|
|
333
422
|
provider: settings.defaultProvider,
|
|
334
423
|
effortLevel,
|
|
335
424
|
skills: normalizedSkills.manifest,
|
|
336
|
-
|
|
425
|
+
workspace,
|
|
337
426
|
inlineToolHandlers: normalizedSkills.handlers,
|
|
338
427
|
autoEndOnDisconnect,
|
|
339
428
|
};
|
|
@@ -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)
|
|
@@ -462,18 +571,18 @@ export class Pedelec {
|
|
|
462
571
|
}
|
|
463
572
|
}
|
|
464
573
|
}
|
|
465
|
-
function
|
|
574
|
+
function normalizeCreateSessionWorkspaceInput(value) {
|
|
466
575
|
if (value === undefined)
|
|
467
576
|
return undefined;
|
|
468
577
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
469
|
-
throw makeError("INVALID_INPUT", "
|
|
578
|
+
throw makeError("INVALID_INPUT", "workspace must be an object");
|
|
470
579
|
}
|
|
471
580
|
const path = value.path;
|
|
472
581
|
if (typeof path !== "string") {
|
|
473
|
-
throw makeError("INVALID_INPUT", "
|
|
582
|
+
throw makeError("INVALID_INPUT", "workspace.path must be a string");
|
|
474
583
|
}
|
|
475
584
|
if (path.trim().length === 0) {
|
|
476
|
-
throw makeError("INVALID_INPUT", "
|
|
585
|
+
throw makeError("INVALID_INPUT", "workspace.path must not be empty");
|
|
477
586
|
}
|
|
478
587
|
return { path };
|
|
479
588
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
526
|
-
this.
|
|
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
|
-
|
|
653
|
+
void this.client
|
|
529
654
|
.request("prepare_session", {
|
|
530
655
|
sessionId: this.sessionId,
|
|
656
|
+
operationId: turn.turnId,
|
|
531
657
|
})
|
|
532
|
-
.
|
|
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.
|
|
537
|
-
this.finishActiveTurn(turn);
|
|
538
|
-
throw error;
|
|
664
|
+
this.failOperationFromRequest(turn, error);
|
|
539
665
|
});
|
|
540
|
-
this.preparePromise =
|
|
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.
|
|
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
|
|
568
|
-
this.
|
|
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
|
-
|
|
703
|
+
void this.client
|
|
571
704
|
.request("send_text", {
|
|
572
705
|
sessionId: this.sessionId,
|
|
573
706
|
text,
|
|
707
|
+
operationId: turn.turnId,
|
|
574
708
|
})
|
|
575
|
-
.
|
|
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.
|
|
580
|
-
this.finishActiveTurn(turn);
|
|
581
|
-
throw error;
|
|
715
|
+
this.failOperationFromRequest(turn, error);
|
|
582
716
|
});
|
|
583
|
-
return
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 === "
|
|
782
|
-
this.
|
|
783
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
903
|
-
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
|
|
942
|
-
if (this.activeTurn)
|
|
1172
|
+
requireOperationTurn(operationId, type, meta) {
|
|
1173
|
+
if (operationId && this.activeTurn?.turnId === operationId) {
|
|
943
1174
|
return this.activeTurn;
|
|
944
|
-
|
|
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 === "
|
|
1383
|
+
message.type === "operation_completed" ||
|
|
1384
|
+
message.type === "usage_updated" ||
|
|
1016
1385
|
message.type === "error" ||
|
|
1017
1386
|
message.type === "ended");
|
|
1018
1387
|
}
|
|
@@ -1033,21 +1402,21 @@ function normalizePedelecSettings(value) {
|
|
|
1033
1402
|
}
|
|
1034
1403
|
return { defaultProvider: value.defaultProvider };
|
|
1035
1404
|
}
|
|
1036
|
-
function
|
|
1405
|
+
function normalizeWorkspaceFolderPickerResponse(value) {
|
|
1037
1406
|
if (!isPlainObject(value) || !("path" in value)) {
|
|
1038
|
-
throw makeError("SDK_PROTOCOL_ERROR", "
|
|
1407
|
+
throw makeError("SDK_PROTOCOL_ERROR", "pick_workspace_folder response had an invalid shape");
|
|
1039
1408
|
}
|
|
1040
1409
|
const path = value.path;
|
|
1041
1410
|
if (path === null)
|
|
1042
1411
|
return null;
|
|
1043
1412
|
if (typeof path !== "string" || path.length === 0 ||
|
|
1044
|
-
typeof value.isEmptyFolder !== "boolean" || typeof value.
|
|
1045
|
-
throw makeError("SDK_PROTOCOL_ERROR", "
|
|
1413
|
+
typeof value.isEmptyFolder !== "boolean" || typeof value.hasWorkspaceConfig !== "boolean") {
|
|
1414
|
+
throw makeError("SDK_PROTOCOL_ERROR", "pick_workspace_folder response had an invalid shape");
|
|
1046
1415
|
}
|
|
1047
1416
|
return {
|
|
1048
1417
|
path,
|
|
1049
1418
|
isEmptyFolder: value.isEmptyFolder,
|
|
1050
|
-
|
|
1419
|
+
hasWorkspaceConfig: value.hasWorkspaceConfig,
|
|
1051
1420
|
};
|
|
1052
1421
|
}
|
|
1053
1422
|
function normalizeProviderInfoList(value) {
|
|
@@ -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);
|