@kaoruisaac/pedelec 0.1.24 → 0.1.26
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/dist/index.d.ts +7 -1
- package/dist/index.js +82 -39
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -224,7 +224,6 @@ export declare class Pedelec {
|
|
|
224
224
|
private readonly sessions;
|
|
225
225
|
private readonly lastSeqBySession;
|
|
226
226
|
private nextRequestNumber;
|
|
227
|
-
private disconnectedError;
|
|
228
227
|
private port;
|
|
229
228
|
constructor(options?: PedelecOptions);
|
|
230
229
|
createSession(): Promise<PedelecSession<string>>;
|
|
@@ -241,12 +240,14 @@ export declare class Pedelec {
|
|
|
241
240
|
private resolveProviderOnlyCreateSessionInput;
|
|
242
241
|
private assertDefaultProviderAvailable;
|
|
243
242
|
private registerSession;
|
|
243
|
+
private ensureExtensionPort;
|
|
244
244
|
private connectExtension;
|
|
245
245
|
private handlePortMessage;
|
|
246
246
|
private handleResponse;
|
|
247
247
|
private isNewEvent;
|
|
248
248
|
private handleDisconnect;
|
|
249
249
|
private broadcastError;
|
|
250
|
+
private invalidateSessions;
|
|
250
251
|
}
|
|
251
252
|
export declare class PedelecSession<TToolName extends string = string> {
|
|
252
253
|
private readonly client;
|
|
@@ -262,6 +263,7 @@ export declare class PedelecSession<TToolName extends string = string> {
|
|
|
262
263
|
private ending;
|
|
263
264
|
private prepared;
|
|
264
265
|
private activeTurn;
|
|
266
|
+
private transportDetached;
|
|
265
267
|
private genericToolHandler;
|
|
266
268
|
private inlineToolHandlers;
|
|
267
269
|
private readonly namedToolHandlers;
|
|
@@ -288,6 +290,10 @@ export declare class PedelecSession<TToolName extends string = string> {
|
|
|
288
290
|
end(): Promise<void>;
|
|
289
291
|
handleEvent(event: SessionEvent, meta?: EventDispatchMeta): void;
|
|
290
292
|
replaceInlineToolHandlers(handlers: Map<string, ToolSpecificHandler>): void;
|
|
293
|
+
/** @internal */
|
|
294
|
+
isTransportDetached(): boolean;
|
|
295
|
+
/** @internal */
|
|
296
|
+
handleTransportDisconnect(error: PedelecError): void;
|
|
291
297
|
private handleToolCall;
|
|
292
298
|
private resolvePendingSend;
|
|
293
299
|
private resolvePendingPrepare;
|
package/dist/index.js
CHANGED
|
@@ -122,14 +122,12 @@ export class Pedelec {
|
|
|
122
122
|
sessions = new Map();
|
|
123
123
|
lastSeqBySession = new Map();
|
|
124
124
|
nextRequestNumber = 1;
|
|
125
|
-
disconnectedError = null;
|
|
126
125
|
port = null;
|
|
127
126
|
constructor(options = {}) {
|
|
128
127
|
this.pageWindow = typeof window === "undefined" ? null : window;
|
|
129
128
|
this.channelId = createChannelId();
|
|
130
129
|
this.bridgeTimeoutMs = Math.max(1, options.bridgeTimeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS);
|
|
131
130
|
if (!this.pageWindow) {
|
|
132
|
-
this.disconnectedError = makeError("EXTENSION_UNAVAILABLE", "Pedelec SDK must run in a browser page context.");
|
|
133
131
|
return;
|
|
134
132
|
}
|
|
135
133
|
this.connectExtension();
|
|
@@ -157,14 +155,6 @@ export class Pedelec {
|
|
|
157
155
|
return normalizePedelecSettings(await this.request("get_settings"));
|
|
158
156
|
}
|
|
159
157
|
async getApprovalStatus() {
|
|
160
|
-
if (this.disconnectedError) {
|
|
161
|
-
return {
|
|
162
|
-
installed: false,
|
|
163
|
-
approved: false,
|
|
164
|
-
origin: getCurrentOrigin(this.pageWindow),
|
|
165
|
-
appConnected: false,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
158
|
try {
|
|
169
159
|
const result = await this.request("get_approval_status");
|
|
170
160
|
return result;
|
|
@@ -195,11 +185,12 @@ export class Pedelec {
|
|
|
195
185
|
return this.registerSession(result.sessionId, "", undefined);
|
|
196
186
|
}
|
|
197
187
|
request(type, payload = {}) {
|
|
198
|
-
|
|
199
|
-
|
|
188
|
+
let port;
|
|
189
|
+
try {
|
|
190
|
+
port = this.ensureExtensionPort();
|
|
200
191
|
}
|
|
201
|
-
|
|
202
|
-
return Promise.reject(
|
|
192
|
+
catch (err) {
|
|
193
|
+
return Promise.reject(normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable."));
|
|
203
194
|
}
|
|
204
195
|
const requestId = `sdk_${Date.now()}_${this.nextRequestNumber++}`;
|
|
205
196
|
const message = { channelId: this.channelId, type, requestId, ...payload };
|
|
@@ -212,17 +203,22 @@ export class Pedelec {
|
|
|
212
203
|
}));
|
|
213
204
|
}, this.bridgeTimeoutMs);
|
|
214
205
|
this.pendingRequests.set(requestId, {
|
|
206
|
+
port,
|
|
215
207
|
resolve: (value) => resolve(value),
|
|
216
208
|
reject,
|
|
217
209
|
timeoutId,
|
|
218
210
|
});
|
|
219
211
|
try {
|
|
220
|
-
|
|
212
|
+
port.postMessage(message);
|
|
221
213
|
}
|
|
222
214
|
catch (err) {
|
|
223
215
|
this.pendingRequests.delete(requestId);
|
|
224
216
|
clearTimeout(timeoutId);
|
|
225
|
-
|
|
217
|
+
const error = normalizeError(err, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
|
|
218
|
+
if (this.port === port) {
|
|
219
|
+
this.handleDisconnect(port, error);
|
|
220
|
+
}
|
|
221
|
+
reject(error);
|
|
226
222
|
}
|
|
227
223
|
});
|
|
228
224
|
}
|
|
@@ -341,7 +337,7 @@ export class Pedelec {
|
|
|
341
337
|
}
|
|
342
338
|
registerSession(sessionId, provider, model, inlineToolHandlers = new Map()) {
|
|
343
339
|
const existing = this.sessions.get(sessionId);
|
|
344
|
-
if (existing) {
|
|
340
|
+
if (existing && !existing.isTransportDetached()) {
|
|
345
341
|
existing.replaceInlineToolHandlers(inlineToolHandlers);
|
|
346
342
|
return existing;
|
|
347
343
|
}
|
|
@@ -349,23 +345,40 @@ export class Pedelec {
|
|
|
349
345
|
this.sessions.set(sessionId, session);
|
|
350
346
|
return session;
|
|
351
347
|
}
|
|
352
|
-
|
|
348
|
+
ensureExtensionPort() {
|
|
349
|
+
if (this.port)
|
|
350
|
+
return this.port;
|
|
351
|
+
if (!this.pageWindow) {
|
|
352
|
+
throw makeError("EXTENSION_UNAVAILABLE", "Pedelec SDK must run in a browser page context.");
|
|
353
|
+
}
|
|
353
354
|
const runtime = globalThis.chrome?.runtime;
|
|
354
355
|
if (!runtime?.connect) {
|
|
355
|
-
|
|
356
|
-
return;
|
|
356
|
+
throw makeError("EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
|
|
357
357
|
}
|
|
358
358
|
try {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
const port = runtime.connect(PEDELEC_EXTENSION_ID, { name: SDK_EXTERNAL_PORT_NAME });
|
|
360
|
+
port.onMessage.addListener((message) => this.handlePortMessage(port, message));
|
|
361
|
+
port.onDisconnect.addListener(() => this.handleDisconnect(port));
|
|
362
|
+
this.port = port;
|
|
363
|
+
return port;
|
|
362
364
|
}
|
|
363
365
|
catch (err) {
|
|
364
|
-
this.disconnectedError = normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
|
|
365
366
|
this.port = null;
|
|
367
|
+
throw normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
connectExtension() {
|
|
371
|
+
try {
|
|
372
|
+
this.ensureExtensionPort();
|
|
373
|
+
}
|
|
374
|
+
catch (_err) {
|
|
375
|
+
// The first connection is eager for normal browser startup, but a failed
|
|
376
|
+
// attempt remains retryable through the next request.
|
|
366
377
|
}
|
|
367
378
|
}
|
|
368
|
-
handlePortMessage(raw) {
|
|
379
|
+
handlePortMessage(port, raw) {
|
|
380
|
+
if (port !== this.port)
|
|
381
|
+
return;
|
|
369
382
|
const message = raw;
|
|
370
383
|
if (!message || typeof message !== "object")
|
|
371
384
|
return;
|
|
@@ -411,23 +424,31 @@ export class Pedelec {
|
|
|
411
424
|
this.lastSeqBySession.set(event.sessionId, event.seq);
|
|
412
425
|
return true;
|
|
413
426
|
}
|
|
414
|
-
handleDisconnect() {
|
|
427
|
+
handleDisconnect(port, disconnectError) {
|
|
428
|
+
if (port !== this.port)
|
|
429
|
+
return;
|
|
415
430
|
const runtime = globalThis.chrome?.runtime;
|
|
416
|
-
const error = normalizeError(runtime?.lastError, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
|
|
417
|
-
this.disconnectedError = error;
|
|
431
|
+
const error = normalizeError(disconnectError ?? runtime?.lastError, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
|
|
418
432
|
this.port = null;
|
|
419
|
-
|
|
433
|
+
this.invalidateSessions(error);
|
|
434
|
+
for (const [requestId, pending] of this.pendingRequests) {
|
|
435
|
+
if (pending.port !== port)
|
|
436
|
+
continue;
|
|
437
|
+
this.pendingRequests.delete(requestId);
|
|
420
438
|
clearTimeout(pending.timeoutId);
|
|
421
439
|
pending.reject(error);
|
|
422
440
|
}
|
|
423
|
-
this.pendingRequests.clear();
|
|
424
|
-
this.broadcastError(error);
|
|
425
441
|
}
|
|
426
442
|
broadcastError(error) {
|
|
427
443
|
for (const session of this.sessions.values()) {
|
|
428
444
|
session.handleEvent({ type: "error", sessionId: session.sessionId, error }, { source: "sdk" });
|
|
429
445
|
}
|
|
430
446
|
}
|
|
447
|
+
invalidateSessions(error) {
|
|
448
|
+
for (const session of this.sessions.values()) {
|
|
449
|
+
session.handleTransportDisconnect(error);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
431
452
|
}
|
|
432
453
|
export class PedelecSession {
|
|
433
454
|
client;
|
|
@@ -443,6 +464,7 @@ export class PedelecSession {
|
|
|
443
464
|
ending = false;
|
|
444
465
|
prepared = false;
|
|
445
466
|
activeTurn = null;
|
|
467
|
+
transportDetached = false;
|
|
446
468
|
genericToolHandler = null;
|
|
447
469
|
inlineToolHandlers = new Map();
|
|
448
470
|
namedToolHandlers = new Map();
|
|
@@ -458,7 +480,7 @@ export class PedelecSession {
|
|
|
458
480
|
this.inlineToolHandlers = new Map(inlineToolHandlers);
|
|
459
481
|
}
|
|
460
482
|
prepare() {
|
|
461
|
-
if (this.status === "ended") {
|
|
483
|
+
if (this.transportDetached || this.status === "ended") {
|
|
462
484
|
return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
|
|
463
485
|
}
|
|
464
486
|
if (this.pendingSend) {
|
|
@@ -482,7 +504,8 @@ export class PedelecSession {
|
|
|
482
504
|
})
|
|
483
505
|
.catch((err) => {
|
|
484
506
|
const error = normalizeError(err, "PREPARE_FAILED", "prepare failed");
|
|
485
|
-
this.
|
|
507
|
+
if (!this.transportDetached)
|
|
508
|
+
this.emitError(error, { source: "sdk" });
|
|
486
509
|
this.clearPendingPrepare();
|
|
487
510
|
this.finishActiveTurn(turn);
|
|
488
511
|
throw error;
|
|
@@ -505,7 +528,7 @@ export class PedelecSession {
|
|
|
505
528
|
// prepare is an optimization; sendText falls back to the original first-run path.
|
|
506
529
|
}
|
|
507
530
|
}
|
|
508
|
-
if (this.status === "ended") {
|
|
531
|
+
if (this.transportDetached || this.status === "ended") {
|
|
509
532
|
return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
|
|
510
533
|
}
|
|
511
534
|
if (this.pendingSend || this.pendingPrepare) {
|
|
@@ -524,7 +547,8 @@ export class PedelecSession {
|
|
|
524
547
|
})
|
|
525
548
|
.catch((err) => {
|
|
526
549
|
const error = normalizeError(err, "SEND_TEXT_FAILED", "sendText failed");
|
|
527
|
-
this.
|
|
550
|
+
if (!this.transportDetached)
|
|
551
|
+
this.emitError(error, { source: "sdk" });
|
|
528
552
|
this.clearPendingSend();
|
|
529
553
|
this.finishActiveTurn(turn);
|
|
530
554
|
throw error;
|
|
@@ -560,7 +584,7 @@ export class PedelecSession {
|
|
|
560
584
|
};
|
|
561
585
|
}
|
|
562
586
|
uploadAsset(file, targetPath) {
|
|
563
|
-
if (this.ending || this.status === "ended")
|
|
587
|
+
if (this.transportDetached || this.ending || this.status === "ended")
|
|
564
588
|
return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
|
|
565
589
|
if (this.uploadPromise) {
|
|
566
590
|
return Promise.reject(makeError("SESSION_BUSY", "session is busy", { sessionId: this.sessionId }));
|
|
@@ -601,13 +625,13 @@ export class PedelecSession {
|
|
|
601
625
|
return this.uploadPromise;
|
|
602
626
|
}
|
|
603
627
|
listAssets() {
|
|
604
|
-
if (this.ending || this.status === "ended") {
|
|
628
|
+
if (this.transportDetached || this.ending || this.status === "ended") {
|
|
605
629
|
return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
|
|
606
630
|
}
|
|
607
631
|
return this.client.request("list_assets", { sessionId: this.sessionId }).then(normalizeListAssetsResponse);
|
|
608
632
|
}
|
|
609
633
|
readAsset(path, type) {
|
|
610
|
-
if (this.ending || this.status === "ended")
|
|
634
|
+
if (this.transportDetached || this.ending || this.status === "ended")
|
|
611
635
|
return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
|
|
612
636
|
if (!isValidAssetPath(path) || !["text", "json", "file"].includes(type)) {
|
|
613
637
|
return Promise.reject(makeError("ASSET_PATH_INVALID", "asset path or read type is invalid"));
|
|
@@ -671,8 +695,12 @@ export class PedelecSession {
|
|
|
671
695
|
return this.status;
|
|
672
696
|
}
|
|
673
697
|
async end() {
|
|
674
|
-
if (this.status === "ended" || this.ending)
|
|
698
|
+
if (this.transportDetached || this.status === "ended" || this.ending) {
|
|
699
|
+
if (this.transportDetached) {
|
|
700
|
+
throw makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId });
|
|
701
|
+
}
|
|
675
702
|
return;
|
|
703
|
+
}
|
|
676
704
|
this.ending = true;
|
|
677
705
|
try {
|
|
678
706
|
await this.client.request("end_session", {
|
|
@@ -742,6 +770,19 @@ export class PedelecSession {
|
|
|
742
770
|
replaceInlineToolHandlers(handlers) {
|
|
743
771
|
this.inlineToolHandlers = new Map(handlers);
|
|
744
772
|
}
|
|
773
|
+
/** @internal */
|
|
774
|
+
isTransportDetached() {
|
|
775
|
+
return this.transportDetached;
|
|
776
|
+
}
|
|
777
|
+
/** @internal */
|
|
778
|
+
handleTransportDisconnect(error) {
|
|
779
|
+
if (this.transportDetached)
|
|
780
|
+
return;
|
|
781
|
+
this.transportDetached = true;
|
|
782
|
+
this.setStatus("error", { source: "sdk" });
|
|
783
|
+
this.emitError(error, { source: "sdk" });
|
|
784
|
+
this.rejectActivePending(error);
|
|
785
|
+
}
|
|
745
786
|
async handleToolCall(event, meta, turn) {
|
|
746
787
|
let result;
|
|
747
788
|
const namedHandler = this.namedToolHandlers.get(event.tool);
|
|
@@ -783,6 +824,8 @@ export class PedelecSession {
|
|
|
783
824
|
}),
|
|
784
825
|
};
|
|
785
826
|
}
|
|
827
|
+
if (this.transportDetached)
|
|
828
|
+
return;
|
|
786
829
|
try {
|
|
787
830
|
await this.client.request("submit_tool_result", {
|
|
788
831
|
sessionId: this.sessionId,
|