@dev-anywhere/relay 0.4.84 → 0.5.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 +13 -8
- package/assets/web/apple-touch-icon-180x180.png +0 -0
- package/assets/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/assets/web/assets/fvad-C44UvSw_.js +1 -0
- package/assets/web/assets/fvad-DYxbW2my.wasm +0 -0
- package/assets/web/assets/index-CYKs-fya.js +762 -0
- package/assets/web/assets/index-CymnUxwB.css +41 -0
- package/assets/web/brand-icon.svg +56 -0
- package/assets/web/favicon.ico +0 -0
- package/assets/web/index.html +53 -0
- package/assets/web/manifest.webmanifest +1 -0
- package/assets/web/maskable-icon-512x512.png +0 -0
- package/assets/web/notification-sw.js +21 -0
- package/assets/web/pwa-192x192.png +0 -0
- package/assets/web/pwa-512x512.png +0 -0
- package/assets/web/pwa-64x64.png +0 -0
- package/assets/web/registerSW.js +1 -0
- package/assets/web/sw.js +1 -0
- package/assets/web/workbox-10a3128e.js +1 -0
- package/dist/{chunk-46NK2URF.js → chunk-2L3L5F4J.js} +566 -227
- package/dist/chunk-2L3L5F4J.js.map +1 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/voice/asr-ws.d.ts.map +1 -1
- package/dist/voice/bailian-asr.d.ts.map +1 -1
- package/dist/voice/config-test.d.ts.map +1 -1
- package/dist/voice/pcm-resample.d.ts +2 -0
- package/dist/voice/pcm-resample.d.ts.map +1 -0
- package/dist/web-app.d.ts +7 -0
- package/dist/web-app.d.ts.map +1 -0
- package/package.json +6 -3
- package/dist/chunk-46NK2URF.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
|
-
import
|
|
5
|
-
import { existsSync as
|
|
4
|
+
import express2 from "express";
|
|
5
|
+
import { existsSync as existsSync3 } from "fs";
|
|
6
6
|
import { createServer } from "http";
|
|
7
7
|
import { homedir } from "os";
|
|
8
8
|
import { dirname as dirname3, resolve } from "path";
|
|
@@ -468,13 +468,56 @@ var VoiceSummaryReasonSchema = z7.enum([
|
|
|
468
468
|
"approval"
|
|
469
469
|
]);
|
|
470
470
|
|
|
471
|
+
// ../../packages/shared/dist/schemas/voice-stream.js
|
|
472
|
+
import { z as z8 } from "zod";
|
|
473
|
+
var VoiceAsrAudioEncodingSchema = z8.enum(["pcm_s16le", "mulaw"]);
|
|
474
|
+
var VoiceAsrStartMessageSchema = z8.object({
|
|
475
|
+
type: z8.literal("start"),
|
|
476
|
+
sessionId: z8.string().min(1),
|
|
477
|
+
attemptId: z8.string().min(1),
|
|
478
|
+
sampleRate: z8.number().int().positive(),
|
|
479
|
+
encoding: VoiceAsrAudioEncodingSchema
|
|
480
|
+
});
|
|
481
|
+
var VoiceAsrStopMessageSchema = z8.object({
|
|
482
|
+
type: z8.literal("stop"),
|
|
483
|
+
attemptId: z8.string().min(1)
|
|
484
|
+
});
|
|
485
|
+
var VoiceAsrClientMessageSchema = z8.discriminatedUnion("type", [
|
|
486
|
+
VoiceAsrStartMessageSchema,
|
|
487
|
+
VoiceAsrStopMessageSchema
|
|
488
|
+
]);
|
|
489
|
+
var VoiceAsrServerMessageSchema = z8.discriminatedUnion("type", [
|
|
490
|
+
z8.object({ type: z8.literal("ready"), attemptId: z8.string().min(1) }),
|
|
491
|
+
z8.object({
|
|
492
|
+
type: z8.literal("audio_ack"),
|
|
493
|
+
attemptId: z8.string().min(1),
|
|
494
|
+
encodedBytes: z8.number().int().nonnegative(),
|
|
495
|
+
pcmBytes: z8.number().int().nonnegative(),
|
|
496
|
+
chunks: z8.number().int().nonnegative()
|
|
497
|
+
}),
|
|
498
|
+
z8.object({ type: z8.literal("partial"), attemptId: z8.string().min(1), text: z8.string() }),
|
|
499
|
+
z8.object({ type: z8.literal("final"), attemptId: z8.string().min(1), text: z8.string() }),
|
|
500
|
+
z8.object({
|
|
501
|
+
type: z8.literal("closed"),
|
|
502
|
+
attemptId: z8.string().min(1),
|
|
503
|
+
code: z8.number().int().optional(),
|
|
504
|
+
reason: z8.string().optional()
|
|
505
|
+
}),
|
|
506
|
+
z8.object({
|
|
507
|
+
type: z8.literal("error"),
|
|
508
|
+
attemptId: z8.string().min(1),
|
|
509
|
+
error: z8.string().optional(),
|
|
510
|
+
errorCode: z8.string().optional()
|
|
511
|
+
})
|
|
512
|
+
]);
|
|
513
|
+
|
|
471
514
|
// ../../packages/shared/dist/builders/index.js
|
|
472
515
|
function serializeControl(msg) {
|
|
473
516
|
return JSON.stringify(msg);
|
|
474
517
|
}
|
|
475
518
|
|
|
476
519
|
// ../../packages/shared/dist/schemas/relay-control.js
|
|
477
|
-
import { z as
|
|
520
|
+
import { z as z9 } from "zod";
|
|
478
521
|
|
|
479
522
|
// ../../packages/shared/dist/constants/relay-errors.js
|
|
480
523
|
var RelayErrorCode = {
|
|
@@ -501,75 +544,75 @@ var ControlErrorCode = {
|
|
|
501
544
|
};
|
|
502
545
|
|
|
503
546
|
// ../../packages/shared/dist/schemas/relay-control.js
|
|
504
|
-
var ProxyInfoSchema =
|
|
547
|
+
var ProxyInfoSchema = z9.object({
|
|
505
548
|
proxyId: IdSchema,
|
|
506
|
-
name:
|
|
507
|
-
online:
|
|
508
|
-
sessions:
|
|
549
|
+
name: z9.string().optional(),
|
|
550
|
+
online: z9.boolean(),
|
|
551
|
+
sessions: z9.array(z9.string()).optional()
|
|
509
552
|
});
|
|
510
|
-
var RelayClientInfoSchema =
|
|
553
|
+
var RelayClientInfoSchema = z9.object({
|
|
511
554
|
clientId: IdSchema,
|
|
512
555
|
proxyId: IdSchema.optional(),
|
|
513
|
-
connectedAt:
|
|
514
|
-
current:
|
|
515
|
-
userAgent:
|
|
516
|
-
platform:
|
|
517
|
-
maxTouchPoints:
|
|
518
|
-
browserName:
|
|
519
|
-
osName:
|
|
520
|
-
deviceKind:
|
|
521
|
-
remoteAddress:
|
|
556
|
+
connectedAt: z9.number().int().nonnegative(),
|
|
557
|
+
current: z9.boolean().optional(),
|
|
558
|
+
userAgent: z9.string().optional(),
|
|
559
|
+
platform: z9.string().optional(),
|
|
560
|
+
maxTouchPoints: z9.number().int().nonnegative().optional(),
|
|
561
|
+
browserName: z9.string().min(1),
|
|
562
|
+
osName: z9.string().min(1),
|
|
563
|
+
deviceKind: z9.enum(["desktop", "tablet", "phone", "unknown"]),
|
|
564
|
+
remoteAddress: z9.string().optional()
|
|
522
565
|
});
|
|
523
|
-
var AgentCliAvailabilitySchema =
|
|
524
|
-
available:
|
|
525
|
-
command:
|
|
526
|
-
error:
|
|
527
|
-
suggestions:
|
|
566
|
+
var AgentCliAvailabilitySchema = z9.object({
|
|
567
|
+
available: z9.boolean(),
|
|
568
|
+
command: z9.string().optional(),
|
|
569
|
+
error: z9.string().optional(),
|
|
570
|
+
suggestions: z9.array(z9.string()).optional()
|
|
528
571
|
});
|
|
529
|
-
var AgentCliStatusSchema =
|
|
572
|
+
var AgentCliStatusSchema = z9.object({
|
|
530
573
|
claude: AgentCliAvailabilitySchema,
|
|
531
574
|
codex: AgentCliAvailabilitySchema
|
|
532
575
|
});
|
|
533
|
-
var DirEntrySchema =
|
|
534
|
-
var FileTreeGroupSchema =
|
|
535
|
-
path:
|
|
536
|
-
entries:
|
|
576
|
+
var DirEntrySchema = z9.object({ name: z9.string(), isDir: z9.boolean() });
|
|
577
|
+
var FileTreeGroupSchema = z9.object({
|
|
578
|
+
path: z9.string(),
|
|
579
|
+
entries: z9.array(DirEntrySchema)
|
|
537
580
|
});
|
|
538
|
-
var CommandEntrySchema =
|
|
539
|
-
name:
|
|
540
|
-
description:
|
|
541
|
-
argumentHint:
|
|
542
|
-
source:
|
|
581
|
+
var CommandEntrySchema = z9.object({
|
|
582
|
+
name: z9.string(),
|
|
583
|
+
description: z9.string(),
|
|
584
|
+
argumentHint: z9.string().optional(),
|
|
585
|
+
source: z9.string()
|
|
543
586
|
});
|
|
544
|
-
var HistorySessionSchema =
|
|
545
|
-
id:
|
|
546
|
-
title:
|
|
547
|
-
projectDir:
|
|
548
|
-
updatedAt:
|
|
549
|
-
provider:
|
|
550
|
-
preferredMode:
|
|
587
|
+
var HistorySessionSchema = z9.object({
|
|
588
|
+
id: z9.string(),
|
|
589
|
+
title: z9.string(),
|
|
590
|
+
projectDir: z9.string(),
|
|
591
|
+
updatedAt: z9.number(),
|
|
592
|
+
provider: z9.enum(providerValues).optional(),
|
|
593
|
+
preferredMode: z9.enum(sessionModeValues).optional()
|
|
551
594
|
});
|
|
552
|
-
var SessionHistoryMessageSchema =
|
|
553
|
-
role:
|
|
554
|
-
text:
|
|
555
|
-
timestamp:
|
|
556
|
-
cursor:
|
|
595
|
+
var SessionHistoryMessageSchema = z9.object({
|
|
596
|
+
role: z9.enum(["user", "assistant", "system"]),
|
|
597
|
+
text: z9.string(),
|
|
598
|
+
timestamp: z9.number().optional(),
|
|
599
|
+
cursor: z9.string().optional()
|
|
557
600
|
});
|
|
558
601
|
var RequestIdShape = { requestId: IdSchema.optional() };
|
|
559
602
|
var RequiredRequestIdShape = { requestId: IdSchema };
|
|
560
|
-
var ControlErrorCodeSchema =
|
|
603
|
+
var ControlErrorCodeSchema = z9.enum(Object.values(ControlErrorCode));
|
|
561
604
|
var RequestErrorShape = {
|
|
562
|
-
error:
|
|
605
|
+
error: z9.string().optional(),
|
|
563
606
|
errorCode: ControlErrorCodeSchema.optional()
|
|
564
607
|
};
|
|
565
|
-
var RemoteFileDispositionSchema =
|
|
566
|
-
var RemoteFileUploadKindSchema =
|
|
608
|
+
var RemoteFileDispositionSchema = z9.enum(["inline", "download"]);
|
|
609
|
+
var RemoteFileUploadKindSchema = z9.enum(["clipboard_image", "file"]);
|
|
567
610
|
function control(type, shape, directions) {
|
|
568
611
|
return {
|
|
569
612
|
type,
|
|
570
613
|
directions: new Set(Array.isArray(directions) ? directions : directions ? [directions] : []),
|
|
571
|
-
schema:
|
|
572
|
-
type:
|
|
614
|
+
schema: z9.object({
|
|
615
|
+
type: z9.literal(type),
|
|
573
616
|
...shape ?? {}
|
|
574
617
|
})
|
|
575
618
|
};
|
|
@@ -577,81 +620,81 @@ function control(type, shape, directions) {
|
|
|
577
620
|
var relayControlDefinitions = [
|
|
578
621
|
control("proxy_register", {
|
|
579
622
|
proxyId: IdSchema,
|
|
580
|
-
name:
|
|
623
|
+
name: z9.string().optional()
|
|
581
624
|
}),
|
|
582
625
|
control("proxy_register_response", {
|
|
583
|
-
status:
|
|
626
|
+
status: z9.enum(["new", "reconnected"])
|
|
584
627
|
}),
|
|
585
628
|
control("proxy_list_request", RequestIdShape),
|
|
586
629
|
control("proxy_list_response", {
|
|
587
630
|
...RequestIdShape,
|
|
588
|
-
proxies:
|
|
631
|
+
proxies: z9.array(ProxyInfoSchema)
|
|
589
632
|
}),
|
|
590
633
|
control("relay_client_list_request", RequestIdShape),
|
|
591
634
|
control("relay_client_list_response", {
|
|
592
635
|
...RequestIdShape,
|
|
593
|
-
clients:
|
|
636
|
+
clients: z9.array(RelayClientInfoSchema)
|
|
594
637
|
}),
|
|
595
638
|
control("relay_client_kick", { ...RequiredRequestIdShape, clientId: IdSchema }),
|
|
596
639
|
control("relay_client_kick_response", {
|
|
597
640
|
...RequiredRequestIdShape,
|
|
598
641
|
clientId: IdSchema,
|
|
599
|
-
success:
|
|
642
|
+
success: z9.boolean(),
|
|
600
643
|
...RequestErrorShape
|
|
601
644
|
}),
|
|
602
645
|
control("relay_client_kicked", {
|
|
603
|
-
reason:
|
|
646
|
+
reason: z9.string().optional()
|
|
604
647
|
}),
|
|
605
648
|
control("remote_file_url_request", {
|
|
606
649
|
...RequiredRequestIdShape,
|
|
607
650
|
sessionId: IdSchema,
|
|
608
|
-
path:
|
|
651
|
+
path: z9.string().min(1),
|
|
609
652
|
disposition: RemoteFileDispositionSchema
|
|
610
653
|
}),
|
|
611
654
|
control("remote_file_url_response", {
|
|
612
655
|
...RequiredRequestIdShape,
|
|
613
656
|
...RequestErrorShape,
|
|
614
657
|
sessionId: IdSchema,
|
|
615
|
-
path:
|
|
616
|
-
success:
|
|
617
|
-
url:
|
|
618
|
-
expiresAt:
|
|
658
|
+
path: z9.string().optional(),
|
|
659
|
+
success: z9.boolean(),
|
|
660
|
+
url: z9.string().optional(),
|
|
661
|
+
expiresAt: z9.number().int().nonnegative().optional()
|
|
619
662
|
}),
|
|
620
663
|
control("remote_file_metadata_request", {
|
|
621
664
|
...RequiredRequestIdShape,
|
|
622
665
|
sessionId: IdSchema,
|
|
623
|
-
path:
|
|
666
|
+
path: z9.string().min(1)
|
|
624
667
|
}),
|
|
625
668
|
control("remote_file_metadata_response", {
|
|
626
669
|
...RequiredRequestIdShape,
|
|
627
670
|
...RequestErrorShape,
|
|
628
671
|
sessionId: IdSchema,
|
|
629
|
-
path:
|
|
630
|
-
success:
|
|
631
|
-
mimeType:
|
|
632
|
-
size:
|
|
633
|
-
fileName:
|
|
672
|
+
path: z9.string().optional(),
|
|
673
|
+
success: z9.boolean(),
|
|
674
|
+
mimeType: z9.string().optional(),
|
|
675
|
+
size: z9.number().int().nonnegative().optional(),
|
|
676
|
+
fileName: z9.string().optional()
|
|
634
677
|
}),
|
|
635
678
|
control("remote_file_stream_request", {
|
|
636
679
|
streamId: IdSchema,
|
|
637
680
|
sessionId: IdSchema,
|
|
638
|
-
path:
|
|
681
|
+
path: z9.string().min(1),
|
|
639
682
|
disposition: RemoteFileDispositionSchema
|
|
640
683
|
}),
|
|
641
684
|
control("remote_file_stream_response", {
|
|
642
685
|
...RequestErrorShape,
|
|
643
686
|
streamId: IdSchema,
|
|
644
687
|
sessionId: IdSchema,
|
|
645
|
-
success:
|
|
646
|
-
path:
|
|
647
|
-
mimeType:
|
|
648
|
-
size:
|
|
649
|
-
fileName:
|
|
688
|
+
success: z9.boolean(),
|
|
689
|
+
path: z9.string().optional(),
|
|
690
|
+
mimeType: z9.string().optional(),
|
|
691
|
+
size: z9.number().int().nonnegative().optional(),
|
|
692
|
+
fileName: z9.string().optional()
|
|
650
693
|
}),
|
|
651
694
|
control("remote_file_stream_complete", {
|
|
652
695
|
...RequestErrorShape,
|
|
653
696
|
streamId: IdSchema,
|
|
654
|
-
success:
|
|
697
|
+
success: z9.boolean()
|
|
655
698
|
}),
|
|
656
699
|
control("remote_file_stream_cancel", {
|
|
657
700
|
streamId: IdSchema
|
|
@@ -660,25 +703,25 @@ var relayControlDefinitions = [
|
|
|
660
703
|
...RequiredRequestIdShape,
|
|
661
704
|
sessionId: IdSchema,
|
|
662
705
|
kind: RemoteFileUploadKindSchema,
|
|
663
|
-
fileName:
|
|
664
|
-
mimeType:
|
|
665
|
-
size:
|
|
706
|
+
fileName: z9.string().optional(),
|
|
707
|
+
mimeType: z9.string().min(1),
|
|
708
|
+
size: z9.number().int().nonnegative().optional()
|
|
666
709
|
}),
|
|
667
710
|
control("remote_file_upload_url_response", {
|
|
668
711
|
...RequiredRequestIdShape,
|
|
669
712
|
...RequestErrorShape,
|
|
670
713
|
sessionId: IdSchema,
|
|
671
|
-
success:
|
|
672
|
-
uploadUrl:
|
|
673
|
-
expiresAt:
|
|
714
|
+
success: z9.boolean(),
|
|
715
|
+
uploadUrl: z9.string().optional(),
|
|
716
|
+
expiresAt: z9.number().int().nonnegative().optional()
|
|
674
717
|
}),
|
|
675
718
|
control("remote_file_upload_stream_request", {
|
|
676
719
|
uploadId: IdSchema,
|
|
677
720
|
sessionId: IdSchema,
|
|
678
721
|
kind: RemoteFileUploadKindSchema,
|
|
679
|
-
fileName:
|
|
680
|
-
mimeType:
|
|
681
|
-
size:
|
|
722
|
+
fileName: z9.string().optional(),
|
|
723
|
+
mimeType: z9.string().min(1),
|
|
724
|
+
size: z9.number().int().nonnegative().optional()
|
|
682
725
|
}),
|
|
683
726
|
control("remote_file_upload_stream_complete", {
|
|
684
727
|
uploadId: IdSchema
|
|
@@ -690,19 +733,19 @@ var relayControlDefinitions = [
|
|
|
690
733
|
...RequestErrorShape,
|
|
691
734
|
uploadId: IdSchema,
|
|
692
735
|
sessionId: IdSchema,
|
|
693
|
-
success:
|
|
694
|
-
path:
|
|
736
|
+
success: z9.boolean(),
|
|
737
|
+
path: z9.string().optional()
|
|
695
738
|
}),
|
|
696
739
|
control("proxy_select", { ...RequestIdShape, proxyId: IdSchema }),
|
|
697
740
|
control("proxy_select_response", {
|
|
698
741
|
...RequestIdShape,
|
|
699
|
-
success:
|
|
742
|
+
success: z9.boolean(),
|
|
700
743
|
proxyId: IdSchema.optional(),
|
|
701
744
|
...RequestErrorShape
|
|
702
745
|
}),
|
|
703
746
|
control("relay_error", {
|
|
704
|
-
code:
|
|
705
|
-
message:
|
|
747
|
+
code: z9.enum(Object.values(RelayErrorCode)),
|
|
748
|
+
message: z9.string(),
|
|
706
749
|
// 可选 requestId: relay 把 client 发来 raw 的 requestId 字段透传回来,
|
|
707
750
|
// client 侧 waitForMessage 据此把对应 pending request 立即拒掉而不必等到 timeout。
|
|
708
751
|
requestId: IdSchema.optional()
|
|
@@ -721,7 +764,7 @@ var relayControlDefinitions = [
|
|
|
721
764
|
control("voice_config_update_response", {
|
|
722
765
|
...RequestIdShape,
|
|
723
766
|
...RequestErrorShape,
|
|
724
|
-
success:
|
|
767
|
+
success: z9.boolean(),
|
|
725
768
|
config: VoiceProviderConfigSchema.optional()
|
|
726
769
|
}),
|
|
727
770
|
control("voice_config_test", {
|
|
@@ -731,15 +774,15 @@ var relayControlDefinitions = [
|
|
|
731
774
|
control("voice_config_test_response", {
|
|
732
775
|
...RequestIdShape,
|
|
733
776
|
...RequestErrorShape,
|
|
734
|
-
success:
|
|
735
|
-
audioBase64:
|
|
736
|
-
audioSampleRate:
|
|
737
|
-
audioEncoding:
|
|
738
|
-
transcript:
|
|
777
|
+
success: z9.boolean(),
|
|
778
|
+
audioBase64: z9.string().optional(),
|
|
779
|
+
audioSampleRate: z9.number().int().positive().optional(),
|
|
780
|
+
audioEncoding: z9.literal("pcm_s16le").optional(),
|
|
781
|
+
transcript: z9.string().optional()
|
|
739
782
|
}),
|
|
740
783
|
control("voice_capabilities_request", {
|
|
741
784
|
...RequestIdShape,
|
|
742
|
-
region:
|
|
785
|
+
region: z9.enum(voiceRegionValues).optional()
|
|
743
786
|
}),
|
|
744
787
|
control("voice_capabilities_response", {
|
|
745
788
|
...RequestIdShape,
|
|
@@ -751,37 +794,37 @@ var relayControlDefinitions = [
|
|
|
751
794
|
control("latency_web_relay_ping", RequiredRequestIdShape),
|
|
752
795
|
control("latency_web_relay_pong", {
|
|
753
796
|
...RequiredRequestIdShape,
|
|
754
|
-
relayNow:
|
|
797
|
+
relayNow: z9.number().optional()
|
|
755
798
|
}),
|
|
756
799
|
control("latency_relay_proxy_request", RequiredRequestIdShape),
|
|
757
800
|
control("latency_relay_proxy_response", {
|
|
758
801
|
...RequiredRequestIdShape,
|
|
759
|
-
success:
|
|
760
|
-
rttMs:
|
|
761
|
-
error:
|
|
802
|
+
success: z9.boolean(),
|
|
803
|
+
rttMs: z9.number().nonnegative().optional(),
|
|
804
|
+
error: z9.string().optional()
|
|
762
805
|
}),
|
|
763
806
|
control("latency_relay_proxy_ping", {
|
|
764
807
|
...RequiredRequestIdShape,
|
|
765
|
-
relayNow:
|
|
808
|
+
relayNow: z9.number().optional()
|
|
766
809
|
}),
|
|
767
810
|
control("latency_relay_proxy_pong", {
|
|
768
811
|
...RequiredRequestIdShape,
|
|
769
|
-
proxyNow:
|
|
812
|
+
proxyNow: z9.number().optional()
|
|
770
813
|
}),
|
|
771
814
|
control("latency_web_proxy_ping", RequiredRequestIdShape, "client_to_proxy"),
|
|
772
|
-
control("latency_web_proxy_pong", { ...RequiredRequestIdShape, proxyNow:
|
|
815
|
+
control("latency_web_proxy_pong", { ...RequiredRequestIdShape, proxyNow: z9.number().optional() }, "proxy_to_client"),
|
|
773
816
|
// 客户端注册协议
|
|
774
817
|
control("client_register", {
|
|
775
818
|
clientId: IdSchema,
|
|
776
|
-
userAgent:
|
|
777
|
-
platform:
|
|
778
|
-
maxTouchPoints:
|
|
779
|
-
browserName:
|
|
780
|
-
osName:
|
|
781
|
-
deviceKind:
|
|
819
|
+
userAgent: z9.string().optional(),
|
|
820
|
+
platform: z9.string().optional(),
|
|
821
|
+
maxTouchPoints: z9.number().int().nonnegative().optional(),
|
|
822
|
+
browserName: z9.string().min(1),
|
|
823
|
+
osName: z9.string().min(1),
|
|
824
|
+
deviceKind: z9.enum(["desktop", "tablet", "phone", "unknown"])
|
|
782
825
|
}),
|
|
783
826
|
control("client_register_response", {
|
|
784
|
-
status:
|
|
827
|
+
status: z9.enum(["restored", "proxy_offline", "new"]),
|
|
785
828
|
proxyId: IdSchema.optional()
|
|
786
829
|
}),
|
|
787
830
|
// Proxy 离线通知
|
|
@@ -800,51 +843,51 @@ var relayControlDefinitions = [
|
|
|
800
843
|
control("dir_list_request", {
|
|
801
844
|
proxyId: IdSchema.optional(),
|
|
802
845
|
...RequestIdShape,
|
|
803
|
-
path:
|
|
846
|
+
path: z9.string()
|
|
804
847
|
}, "client_to_proxy"),
|
|
805
|
-
control("dir_list_response", { ...RequestIdShape, ...RequestErrorShape, entries:
|
|
848
|
+
control("dir_list_response", { ...RequestIdShape, ...RequestErrorShape, entries: z9.array(DirEntrySchema), path: z9.string() }, "proxy_to_client"),
|
|
806
849
|
// 目录创建请求与响应
|
|
807
|
-
control("dir_create_request", { ...RequestIdShape, path:
|
|
850
|
+
control("dir_create_request", { ...RequestIdShape, path: z9.string() }, "client_to_proxy"),
|
|
808
851
|
control("dir_create_response", {
|
|
809
852
|
...RequestIdShape,
|
|
810
853
|
...RequestErrorShape,
|
|
811
|
-
path:
|
|
812
|
-
success:
|
|
854
|
+
path: z9.string(),
|
|
855
|
+
success: z9.boolean()
|
|
813
856
|
}, "proxy_to_client"),
|
|
814
857
|
// 命令列表推送,proxy 将可用命令列表推给 client
|
|
815
|
-
control("command_list_push", { commands:
|
|
858
|
+
control("command_list_push", { commands: z9.array(CommandEntrySchema) }, "proxy_to_client"),
|
|
816
859
|
// 文件树推送: 按目录分组, 首组 path 即为 session cwd
|
|
817
860
|
// 前端直接把每组写入 tree[path], 与 dir_list_response 共享 cache slot
|
|
818
861
|
control("file_tree_push", {
|
|
819
|
-
groups:
|
|
862
|
+
groups: z9.array(FileTreeGroupSchema)
|
|
820
863
|
}, "proxy_to_client"),
|
|
821
864
|
// 会话列表请求与权限模式变更
|
|
822
865
|
control("session_list", void 0, ["client_to_proxy", "proxy_to_client"]),
|
|
823
866
|
control("permission_mode_change", {
|
|
824
|
-
mode:
|
|
867
|
+
mode: z9.enum(["default", "auto_accept", "plan"]),
|
|
825
868
|
// sessionId 可选:传入时 proxy 按该会话的 mode 分叉(PTY 发 Tab ANSI),未传走全局日志行为
|
|
826
869
|
sessionId: IdSchema.optional()
|
|
827
870
|
}, "client_to_proxy"),
|
|
828
871
|
// 会话历史浏览
|
|
829
872
|
control("session_history_request", RequestIdShape, "client_to_proxy"),
|
|
830
|
-
control("session_history_response", { ...RequestIdShape, sessions:
|
|
873
|
+
control("session_history_response", { ...RequestIdShape, sessions: z9.array(HistorySessionSchema) }, "proxy_to_client"),
|
|
831
874
|
// PTY 语义状态,从 Envelope 迁移到 Control 层
|
|
832
875
|
control("pty_state", { sessionId: IdSchema, payload: PtyStatePayloadSchema }, "proxy_to_client"),
|
|
833
876
|
// Provider 语义状态,来自 Claude/Codex hook 等结构化事件,不从 PTY 字节推断
|
|
834
877
|
control("agent_status", { sessionId: IdSchema, payload: AgentStatusPayloadSchema }, "proxy_to_client"),
|
|
835
878
|
// 终端标题变化,proxy -> client
|
|
836
|
-
control("terminal_title", { sessionId: IdSchema, title:
|
|
879
|
+
control("terminal_title", { sessionId: IdSchema, title: z9.string() }, "proxy_to_client"),
|
|
837
880
|
// 终端尺寸变化,proxy -> client
|
|
838
|
-
control("terminal_resize", { sessionId: IdSchema, cols:
|
|
839
|
-
control("terminal_resize_request", { sessionId: IdSchema, cols:
|
|
881
|
+
control("terminal_resize", { sessionId: IdSchema, cols: z9.number().int().positive(), rows: z9.number().int().positive() }, "proxy_to_client"),
|
|
882
|
+
control("terminal_resize_request", { sessionId: IdSchema, cols: z9.number().int().positive(), rows: z9.number().int().positive() }, "client_to_proxy"),
|
|
840
883
|
// 远程终止 JSON 会话,client -> proxy
|
|
841
884
|
control("session_terminate", { sessionId: IdSchema }, "client_to_proxy"),
|
|
842
|
-
control("session_rename", { ...RequestIdShape, sessionId: IdSchema, name:
|
|
885
|
+
control("session_rename", { ...RequestIdShape, sessionId: IdSchema, name: z9.string() }, "client_to_proxy"),
|
|
843
886
|
control("session_rename_response", {
|
|
844
887
|
...RequestIdShape,
|
|
845
888
|
sessionId: IdSchema,
|
|
846
|
-
success:
|
|
847
|
-
name:
|
|
889
|
+
success: z9.boolean(),
|
|
890
|
+
name: z9.string().optional(),
|
|
848
891
|
...RequestErrorShape
|
|
849
892
|
}, "proxy_to_client"),
|
|
850
893
|
// 中断当前 turn,client -> proxy;JSON 由 session-worker 打断 Claude 子进程并保留会话。
|
|
@@ -852,55 +895,55 @@ var relayControlDefinitions = [
|
|
|
852
895
|
// turn 完成信号,proxy -> client,对应 claude stream-json 的 result 事件
|
|
853
896
|
control("turn_result", {
|
|
854
897
|
sessionId: IdSchema,
|
|
855
|
-
success:
|
|
856
|
-
isError:
|
|
898
|
+
success: z9.boolean(),
|
|
899
|
+
isError: z9.boolean(),
|
|
857
900
|
// stream-json result.result 是本轮最终文本。assistant_message 流丢失或 CLI 未发增量时,
|
|
858
901
|
// Web 用它作为 JSON 模式兜底展示,避免 turn 已结束但界面空白。
|
|
859
|
-
result:
|
|
902
|
+
result: z9.string().optional()
|
|
860
903
|
}, "proxy_to_client"),
|
|
861
904
|
// 客户端发送到 PTY 的原始字节(ANSI 序列),不追加换行
|
|
862
|
-
control("remote_input_raw", { sessionId: IdSchema, data:
|
|
905
|
+
control("remote_input_raw", { sessionId: IdSchema, data: z9.string(), traceId: IdSchema.optional() }, "client_to_proxy"),
|
|
863
906
|
// 客户端询问 proxy 的环境信息 (home 路径等), client -> proxy -> response
|
|
864
907
|
// FilePathPicker 用 homePath 作为 select 模式下的默认起点, 新建会话时打开即可浏览
|
|
865
908
|
control("proxy_info_request", RequestIdShape, "client_to_proxy"),
|
|
866
|
-
control("proxy_info", { ...RequestIdShape, homePath:
|
|
867
|
-
control("agent_cli_config_update", { ...RequestIdShape, provider:
|
|
909
|
+
control("proxy_info", { ...RequestIdShape, homePath: z9.string(), agentCli: AgentCliStatusSchema }, "proxy_to_client"),
|
|
910
|
+
control("agent_cli_config_update", { ...RequestIdShape, provider: z9.enum(providerValues), path: z9.string().min(1) }, "client_to_proxy"),
|
|
868
911
|
control("agent_cli_config_update_response", {
|
|
869
912
|
...RequestIdShape,
|
|
870
|
-
provider:
|
|
913
|
+
provider: z9.enum(providerValues),
|
|
871
914
|
agentCli: AgentCliStatusSchema.optional(),
|
|
872
915
|
...RequestErrorShape
|
|
873
916
|
}, "proxy_to_client"),
|
|
874
917
|
// 远程创建 JSON 会话,client -> proxy -> response
|
|
875
918
|
control("session_create", {
|
|
876
919
|
...RequestIdShape,
|
|
877
|
-
kind:
|
|
878
|
-
cwd:
|
|
879
|
-
name:
|
|
880
|
-
provider:
|
|
881
|
-
mode:
|
|
882
|
-
resumeSessionId:
|
|
920
|
+
kind: z9.enum(sessionKindValues).optional(),
|
|
921
|
+
cwd: z9.string().optional(),
|
|
922
|
+
name: z9.string().optional(),
|
|
923
|
+
provider: z9.enum(providerValues).optional(),
|
|
924
|
+
mode: z9.enum(sessionModeValues).optional(),
|
|
925
|
+
resumeSessionId: z9.string().optional(),
|
|
883
926
|
// 透传给 claude CLI 的 --permission-mode, undefined 时 proxy 兜底为 "default"
|
|
884
|
-
permissionMode:
|
|
927
|
+
permissionMode: z9.enum(["default", "auto", "acceptEdits", "plan", "bypassPermissions", "dontAsk"]).optional()
|
|
885
928
|
}, "client_to_proxy"),
|
|
886
929
|
control("session_create_response", {
|
|
887
930
|
...RequestIdShape,
|
|
888
931
|
// 失败路径只送 errorCode/error, sessionId 此时无语义。成功路径才有 id。
|
|
889
932
|
sessionId: IdSchema.optional(),
|
|
890
|
-
name:
|
|
891
|
-
nameLocked:
|
|
892
|
-
kind:
|
|
893
|
-
mode:
|
|
894
|
-
provider:
|
|
895
|
-
ptyOwner:
|
|
933
|
+
name: z9.string().optional(),
|
|
934
|
+
nameLocked: z9.boolean().optional(),
|
|
935
|
+
kind: z9.enum(sessionKindValues).optional(),
|
|
936
|
+
mode: z9.enum(sessionModeValues).optional(),
|
|
937
|
+
provider: z9.enum(providerValues).optional(),
|
|
938
|
+
ptyOwner: z9.enum(ptyOwnerValues).optional(),
|
|
896
939
|
...RequestErrorShape
|
|
897
940
|
}, "proxy_to_client"),
|
|
898
941
|
// 客户端请求会话历史消息,client -> proxy
|
|
899
942
|
control("session_messages_request", {
|
|
900
943
|
...RequestIdShape,
|
|
901
944
|
sessionId: IdSchema,
|
|
902
|
-
limit:
|
|
903
|
-
before:
|
|
945
|
+
limit: z9.number().int().min(1).max(200).optional(),
|
|
946
|
+
before: z9.string().optional()
|
|
904
947
|
}, "client_to_proxy"),
|
|
905
948
|
// 客户端请求会话资源(命令列表 + 文件树),client -> proxy
|
|
906
949
|
control("session_resources_request", { ...RequestIdShape, sessionId: IdSchema }, "client_to_proxy"),
|
|
@@ -908,14 +951,14 @@ var relayControlDefinitions = [
|
|
|
908
951
|
...RequestIdShape,
|
|
909
952
|
...RequestErrorShape,
|
|
910
953
|
sessionId: IdSchema,
|
|
911
|
-
commands:
|
|
912
|
-
groups:
|
|
954
|
+
commands: z9.array(CommandEntrySchema),
|
|
955
|
+
groups: z9.array(FileTreeGroupSchema)
|
|
913
956
|
}, "proxy_to_client"),
|
|
914
957
|
// 客户端请求当前 provider 语义状态;不经 relay 缓存,由 proxy 返回当前值
|
|
915
958
|
control("agent_status_request", { ...RequestIdShape, sessionId: IdSchema.optional() }, "client_to_proxy"),
|
|
916
959
|
control("agent_status_response", {
|
|
917
960
|
...RequestIdShape,
|
|
918
|
-
statuses:
|
|
961
|
+
statuses: z9.array(z9.object({ sessionId: IdSchema, payload: AgentStatusPayloadSchema }))
|
|
919
962
|
}, "proxy_to_client"),
|
|
920
963
|
// 客户端确认已收到审批请求;proxy 只记录送达状态,不把它当成用户决策
|
|
921
964
|
control("permission_request_delivered", { sessionId: IdSchema, requestId: IdSchema }, "client_to_proxy"),
|
|
@@ -925,17 +968,17 @@ var relayControlDefinitions = [
|
|
|
925
968
|
control("permission_decision_result", {
|
|
926
969
|
sessionId: IdSchema,
|
|
927
970
|
requestId: IdSchema,
|
|
928
|
-
outcome:
|
|
929
|
-
delivered:
|
|
930
|
-
message:
|
|
971
|
+
outcome: z9.enum(["allow", "deny"]),
|
|
972
|
+
delivered: z9.boolean(),
|
|
973
|
+
message: z9.string().optional()
|
|
931
974
|
}, "proxy_to_client"),
|
|
932
975
|
// proxy 推送当前 pending 的工具审批列表,client 据此恢复审批卡片
|
|
933
976
|
control("pending_approvals_push", {
|
|
934
977
|
sessionId: IdSchema,
|
|
935
|
-
approvals:
|
|
978
|
+
approvals: z9.array(z9.object({
|
|
936
979
|
requestId: IdSchema,
|
|
937
|
-
toolName:
|
|
938
|
-
input:
|
|
980
|
+
toolName: z9.string(),
|
|
981
|
+
input: z9.record(z9.string(), z9.unknown())
|
|
939
982
|
}))
|
|
940
983
|
}, "proxy_to_client"),
|
|
941
984
|
// Voice Pilot speech summaries are produced by proxy-side Claude Code so it can read project context.
|
|
@@ -943,7 +986,7 @@ var relayControlDefinitions = [
|
|
|
943
986
|
...RequestIdShape,
|
|
944
987
|
sessionId: IdSchema,
|
|
945
988
|
messageId: IdSchema,
|
|
946
|
-
text:
|
|
989
|
+
text: z9.string().min(1),
|
|
947
990
|
reason: VoiceSummaryReasonSchema
|
|
948
991
|
}, "client_to_proxy"),
|
|
949
992
|
control("voice_summary_response", {
|
|
@@ -951,31 +994,31 @@ var relayControlDefinitions = [
|
|
|
951
994
|
...RequestErrorShape,
|
|
952
995
|
sessionId: IdSchema,
|
|
953
996
|
messageId: IdSchema,
|
|
954
|
-
success:
|
|
955
|
-
summary:
|
|
997
|
+
success: z9.boolean(),
|
|
998
|
+
summary: z9.string().min(1).optional()
|
|
956
999
|
}, "proxy_to_client"),
|
|
957
1000
|
// 恢复会话时推送历史消息,proxy -> client
|
|
958
1001
|
control("session_history_messages", {
|
|
959
1002
|
...RequestIdShape,
|
|
960
1003
|
sessionId: IdSchema,
|
|
961
|
-
before:
|
|
962
|
-
messages:
|
|
963
|
-
hasMore:
|
|
964
|
-
nextBefore:
|
|
1004
|
+
before: z9.string().optional(),
|
|
1005
|
+
messages: z9.array(SessionHistoryMessageSchema),
|
|
1006
|
+
hasMore: z9.boolean().optional(),
|
|
1007
|
+
nextBefore: z9.string().optional()
|
|
965
1008
|
}, "proxy_to_client"),
|
|
966
1009
|
// proxy 重连后同步活跃 session 列表给 relay。session_sync 由 relay 自消费(更新 proxy-session
|
|
967
1010
|
// 关联)不转发给 client,因此**没有** direction 标注——RelayControlDirection 只描述转发流。
|
|
968
1011
|
control("session_sync", {
|
|
969
|
-
sessions:
|
|
970
|
-
id:
|
|
971
|
-
kind:
|
|
972
|
-
mode:
|
|
973
|
-
provider:
|
|
974
|
-
ptyOwner:
|
|
975
|
-
cwd:
|
|
976
|
-
name:
|
|
977
|
-
nameLocked:
|
|
978
|
-
state:
|
|
1012
|
+
sessions: z9.array(z9.object({
|
|
1013
|
+
id: z9.string(),
|
|
1014
|
+
kind: z9.enum(sessionKindValues).optional(),
|
|
1015
|
+
mode: z9.enum(sessionModeValues),
|
|
1016
|
+
provider: z9.enum(providerValues),
|
|
1017
|
+
ptyOwner: z9.enum(ptyOwnerValues).optional(),
|
|
1018
|
+
cwd: z9.string().optional(),
|
|
1019
|
+
name: z9.string().optional(),
|
|
1020
|
+
nameLocked: z9.boolean().optional(),
|
|
1021
|
+
state: z9.enum(sessionStateValues)
|
|
979
1022
|
}))
|
|
980
1023
|
}),
|
|
981
1024
|
// PTY 会话订阅,client -> proxy,触发 terminal serialize() 返回当前状态
|
|
@@ -983,15 +1026,15 @@ var relayControlDefinitions = [
|
|
|
983
1026
|
// PTY 会话快照,proxy -> client,serialize() 的全量终端状态
|
|
984
1027
|
control("session_snapshot", {
|
|
985
1028
|
sessionId: IdSchema,
|
|
986
|
-
cols:
|
|
987
|
-
rows:
|
|
988
|
-
data:
|
|
989
|
-
outputSeq:
|
|
1029
|
+
cols: z9.number().int().positive(),
|
|
1030
|
+
rows: z9.number().int().positive(),
|
|
1031
|
+
data: z9.string(),
|
|
1032
|
+
outputSeq: z9.number().int().nonnegative(),
|
|
990
1033
|
requestId: IdSchema.optional()
|
|
991
1034
|
}, "proxy_to_client")
|
|
992
1035
|
];
|
|
993
1036
|
var relayControlSchemas = relayControlDefinitions.map((definition) => definition.schema);
|
|
994
|
-
var RelayControlSchema =
|
|
1037
|
+
var RelayControlSchema = z9.discriminatedUnion("type", relayControlSchemas);
|
|
995
1038
|
var ProxyToClientRelayControlTypes = new Set(relayControlDefinitions.filter((definition) => definition.directions.has("proxy_to_client")).map((definition) => definition.type));
|
|
996
1039
|
function isProxyToClientRelayControlType(type) {
|
|
997
1040
|
return ProxyToClientRelayControlTypes.has(type);
|
|
@@ -1001,6 +1044,11 @@ function isClientToProxyRelayControlType(type) {
|
|
|
1001
1044
|
return ClientToProxyRelayControlTypes.has(type);
|
|
1002
1045
|
}
|
|
1003
1046
|
|
|
1047
|
+
// ../../packages/shared/dist/constants/session-create.js
|
|
1048
|
+
var SESSION_CREATE_SERVER_DEADLINE_MS = 3e4;
|
|
1049
|
+
var SESSION_CREATE_RESPONSE_GRACE_MS = 5e3;
|
|
1050
|
+
var SESSION_CREATE_CLIENT_TIMEOUT_MS = SESSION_CREATE_SERVER_DEADLINE_MS + SESSION_CREATE_RESPONSE_GRACE_MS;
|
|
1051
|
+
|
|
1004
1052
|
// ../../packages/shared/dist/constants/relay-close-codes.js
|
|
1005
1053
|
var RelayCloseCode = {
|
|
1006
1054
|
CLIENT_KICKED: 4401,
|
|
@@ -1052,6 +1100,25 @@ function decodeFileStreamFrame(view) {
|
|
|
1052
1100
|
return { streamId, chunkSeq, data };
|
|
1053
1101
|
}
|
|
1054
1102
|
|
|
1103
|
+
// ../../packages/shared/dist/voice-audio-codec.js
|
|
1104
|
+
var MU_LAW_BIAS = 132;
|
|
1105
|
+
function decodeSample(encodedValue) {
|
|
1106
|
+
const value = ~encodedValue & 255;
|
|
1107
|
+
const sign = value & 128;
|
|
1108
|
+
const exponent = value >> 4 & 7;
|
|
1109
|
+
const mantissa = value & 15;
|
|
1110
|
+
const magnitude = ((mantissa << 3) + MU_LAW_BIAS) * 2 ** exponent - MU_LAW_BIAS;
|
|
1111
|
+
return sign ? -magnitude : magnitude;
|
|
1112
|
+
}
|
|
1113
|
+
function decodeMuLawToPcm16(encoded) {
|
|
1114
|
+
const pcm = new Uint8Array(encoded.byteLength * 2);
|
|
1115
|
+
const view = new DataView(pcm.buffer);
|
|
1116
|
+
for (let index = 0; index < encoded.byteLength; index += 1) {
|
|
1117
|
+
view.setInt16(index * 2, decodeSample(encoded[index] ?? 255), true);
|
|
1118
|
+
}
|
|
1119
|
+
return pcm;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1055
1122
|
// ../../packages/shared/dist/state-machine.js
|
|
1056
1123
|
function computeAbsorbingSet(transitions) {
|
|
1057
1124
|
const absorbing = /* @__PURE__ */ new Set();
|
|
@@ -1228,6 +1295,14 @@ var RelayRegistry = class {
|
|
|
1228
1295
|
binding.ws = null;
|
|
1229
1296
|
}
|
|
1230
1297
|
}
|
|
1298
|
+
// 连接关闭只能释放自己持有的绑定。旧连接的 close 可能晚于新连接注册,
|
|
1299
|
+
// 此时不能把已经切换到新 WebSocket 的绑定清空。
|
|
1300
|
+
unbindClientSocket(clientId, ws) {
|
|
1301
|
+
const binding = this.clientBindings.get(clientId);
|
|
1302
|
+
if (!binding || binding.ws !== ws) return false;
|
|
1303
|
+
binding.ws = null;
|
|
1304
|
+
return true;
|
|
1305
|
+
}
|
|
1231
1306
|
getClientBinding(clientId) {
|
|
1232
1307
|
return this.clientBindings.get(clientId);
|
|
1233
1308
|
}
|
|
@@ -1899,6 +1974,7 @@ function bailianInferenceUrl(region) {
|
|
|
1899
1974
|
// src/voice/bailian-asr.ts
|
|
1900
1975
|
var OPEN = 1;
|
|
1901
1976
|
var END_OF_SPEECH_SILENCE_MS = 1200;
|
|
1977
|
+
var NOISE_RESISTANT_SERVER_VAD_THRESHOLD = 0.8;
|
|
1902
1978
|
function defaultSocketFactory(url, options) {
|
|
1903
1979
|
return new WebSocket5(url, options);
|
|
1904
1980
|
}
|
|
@@ -1974,6 +2050,7 @@ var BailianAsrClientImpl = class extends EventEmitter {
|
|
|
1974
2050
|
socket;
|
|
1975
2051
|
isOpen = false;
|
|
1976
2052
|
isReady = false;
|
|
2053
|
+
hasFinalTranscript = false;
|
|
1977
2054
|
pending = [];
|
|
1978
2055
|
sendPcm(chunk) {
|
|
1979
2056
|
this.sendWhenReady({
|
|
@@ -2005,7 +2082,7 @@ var BailianAsrClientImpl = class extends EventEmitter {
|
|
|
2005
2082
|
},
|
|
2006
2083
|
turn_detection: {
|
|
2007
2084
|
type: "server_vad",
|
|
2008
|
-
threshold:
|
|
2085
|
+
threshold: NOISE_RESISTANT_SERVER_VAD_THRESHOLD,
|
|
2009
2086
|
silence_duration_ms: END_OF_SPEECH_SILENCE_MS
|
|
2010
2087
|
}
|
|
2011
2088
|
}
|
|
@@ -2041,11 +2118,21 @@ var BailianAsrClientImpl = class extends EventEmitter {
|
|
|
2041
2118
|
if (preview) this.emit("partial", preview);
|
|
2042
2119
|
return;
|
|
2043
2120
|
}
|
|
2044
|
-
if (type === "conversation.item.input_audio_transcription.completed"
|
|
2121
|
+
if (type === "conversation.item.input_audio_transcription.completed") {
|
|
2045
2122
|
const transcript = extractFinalText(payload);
|
|
2046
2123
|
if (transcript) {
|
|
2124
|
+
this.hasFinalTranscript = true;
|
|
2125
|
+
this.emit("final", transcript);
|
|
2126
|
+
}
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (type === "session.finished") {
|
|
2130
|
+
const transcript = extractFinalText(payload);
|
|
2131
|
+
if (transcript && !this.hasFinalTranscript) {
|
|
2132
|
+
this.hasFinalTranscript = true;
|
|
2047
2133
|
this.emit("final", transcript);
|
|
2048
2134
|
}
|
|
2135
|
+
this.socket.close(1e3, "session-finished");
|
|
2049
2136
|
}
|
|
2050
2137
|
}
|
|
2051
2138
|
sendWhenReady(payload) {
|
|
@@ -2230,8 +2317,48 @@ function createBailianTtsClient(config, options = {}) {
|
|
|
2230
2317
|
);
|
|
2231
2318
|
}
|
|
2232
2319
|
|
|
2320
|
+
// src/voice/pcm-resample.ts
|
|
2321
|
+
import waveResampler from "wave-resampler";
|
|
2322
|
+
var RESAMPLE_DETAILS = {
|
|
2323
|
+
method: "sinc",
|
|
2324
|
+
LPF: true,
|
|
2325
|
+
LPFType: "FIR"
|
|
2326
|
+
};
|
|
2327
|
+
function resampleMonoPcmS16le(audio, inputSampleRate, outputSampleRate) {
|
|
2328
|
+
if (audio.byteLength % 2 !== 0) {
|
|
2329
|
+
throw new Error("PCM \u97F3\u9891\u6570\u636E\u957F\u5EA6\u65E0\u6548");
|
|
2330
|
+
}
|
|
2331
|
+
if (!validSampleRate(inputSampleRate) || !validSampleRate(outputSampleRate)) {
|
|
2332
|
+
throw new Error("PCM \u97F3\u9891\u91C7\u6837\u7387\u65E0\u6548");
|
|
2333
|
+
}
|
|
2334
|
+
if (inputSampleRate === outputSampleRate) return Buffer.from(audio);
|
|
2335
|
+
const input = new Int16Array(audio.byteLength / 2);
|
|
2336
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
2337
|
+
input[index] = audio.readInt16LE(index * 2);
|
|
2338
|
+
}
|
|
2339
|
+
const samples = waveResampler.resample(
|
|
2340
|
+
input,
|
|
2341
|
+
inputSampleRate,
|
|
2342
|
+
outputSampleRate,
|
|
2343
|
+
RESAMPLE_DETAILS
|
|
2344
|
+
);
|
|
2345
|
+
const output = Buffer.alloc(samples.length * 2);
|
|
2346
|
+
for (let index = 0; index < samples.length; index += 1) {
|
|
2347
|
+
const sample = samples[index];
|
|
2348
|
+
if (typeof sample !== "number" || !Number.isFinite(sample)) {
|
|
2349
|
+
throw new Error("PCM \u97F3\u9891\u91CD\u91C7\u6837\u5931\u8D25");
|
|
2350
|
+
}
|
|
2351
|
+
output.writeInt16LE(Math.max(-32768, Math.min(32767, Math.round(sample))), index * 2);
|
|
2352
|
+
}
|
|
2353
|
+
return output;
|
|
2354
|
+
}
|
|
2355
|
+
function validSampleRate(sampleRate) {
|
|
2356
|
+
return Number.isSafeInteger(sampleRate) && sampleRate > 0;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2233
2359
|
// src/voice/config-test.ts
|
|
2234
|
-
var
|
|
2360
|
+
var TTS_TEST_SAMPLE_RATE = 24e3;
|
|
2361
|
+
var ASR_TEST_SAMPLE_RATE = 16e3;
|
|
2235
2362
|
var ASR_TEST_CHUNK_BYTES = 3200;
|
|
2236
2363
|
var ASR_TEST_CHUNK_INTERVAL_MS = 100;
|
|
2237
2364
|
function mergeVoiceConfigForTest(current, update) {
|
|
@@ -2263,14 +2390,19 @@ function createBailianVoiceConfigTester(options = {}) {
|
|
|
2263
2390
|
timeoutMs,
|
|
2264
2391
|
clientFactory: ttsClientFactory
|
|
2265
2392
|
});
|
|
2393
|
+
const recognitionAudio = resampleMonoPcmS16le(
|
|
2394
|
+
audio,
|
|
2395
|
+
TTS_TEST_SAMPLE_RATE,
|
|
2396
|
+
ASR_TEST_SAMPLE_RATE
|
|
2397
|
+
);
|
|
2266
2398
|
const transcript = await recognizeTestAudio({
|
|
2267
2399
|
config,
|
|
2268
|
-
audio,
|
|
2400
|
+
audio: recognitionAudio,
|
|
2269
2401
|
sampleText,
|
|
2270
2402
|
timeoutMs,
|
|
2271
2403
|
clientFactory: asrClientFactory
|
|
2272
2404
|
});
|
|
2273
|
-
return { audio, sampleRate:
|
|
2405
|
+
return { audio, sampleRate: TTS_TEST_SAMPLE_RATE, transcript };
|
|
2274
2406
|
}
|
|
2275
2407
|
};
|
|
2276
2408
|
}
|
|
@@ -2281,7 +2413,7 @@ function synthesizeTestAudio(options) {
|
|
|
2281
2413
|
region: config.region,
|
|
2282
2414
|
model: config.ttsModel,
|
|
2283
2415
|
voice: config.ttsVoice,
|
|
2284
|
-
sampleRate:
|
|
2416
|
+
sampleRate: TTS_TEST_SAMPLE_RATE
|
|
2285
2417
|
});
|
|
2286
2418
|
return new Promise((resolve2, reject) => {
|
|
2287
2419
|
let settled = false;
|
|
@@ -2324,7 +2456,7 @@ function recognizeTestAudio(options) {
|
|
|
2324
2456
|
apiKey: config.apiKey,
|
|
2325
2457
|
region: config.region,
|
|
2326
2458
|
model: config.asrModel,
|
|
2327
|
-
sampleRate:
|
|
2459
|
+
sampleRate: ASR_TEST_SAMPLE_RATE,
|
|
2328
2460
|
language: "zh"
|
|
2329
2461
|
});
|
|
2330
2462
|
return new Promise((resolve2, reject) => {
|
|
@@ -3036,7 +3168,7 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
|
|
|
3036
3168
|
clientWs.on("close", (code, reason) => {
|
|
3037
3169
|
registry.removeClientWs(clientWs);
|
|
3038
3170
|
if (clientWs.clientId) {
|
|
3039
|
-
registry.
|
|
3171
|
+
registry.unbindClientSocket(clientWs.clientId, clientWs);
|
|
3040
3172
|
}
|
|
3041
3173
|
logger.info(
|
|
3042
3174
|
{
|
|
@@ -3242,59 +3374,190 @@ function parseJson(data) {
|
|
|
3242
3374
|
return null;
|
|
3243
3375
|
}
|
|
3244
3376
|
}
|
|
3245
|
-
function
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3377
|
+
function buildStats(payload, config) {
|
|
3378
|
+
return {
|
|
3379
|
+
sessionId: payload.sessionId,
|
|
3380
|
+
attemptId: payload.attemptId,
|
|
3381
|
+
sampleRate: payload.sampleRate,
|
|
3382
|
+
provider: config.provider,
|
|
3383
|
+
region: config.region,
|
|
3384
|
+
asrModel: config.asrModel,
|
|
3385
|
+
encoding: payload.encoding,
|
|
3386
|
+
startedAt: Date.now(),
|
|
3387
|
+
readyAt: null,
|
|
3388
|
+
firstPcmAt: null,
|
|
3389
|
+
encodedBytes: 0,
|
|
3390
|
+
pcmBytes: 0,
|
|
3391
|
+
pcmChunks: 0,
|
|
3392
|
+
partialEvents: 0,
|
|
3393
|
+
finalEvents: 0,
|
|
3394
|
+
finalChars: 0
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
function statsLogFields(stats) {
|
|
3398
|
+
const now = Date.now();
|
|
3399
|
+
return {
|
|
3400
|
+
sessionId: stats.sessionId,
|
|
3401
|
+
attemptId: stats.attemptId,
|
|
3402
|
+
sampleRate: stats.sampleRate,
|
|
3403
|
+
provider: stats.provider,
|
|
3404
|
+
region: stats.region,
|
|
3405
|
+
asrModel: stats.asrModel,
|
|
3406
|
+
encoding: stats.encoding,
|
|
3407
|
+
encodedBytes: stats.encodedBytes,
|
|
3408
|
+
pcmBytes: stats.pcmBytes,
|
|
3409
|
+
pcmChunks: stats.pcmChunks,
|
|
3410
|
+
partialEvents: stats.partialEvents,
|
|
3411
|
+
finalEvents: stats.finalEvents,
|
|
3412
|
+
finalChars: stats.finalChars,
|
|
3413
|
+
durationMs: now - stats.startedAt,
|
|
3414
|
+
readyMs: stats.readyAt === null ? null : stats.readyAt - stats.startedAt,
|
|
3415
|
+
firstPcmMs: stats.firstPcmAt === null ? null : stats.firstPcmAt - stats.startedAt
|
|
3416
|
+
};
|
|
3249
3417
|
}
|
|
3250
3418
|
function handleVoiceAsrConnection(ws, store, logger, providers) {
|
|
3251
3419
|
let provider = null;
|
|
3420
|
+
let activeStats = null;
|
|
3421
|
+
function isCurrent(candidate, stats, event) {
|
|
3422
|
+
if (provider === candidate && activeStats === stats) return true;
|
|
3423
|
+
logger.debug(
|
|
3424
|
+
{ attemptId: stats.attemptId, activeAttemptId: activeStats?.attemptId ?? null, event },
|
|
3425
|
+
"Ignored stale Voice ASR provider event"
|
|
3426
|
+
);
|
|
3427
|
+
return false;
|
|
3428
|
+
}
|
|
3429
|
+
function detachProvider(reason) {
|
|
3430
|
+
const previousProvider = provider;
|
|
3431
|
+
const previousStats = activeStats;
|
|
3432
|
+
provider = null;
|
|
3433
|
+
activeStats = null;
|
|
3434
|
+
if (previousStats) {
|
|
3435
|
+
logger.info({ ...statsLogFields(previousStats), reason }, "Voice ASR attempt detached");
|
|
3436
|
+
}
|
|
3437
|
+
previousProvider?.close();
|
|
3438
|
+
}
|
|
3252
3439
|
function start(payload) {
|
|
3253
3440
|
const config = store.readSecret();
|
|
3254
3441
|
if (!config.apiKey) {
|
|
3255
3442
|
sendJson(ws, {
|
|
3256
3443
|
type: "error",
|
|
3444
|
+
attemptId: payload.attemptId,
|
|
3257
3445
|
errorCode: "not_configured",
|
|
3258
3446
|
error: "Voice provider is not configured"
|
|
3259
3447
|
});
|
|
3260
3448
|
return;
|
|
3261
3449
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3450
|
+
detachProvider("replaced");
|
|
3451
|
+
const stats = buildStats(payload, config);
|
|
3452
|
+
let currentProvider;
|
|
3453
|
+
try {
|
|
3454
|
+
currentProvider = providers.current(config).createAsrClient(config, {
|
|
3455
|
+
sampleRate: stats.sampleRate,
|
|
3456
|
+
language: "zh"
|
|
3457
|
+
});
|
|
3458
|
+
} catch (error) {
|
|
3459
|
+
logger.warn({ ...statsLogFields(stats), err: error }, "Voice ASR provider creation failed");
|
|
3460
|
+
sendJson(ws, {
|
|
3461
|
+
type: "error",
|
|
3462
|
+
attemptId: payload.attemptId,
|
|
3463
|
+
error: error instanceof Error ? error.message : "ASR failed"
|
|
3464
|
+
});
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
provider = currentProvider;
|
|
3468
|
+
activeStats = stats;
|
|
3469
|
+
logger.info(statsLogFields(stats), "Voice ASR attempt started");
|
|
3470
|
+
currentProvider.on("ready", () => {
|
|
3471
|
+
if (!isCurrent(currentProvider, stats, "ready")) return;
|
|
3472
|
+
stats.readyAt = Date.now();
|
|
3473
|
+
logger.info(statsLogFields(stats), "Voice ASR provider ready");
|
|
3474
|
+
sendJson(ws, { type: "ready", attemptId: stats.attemptId });
|
|
3475
|
+
});
|
|
3476
|
+
currentProvider.on("partial", (text) => {
|
|
3477
|
+
if (!isCurrent(currentProvider, stats, "partial")) return;
|
|
3478
|
+
stats.partialEvents += 1;
|
|
3479
|
+
sendJson(ws, { type: "partial", attemptId: stats.attemptId, text });
|
|
3480
|
+
});
|
|
3481
|
+
currentProvider.on("final", (text) => {
|
|
3482
|
+
if (!isCurrent(currentProvider, stats, "final")) return;
|
|
3483
|
+
stats.finalEvents += 1;
|
|
3484
|
+
stats.finalChars += text.length;
|
|
3485
|
+
sendJson(ws, { type: "final", attemptId: stats.attemptId, text });
|
|
3486
|
+
});
|
|
3487
|
+
currentProvider.on("error", (error) => {
|
|
3488
|
+
if (!isCurrent(currentProvider, stats, "error")) return;
|
|
3489
|
+
logger.warn({ ...statsLogFields(stats), err: error }, "Voice ASR provider reported an error");
|
|
3490
|
+
sendJson(ws, {
|
|
3491
|
+
type: "error",
|
|
3492
|
+
attemptId: stats.attemptId,
|
|
3493
|
+
error: error.message || "ASR failed"
|
|
3494
|
+
});
|
|
3495
|
+
});
|
|
3496
|
+
currentProvider.on("closed", (code, reason) => {
|
|
3497
|
+
if (!isCurrent(currentProvider, stats, "closed")) return;
|
|
3498
|
+
provider = null;
|
|
3499
|
+
activeStats = null;
|
|
3500
|
+
logger.info({ ...statsLogFields(stats), code, reason }, "Voice ASR provider closed");
|
|
3501
|
+
sendJson(ws, { type: "closed", attemptId: stats.attemptId, code, reason });
|
|
3266
3502
|
});
|
|
3267
|
-
provider.on("ready", () => sendJson(ws, { type: "ready" }));
|
|
3268
|
-
provider.on("partial", (text) => sendJson(ws, { type: "partial", text }));
|
|
3269
|
-
provider.on("final", (text) => sendJson(ws, { type: "final", text }));
|
|
3270
|
-
provider.on(
|
|
3271
|
-
"error",
|
|
3272
|
-
(error) => sendJson(ws, { type: "error", error: error.message || "ASR failed" })
|
|
3273
|
-
);
|
|
3274
|
-
provider.on("closed", (code, reason) => sendJson(ws, { type: "closed", code, reason }));
|
|
3275
3503
|
}
|
|
3276
3504
|
ws.on("message", (data, isBinary) => {
|
|
3277
3505
|
if (isBinary) {
|
|
3278
|
-
|
|
3506
|
+
const encodedChunk = toBuffer(data);
|
|
3507
|
+
if (provider && activeStats) {
|
|
3508
|
+
const pcmChunk = activeStats.encoding === "mulaw" ? Buffer.from(decodeMuLawToPcm16(encodedChunk)) : encodedChunk;
|
|
3509
|
+
if (activeStats.firstPcmAt === null) activeStats.firstPcmAt = Date.now();
|
|
3510
|
+
activeStats.encodedBytes += encodedChunk.byteLength;
|
|
3511
|
+
activeStats.pcmBytes += pcmChunk.byteLength;
|
|
3512
|
+
activeStats.pcmChunks += 1;
|
|
3513
|
+
provider.sendPcm(pcmChunk);
|
|
3514
|
+
sendJson(ws, {
|
|
3515
|
+
type: "audio_ack",
|
|
3516
|
+
attemptId: activeStats.attemptId,
|
|
3517
|
+
encodedBytes: activeStats.encodedBytes,
|
|
3518
|
+
pcmBytes: activeStats.pcmBytes,
|
|
3519
|
+
chunks: activeStats.pcmChunks
|
|
3520
|
+
});
|
|
3521
|
+
}
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
const parsed = VoiceAsrClientMessageSchema.safeParse(parseJson(data));
|
|
3525
|
+
if (!parsed.success) {
|
|
3526
|
+
logger.debug({ issues: parsed.error.issues }, "Ignored invalid Voice ASR client message");
|
|
3279
3527
|
return;
|
|
3280
3528
|
}
|
|
3281
|
-
const payload =
|
|
3282
|
-
if (
|
|
3529
|
+
const payload = parsed.data;
|
|
3530
|
+
if (payload.type === "start") {
|
|
3283
3531
|
start(payload);
|
|
3284
3532
|
return;
|
|
3285
3533
|
}
|
|
3286
|
-
if (payload
|
|
3287
|
-
provider
|
|
3534
|
+
if (payload.type === "stop") {
|
|
3535
|
+
if (!provider || !activeStats || activeStats.attemptId !== payload.attemptId) {
|
|
3536
|
+
logger.debug(
|
|
3537
|
+
{ attemptId: payload.attemptId, activeAttemptId: activeStats?.attemptId ?? null },
|
|
3538
|
+
"Ignored stale Voice ASR stop"
|
|
3539
|
+
);
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
logger.info(statsLogFields(activeStats), "Voice ASR stop requested");
|
|
3543
|
+
provider.stop();
|
|
3288
3544
|
}
|
|
3289
3545
|
});
|
|
3290
|
-
ws.on("close", () => {
|
|
3291
|
-
|
|
3292
|
-
|
|
3546
|
+
ws.on("close", (code, reason) => {
|
|
3547
|
+
if (activeStats) {
|
|
3548
|
+
logger.info(
|
|
3549
|
+
{ ...statsLogFields(activeStats), code, reason: reason.toString("utf8") },
|
|
3550
|
+
"Voice ASR client websocket closed"
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
detachProvider("client-websocket-closed");
|
|
3293
3554
|
});
|
|
3294
3555
|
ws.on("error", (err) => {
|
|
3295
|
-
logger.warn(
|
|
3296
|
-
|
|
3297
|
-
|
|
3556
|
+
logger.warn(
|
|
3557
|
+
{ ...activeStats ? statsLogFields(activeStats) : {}, err },
|
|
3558
|
+
"Voice ASR websocket error"
|
|
3559
|
+
);
|
|
3560
|
+
detachProvider("client-websocket-error");
|
|
3298
3561
|
});
|
|
3299
3562
|
}
|
|
3300
3563
|
|
|
@@ -3321,7 +3584,7 @@ function isSpeakMessage(payload) {
|
|
|
3321
3584
|
function nowMs() {
|
|
3322
3585
|
return Date.now();
|
|
3323
3586
|
}
|
|
3324
|
-
function
|
|
3587
|
+
function buildStats2(requestId, text, config) {
|
|
3325
3588
|
return {
|
|
3326
3589
|
requestId,
|
|
3327
3590
|
textChars: text.length,
|
|
@@ -3335,7 +3598,7 @@ function buildStats(requestId, text, config) {
|
|
|
3335
3598
|
audioChunks: 0
|
|
3336
3599
|
};
|
|
3337
3600
|
}
|
|
3338
|
-
function
|
|
3601
|
+
function statsLogFields2(stats) {
|
|
3339
3602
|
const currentTime = nowMs();
|
|
3340
3603
|
return {
|
|
3341
3604
|
requestId: stats.requestId,
|
|
@@ -3375,7 +3638,7 @@ function handleVoiceTtsConnection(ws, store, logger, providers) {
|
|
|
3375
3638
|
});
|
|
3376
3639
|
providerConfig = config;
|
|
3377
3640
|
provider.on("started", () => {
|
|
3378
|
-
if (activeStats) logger.info(
|
|
3641
|
+
if (activeStats) logger.info(statsLogFields2(activeStats), "Voice TTS started");
|
|
3379
3642
|
sendJson2(ws, { type: "started", requestId: activeRequestId });
|
|
3380
3643
|
});
|
|
3381
3644
|
provider.on("audio", (chunk) => {
|
|
@@ -3387,7 +3650,7 @@ function handleVoiceTtsConnection(ws, store, logger, providers) {
|
|
|
3387
3650
|
if (ws.readyState === WebSocket10.OPEN) ws.send(chunk);
|
|
3388
3651
|
});
|
|
3389
3652
|
provider.on("finished", () => {
|
|
3390
|
-
if (activeStats) logger.info(
|
|
3653
|
+
if (activeStats) logger.info(statsLogFields2(activeStats), "Voice TTS finished");
|
|
3391
3654
|
sendJson2(ws, { type: "finished", requestId: activeRequestId });
|
|
3392
3655
|
activeRequestId = null;
|
|
3393
3656
|
activeStats = null;
|
|
@@ -3395,7 +3658,7 @@ function handleVoiceTtsConnection(ws, store, logger, providers) {
|
|
|
3395
3658
|
provider.on("error", (error) => {
|
|
3396
3659
|
if (activeStats) {
|
|
3397
3660
|
logger.warn(
|
|
3398
|
-
{ ...
|
|
3661
|
+
{ ...statsLogFields2(activeStats), err: error },
|
|
3399
3662
|
"Voice TTS provider reported an error"
|
|
3400
3663
|
);
|
|
3401
3664
|
}
|
|
@@ -3410,7 +3673,7 @@ function handleVoiceTtsConnection(ws, store, logger, providers) {
|
|
|
3410
3673
|
provider.on("closed", (code, reason) => {
|
|
3411
3674
|
if (activeStats) {
|
|
3412
3675
|
logger.warn(
|
|
3413
|
-
{ ...
|
|
3676
|
+
{ ...statsLogFields2(activeStats), code, reason },
|
|
3414
3677
|
"Voice TTS provider closed before finishing"
|
|
3415
3678
|
);
|
|
3416
3679
|
sendJson2(ws, {
|
|
@@ -3449,14 +3712,14 @@ function handleVoiceTtsConnection(ws, store, logger, providers) {
|
|
|
3449
3712
|
if (!ensured) return;
|
|
3450
3713
|
const { client, config } = ensured;
|
|
3451
3714
|
activeRequestId = payload.requestId;
|
|
3452
|
-
activeStats =
|
|
3453
|
-
logger.info(
|
|
3715
|
+
activeStats = buildStats2(payload.requestId, payload.text, config);
|
|
3716
|
+
logger.info(statsLogFields2(activeStats), "Voice TTS request received");
|
|
3454
3717
|
try {
|
|
3455
3718
|
client.speak(payload.text);
|
|
3456
3719
|
} catch (err) {
|
|
3457
3720
|
if (activeStats) {
|
|
3458
3721
|
logger.warn(
|
|
3459
|
-
{ ...
|
|
3722
|
+
{ ...statsLogFields2(activeStats), err },
|
|
3460
3723
|
"Voice TTS request failed before provider accepted it"
|
|
3461
3724
|
);
|
|
3462
3725
|
}
|
|
@@ -3472,7 +3735,7 @@ function handleVoiceTtsConnection(ws, store, logger, providers) {
|
|
|
3472
3735
|
ws.on("close", (code, reason) => {
|
|
3473
3736
|
if (activeStats) {
|
|
3474
3737
|
logger.warn(
|
|
3475
|
-
{ ...
|
|
3738
|
+
{ ...statsLogFields2(activeStats), code, reason: closeReasonText(reason) },
|
|
3476
3739
|
"Voice TTS client websocket closed before finishing"
|
|
3477
3740
|
);
|
|
3478
3741
|
}
|
|
@@ -4042,9 +4305,80 @@ var RemoteFileBridge = class {
|
|
|
4042
4305
|
}
|
|
4043
4306
|
};
|
|
4044
4307
|
|
|
4308
|
+
// src/web-app.ts
|
|
4309
|
+
import compression from "compression";
|
|
4310
|
+
import express from "express";
|
|
4311
|
+
import { existsSync as existsSync2 } from "fs";
|
|
4312
|
+
import { basename, join as join3 } from "path";
|
|
4313
|
+
var IMMUTABLE_CACHE_SECONDS = 365 * 24 * 60 * 60;
|
|
4314
|
+
var SHORT_CACHE_SECONDS = 60 * 60;
|
|
4315
|
+
var RELAY_ROUTE_ROOTS = /* @__PURE__ */ new Set([
|
|
4316
|
+
"/api",
|
|
4317
|
+
"/client",
|
|
4318
|
+
"/fonts",
|
|
4319
|
+
"/health",
|
|
4320
|
+
"/proxy",
|
|
4321
|
+
"/status",
|
|
4322
|
+
"/voice"
|
|
4323
|
+
]);
|
|
4324
|
+
function isRelayRoute(pathname) {
|
|
4325
|
+
for (const root of RELAY_ROUTE_ROOTS) {
|
|
4326
|
+
if (pathname === root || pathname.startsWith(`${root}/`)) return true;
|
|
4327
|
+
}
|
|
4328
|
+
return false;
|
|
4329
|
+
}
|
|
4330
|
+
function setStaticCacheHeaders(response, filePath) {
|
|
4331
|
+
const fileName = basename(filePath);
|
|
4332
|
+
const normalizedPath = filePath.replaceAll("\\", "/");
|
|
4333
|
+
if (normalizedPath.includes("/assets/") || /^workbox-.+\.js$/.test(fileName)) {
|
|
4334
|
+
response.setHeader("Cache-Control", `public, max-age=${IMMUTABLE_CACHE_SECONDS}, immutable`);
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4337
|
+
if (fileName === "index.html" || fileName === "sw.js" || fileName === "registerSW.js" || fileName === "manifest.webmanifest") {
|
|
4338
|
+
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
4339
|
+
return;
|
|
4340
|
+
}
|
|
4341
|
+
response.setHeader("Cache-Control", `public, max-age=${SHORT_CACHE_SECONDS}`);
|
|
4342
|
+
}
|
|
4343
|
+
function mountWebApp(app, options) {
|
|
4344
|
+
const { webAssetDir, logger } = options;
|
|
4345
|
+
const indexPath = join3(webAssetDir, "index.html");
|
|
4346
|
+
if (!existsSync2(indexPath)) {
|
|
4347
|
+
logger.warn({ webAssetDir }, "Web assets not found; Relay will run without the Web UI");
|
|
4348
|
+
return false;
|
|
4349
|
+
}
|
|
4350
|
+
app.use(compression());
|
|
4351
|
+
app.use(
|
|
4352
|
+
express.static(webAssetDir, {
|
|
4353
|
+
index: false,
|
|
4354
|
+
fallthrough: true,
|
|
4355
|
+
setHeaders: setStaticCacheHeaders
|
|
4356
|
+
})
|
|
4357
|
+
);
|
|
4358
|
+
app.use((request, response, next) => {
|
|
4359
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
4360
|
+
next();
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4363
|
+
if (isRelayRoute(request.path)) {
|
|
4364
|
+
response.status(404).json({ error: "not_found" });
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
if (!request.accepts("html")) {
|
|
4368
|
+
next();
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
4372
|
+
response.sendFile(indexPath);
|
|
4373
|
+
});
|
|
4374
|
+
logger.info({ webAssetDir }, "Web UI mounted");
|
|
4375
|
+
return true;
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4045
4378
|
// src/server.ts
|
|
4046
4379
|
var MODULE_DIR = dirname3(fileURLToPath2(import.meta.url));
|
|
4047
4380
|
var PACKAGED_FONTS_DIR = resolve(MODULE_DIR, "../assets/fonts");
|
|
4381
|
+
var PACKAGED_WEB_DIR = resolve(MODULE_DIR, "../assets/web");
|
|
4048
4382
|
function firstHeaderValue(value) {
|
|
4049
4383
|
if (Array.isArray(value)) return value[0];
|
|
4050
4384
|
return value;
|
|
@@ -4099,7 +4433,8 @@ function createRelayServer(options) {
|
|
|
4099
4433
|
"Relay chaos mode enabled"
|
|
4100
4434
|
);
|
|
4101
4435
|
}
|
|
4102
|
-
const app =
|
|
4436
|
+
const app = express2();
|
|
4437
|
+
app.disable("x-powered-by");
|
|
4103
4438
|
const fontsDir = dataDir ? `${dataDir}/fonts` : `${homedir()}/.dev-anywhere/relay-data/fonts`;
|
|
4104
4439
|
const fontAssetDir = options.fontAssetDir ?? PACKAGED_FONTS_DIR;
|
|
4105
4440
|
app.use(
|
|
@@ -4108,15 +4443,15 @@ function createRelayServer(options) {
|
|
|
4108
4443
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4109
4444
|
next();
|
|
4110
4445
|
},
|
|
4111
|
-
|
|
4446
|
+
express2.static(fontsDir, {
|
|
4112
4447
|
maxAge: "30d",
|
|
4113
4448
|
immutable: true
|
|
4114
4449
|
})
|
|
4115
4450
|
);
|
|
4116
|
-
if (
|
|
4451
|
+
if (existsSync3(fontAssetDir)) {
|
|
4117
4452
|
app.use(
|
|
4118
4453
|
"/fonts",
|
|
4119
|
-
|
|
4454
|
+
express2.static(fontAssetDir, {
|
|
4120
4455
|
maxAge: "30d",
|
|
4121
4456
|
immutable: true
|
|
4122
4457
|
})
|
|
@@ -4137,6 +4472,10 @@ function createRelayServer(options) {
|
|
|
4137
4472
|
app.put("/api/remote-uploads/:token", (req, res) => {
|
|
4138
4473
|
remoteFileBridge.handleUploadHttpRequest(req, res);
|
|
4139
4474
|
});
|
|
4475
|
+
const webAssetDir = options.webAssetDir === false ? void 0 : options.webAssetDir ?? PACKAGED_WEB_DIR;
|
|
4476
|
+
if (webAssetDir) {
|
|
4477
|
+
mountWebApp(app, { webAssetDir, logger });
|
|
4478
|
+
}
|
|
4140
4479
|
const httpServer = createServer(app);
|
|
4141
4480
|
const proxyWss = new WebSocketServer({ noServer: true });
|
|
4142
4481
|
const clientWss = new WebSocketServer({ noServer: true });
|
|
@@ -4306,4 +4645,4 @@ export {
|
|
|
4306
4645
|
parseRelayChaosFromEnv,
|
|
4307
4646
|
createRelayServer
|
|
4308
4647
|
};
|
|
4309
|
-
//# sourceMappingURL=chunk-
|
|
4648
|
+
//# sourceMappingURL=chunk-2L3L5F4J.js.map
|