@openclaw-china/shared 0.1.31 → 0.1.33
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/package.json +4 -1
- package/src/asr/README.md +100 -100
- package/src/asr/errors.ts +61 -61
- package/src/asr/index.ts +11 -11
- package/src/asr/tencent-flash.ts +165 -165
- package/src/cli/china-setup.ts +783 -0
- package/src/cli/index.ts +1 -0
- package/src/cron/index.ts +115 -115
- package/src/file/file-utils.test.ts +141 -141
- package/src/file/file-utils.ts +284 -284
- package/src/file/index.ts +10 -10
- package/src/http/client.ts +141 -141
- package/src/http/index.ts +2 -2
- package/src/http/retry.ts +110 -110
- package/src/index.ts +10 -9
- package/src/logger/index.ts +1 -1
- package/src/logger/logger.ts +51 -51
- package/src/media/index.ts +65 -65
- package/src/media/media-io.ts +732 -732
- package/src/media/media-parser.ts +722 -722
- package/src/policy/dm-policy.ts +82 -82
- package/src/policy/group-policy.ts +93 -93
- package/src/policy/index.ts +2 -2
- package/src/types/common.ts +24 -24
- package/tsconfig.json +8 -8
- package/vitest.config.ts +8 -8
package/src/asr/tencent-flash.ts
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
import { createHmac } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
ASRAuthError,
|
|
4
|
-
ASREmptyResultError,
|
|
5
|
-
ASRRequestError,
|
|
6
|
-
ASRResponseParseError,
|
|
7
|
-
ASRServiceError,
|
|
8
|
-
ASRTimeoutError,
|
|
9
|
-
} from "./errors.js";
|
|
10
|
-
|
|
11
|
-
const ASR_FLASH_HOST = "asr.cloud.tencent.com";
|
|
12
|
-
const ASR_FLASH_PATH_PREFIX = "/asr/flash/v1";
|
|
13
|
-
const ASR_FLASH_URL_PREFIX = `https://${ASR_FLASH_HOST}${ASR_FLASH_PATH_PREFIX}`;
|
|
14
|
-
const ASR_PROVIDER = "tencent-flash";
|
|
15
|
-
|
|
16
|
-
export interface TencentFlashASRConfig {
|
|
17
|
-
appId: string;
|
|
18
|
-
secretId: string;
|
|
19
|
-
secretKey: string;
|
|
20
|
-
engineType?: string;
|
|
21
|
-
voiceFormat?: string;
|
|
22
|
-
timeoutMs?: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface TencentFlashResponseSentence {
|
|
26
|
-
text?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface TencentFlashResponseItem {
|
|
30
|
-
text?: string;
|
|
31
|
-
sentence_list?: TencentFlashResponseSentence[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface TencentFlashResponse {
|
|
35
|
-
code?: number;
|
|
36
|
-
message?: string;
|
|
37
|
-
flash_result?: TencentFlashResponseItem[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function encodeQueryValue(value: string): string {
|
|
41
|
-
return encodeURIComponent(value)
|
|
42
|
-
.replace(/%20/g, "+")
|
|
43
|
-
.replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function buildSignedQuery(params: Record<string, string>): string {
|
|
47
|
-
return Object.entries(params)
|
|
48
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
49
|
-
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeQueryValue(value)}`)
|
|
50
|
-
.join("&");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function extractTranscript(payload: TencentFlashResponse): string {
|
|
54
|
-
const items = Array.isArray(payload.flash_result) ? payload.flash_result : [];
|
|
55
|
-
const lines: string[] = [];
|
|
56
|
-
|
|
57
|
-
for (const item of items) {
|
|
58
|
-
if (typeof item?.text === "string" && item.text.trim()) {
|
|
59
|
-
lines.push(item.text.trim());
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const sentenceList = Array.isArray(item?.sentence_list) ? item.sentence_list : [];
|
|
63
|
-
for (const sentence of sentenceList) {
|
|
64
|
-
if (typeof sentence?.text === "string" && sentence.text.trim()) {
|
|
65
|
-
lines.push(sentence.text.trim());
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return lines.join("\n").trim();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function transcribeTencentFlash(params: {
|
|
74
|
-
audio: Buffer;
|
|
75
|
-
config: TencentFlashASRConfig;
|
|
76
|
-
}): Promise<string> {
|
|
77
|
-
const { audio, config } = params;
|
|
78
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
79
|
-
const engineType = config.engineType ?? "16k_zh";
|
|
80
|
-
const voiceFormat = config.voiceFormat ?? "silk";
|
|
81
|
-
const query = buildSignedQuery({
|
|
82
|
-
engine_type: engineType,
|
|
83
|
-
secretid: config.secretId,
|
|
84
|
-
timestamp,
|
|
85
|
-
voice_format: voiceFormat,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const signText = `POST${ASR_FLASH_HOST}${ASR_FLASH_PATH_PREFIX}/${config.appId}?${query}`;
|
|
89
|
-
const authorization = createHmac("sha1", config.secretKey).update(signText).digest("base64");
|
|
90
|
-
const url = `${ASR_FLASH_URL_PREFIX}/${config.appId}?${query}`;
|
|
91
|
-
const timeoutMs = config.timeoutMs ?? 30000;
|
|
92
|
-
const controller = new AbortController();
|
|
93
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const response = await fetch(url, {
|
|
97
|
-
method: "POST",
|
|
98
|
-
headers: {
|
|
99
|
-
Authorization: authorization,
|
|
100
|
-
"Content-Type": "application/octet-stream",
|
|
101
|
-
},
|
|
102
|
-
body: audio,
|
|
103
|
-
signal: controller.signal,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const bodyText = await response.text();
|
|
107
|
-
let payload: TencentFlashResponse;
|
|
108
|
-
try {
|
|
109
|
-
payload = JSON.parse(bodyText) as TencentFlashResponse;
|
|
110
|
-
} catch {
|
|
111
|
-
throw new ASRResponseParseError(ASR_PROVIDER, bodyText.slice(0, 300));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!response.ok) {
|
|
115
|
-
const message = payload.message ?? `HTTP ${response.status}`;
|
|
116
|
-
if (response.status === 401 || response.status === 403) {
|
|
117
|
-
throw new ASRAuthError(
|
|
118
|
-
ASR_PROVIDER,
|
|
119
|
-
`Tencent Flash ASR authentication failed: ${message}`,
|
|
120
|
-
response.status
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
throw new ASRRequestError(
|
|
124
|
-
ASR_PROVIDER,
|
|
125
|
-
`Tencent Flash ASR request failed: ${message}`,
|
|
126
|
-
response.status
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (payload.code !== 0) {
|
|
131
|
-
throw new ASRServiceError(
|
|
132
|
-
ASR_PROVIDER,
|
|
133
|
-
`Tencent Flash ASR failed: ${payload.message ?? "unknown error"} (code=${payload.code})`
|
|
134
|
-
,
|
|
135
|
-
payload.code
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const transcript = extractTranscript(payload);
|
|
140
|
-
if (!transcript) {
|
|
141
|
-
throw new ASREmptyResultError(ASR_PROVIDER);
|
|
142
|
-
}
|
|
143
|
-
return transcript;
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
146
|
-
throw new ASRTimeoutError(ASR_PROVIDER, timeoutMs);
|
|
147
|
-
}
|
|
148
|
-
if (
|
|
149
|
-
error instanceof ASRResponseParseError ||
|
|
150
|
-
error instanceof ASRAuthError ||
|
|
151
|
-
error instanceof ASRRequestError ||
|
|
152
|
-
error instanceof ASRServiceError ||
|
|
153
|
-
error instanceof ASREmptyResultError ||
|
|
154
|
-
error instanceof ASRTimeoutError
|
|
155
|
-
) {
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
158
|
-
throw new ASRRequestError(
|
|
159
|
-
ASR_PROVIDER,
|
|
160
|
-
`Tencent Flash ASR request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
161
|
-
);
|
|
162
|
-
} finally {
|
|
163
|
-
clearTimeout(timeoutId);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
ASRAuthError,
|
|
4
|
+
ASREmptyResultError,
|
|
5
|
+
ASRRequestError,
|
|
6
|
+
ASRResponseParseError,
|
|
7
|
+
ASRServiceError,
|
|
8
|
+
ASRTimeoutError,
|
|
9
|
+
} from "./errors.js";
|
|
10
|
+
|
|
11
|
+
const ASR_FLASH_HOST = "asr.cloud.tencent.com";
|
|
12
|
+
const ASR_FLASH_PATH_PREFIX = "/asr/flash/v1";
|
|
13
|
+
const ASR_FLASH_URL_PREFIX = `https://${ASR_FLASH_HOST}${ASR_FLASH_PATH_PREFIX}`;
|
|
14
|
+
const ASR_PROVIDER = "tencent-flash";
|
|
15
|
+
|
|
16
|
+
export interface TencentFlashASRConfig {
|
|
17
|
+
appId: string;
|
|
18
|
+
secretId: string;
|
|
19
|
+
secretKey: string;
|
|
20
|
+
engineType?: string;
|
|
21
|
+
voiceFormat?: string;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TencentFlashResponseSentence {
|
|
26
|
+
text?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface TencentFlashResponseItem {
|
|
30
|
+
text?: string;
|
|
31
|
+
sentence_list?: TencentFlashResponseSentence[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface TencentFlashResponse {
|
|
35
|
+
code?: number;
|
|
36
|
+
message?: string;
|
|
37
|
+
flash_result?: TencentFlashResponseItem[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function encodeQueryValue(value: string): string {
|
|
41
|
+
return encodeURIComponent(value)
|
|
42
|
+
.replace(/%20/g, "+")
|
|
43
|
+
.replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildSignedQuery(params: Record<string, string>): string {
|
|
47
|
+
return Object.entries(params)
|
|
48
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
49
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeQueryValue(value)}`)
|
|
50
|
+
.join("&");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function extractTranscript(payload: TencentFlashResponse): string {
|
|
54
|
+
const items = Array.isArray(payload.flash_result) ? payload.flash_result : [];
|
|
55
|
+
const lines: string[] = [];
|
|
56
|
+
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
if (typeof item?.text === "string" && item.text.trim()) {
|
|
59
|
+
lines.push(item.text.trim());
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const sentenceList = Array.isArray(item?.sentence_list) ? item.sentence_list : [];
|
|
63
|
+
for (const sentence of sentenceList) {
|
|
64
|
+
if (typeof sentence?.text === "string" && sentence.text.trim()) {
|
|
65
|
+
lines.push(sentence.text.trim());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return lines.join("\n").trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function transcribeTencentFlash(params: {
|
|
74
|
+
audio: Buffer;
|
|
75
|
+
config: TencentFlashASRConfig;
|
|
76
|
+
}): Promise<string> {
|
|
77
|
+
const { audio, config } = params;
|
|
78
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
79
|
+
const engineType = config.engineType ?? "16k_zh";
|
|
80
|
+
const voiceFormat = config.voiceFormat ?? "silk";
|
|
81
|
+
const query = buildSignedQuery({
|
|
82
|
+
engine_type: engineType,
|
|
83
|
+
secretid: config.secretId,
|
|
84
|
+
timestamp,
|
|
85
|
+
voice_format: voiceFormat,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const signText = `POST${ASR_FLASH_HOST}${ASR_FLASH_PATH_PREFIX}/${config.appId}?${query}`;
|
|
89
|
+
const authorization = createHmac("sha1", config.secretKey).update(signText).digest("base64");
|
|
90
|
+
const url = `${ASR_FLASH_URL_PREFIX}/${config.appId}?${query}`;
|
|
91
|
+
const timeoutMs = config.timeoutMs ?? 30000;
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: {
|
|
99
|
+
Authorization: authorization,
|
|
100
|
+
"Content-Type": "application/octet-stream",
|
|
101
|
+
},
|
|
102
|
+
body: audio,
|
|
103
|
+
signal: controller.signal,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const bodyText = await response.text();
|
|
107
|
+
let payload: TencentFlashResponse;
|
|
108
|
+
try {
|
|
109
|
+
payload = JSON.parse(bodyText) as TencentFlashResponse;
|
|
110
|
+
} catch {
|
|
111
|
+
throw new ASRResponseParseError(ASR_PROVIDER, bodyText.slice(0, 300));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
const message = payload.message ?? `HTTP ${response.status}`;
|
|
116
|
+
if (response.status === 401 || response.status === 403) {
|
|
117
|
+
throw new ASRAuthError(
|
|
118
|
+
ASR_PROVIDER,
|
|
119
|
+
`Tencent Flash ASR authentication failed: ${message}`,
|
|
120
|
+
response.status
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
throw new ASRRequestError(
|
|
124
|
+
ASR_PROVIDER,
|
|
125
|
+
`Tencent Flash ASR request failed: ${message}`,
|
|
126
|
+
response.status
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (payload.code !== 0) {
|
|
131
|
+
throw new ASRServiceError(
|
|
132
|
+
ASR_PROVIDER,
|
|
133
|
+
`Tencent Flash ASR failed: ${payload.message ?? "unknown error"} (code=${payload.code})`
|
|
134
|
+
,
|
|
135
|
+
payload.code
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const transcript = extractTranscript(payload);
|
|
140
|
+
if (!transcript) {
|
|
141
|
+
throw new ASREmptyResultError(ASR_PROVIDER);
|
|
142
|
+
}
|
|
143
|
+
return transcript;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
146
|
+
throw new ASRTimeoutError(ASR_PROVIDER, timeoutMs);
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
error instanceof ASRResponseParseError ||
|
|
150
|
+
error instanceof ASRAuthError ||
|
|
151
|
+
error instanceof ASRRequestError ||
|
|
152
|
+
error instanceof ASRServiceError ||
|
|
153
|
+
error instanceof ASREmptyResultError ||
|
|
154
|
+
error instanceof ASRTimeoutError
|
|
155
|
+
) {
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
throw new ASRRequestError(
|
|
159
|
+
ASR_PROVIDER,
|
|
160
|
+
`Tencent Flash ASR request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
161
|
+
);
|
|
162
|
+
} finally {
|
|
163
|
+
clearTimeout(timeoutId);
|
|
164
|
+
}
|
|
165
|
+
}
|