mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
fix(check): finish extension type cleanup
This commit is contained in:
@@ -149,6 +149,7 @@ export async function handleStructuredPayload(
|
||||
}
|
||||
|
||||
const parsedPayload = payloadResult.payload;
|
||||
const unknownPayload = payloadResult.payload as unknown;
|
||||
log?.info(
|
||||
`[qqbot:${account.accountId}] Detected structured payload, type: ${parsedPayload.type}`,
|
||||
);
|
||||
@@ -195,7 +196,14 @@ export async function handleStructuredPayload(
|
||||
return true;
|
||||
}
|
||||
|
||||
log?.error(`[qqbot:${account.accountId}] Unknown payload type in structured payload`);
|
||||
const payloadType =
|
||||
typeof unknownPayload === "object" &&
|
||||
unknownPayload !== null &&
|
||||
"type" in unknownPayload &&
|
||||
typeof unknownPayload.type === "string"
|
||||
? unknownPayload.type
|
||||
: "unknown";
|
||||
log?.error(`[qqbot:${account.accountId}] Unknown payload type: ${payloadType}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,64 +14,48 @@ export interface STTConfig {
|
||||
model: string;
|
||||
}
|
||||
|
||||
type QQBotSttProviderConfig = {
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return typeof value === "object" && value !== null
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
type QQBotSttChannelConfig = QQBotSttProviderConfig & {
|
||||
enabled?: boolean;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
type QQBotSttToolAudioModel = QQBotSttProviderConfig & {
|
||||
provider?: string;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
type QQBotSttConfigRoot = {
|
||||
channels?: {
|
||||
qqbot?: {
|
||||
stt?: QQBotSttChannelConfig;
|
||||
};
|
||||
};
|
||||
models?: {
|
||||
providers?: Record<string, QQBotSttProviderConfig>;
|
||||
};
|
||||
tools?: {
|
||||
media?: {
|
||||
audio?: {
|
||||
models?: QQBotSttToolAudioModel[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
function readString(record: Record<string, unknown> | undefined, key: string): string | undefined {
|
||||
const value = record?.[key];
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
export function resolveSTTConfig(cfg: Record<string, unknown>): STTConfig | null {
|
||||
const c = cfg as QQBotSttConfigRoot;
|
||||
const channels = asRecord(cfg.channels);
|
||||
const qqbot = asRecord(channels?.qqbot);
|
||||
const channelStt = asRecord(qqbot?.stt);
|
||||
const models = asRecord(cfg.models);
|
||||
const providers = asRecord(models?.providers);
|
||||
|
||||
// Prefer plugin-specific STT config.
|
||||
const channelStt = c?.channels?.qqbot?.stt;
|
||||
if (channelStt && channelStt.enabled !== false) {
|
||||
const providerId: string = channelStt?.provider || "openai";
|
||||
const providerCfg = c?.models?.providers?.[providerId];
|
||||
const baseUrl: string | undefined = channelStt?.baseUrl || providerCfg?.baseUrl;
|
||||
const apiKey: string | undefined = channelStt?.apiKey || providerCfg?.apiKey;
|
||||
const model: string = channelStt?.model || "whisper-1";
|
||||
const providerId = readString(channelStt, "provider") ?? "openai";
|
||||
const providerCfg = asRecord(providers?.[providerId]);
|
||||
const baseUrl = readString(channelStt, "baseUrl") ?? readString(providerCfg, "baseUrl");
|
||||
const apiKey = readString(channelStt, "apiKey") ?? readString(providerCfg, "apiKey");
|
||||
const model = readString(channelStt, "model") ?? "whisper-1";
|
||||
if (baseUrl && apiKey) {
|
||||
return { baseUrl: baseUrl.replace(/\/+$/, ""), apiKey, model };
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to framework-level audio model config.
|
||||
const audioModelEntry = c?.tools?.media?.audio?.models?.[0];
|
||||
const tools = asRecord(cfg.tools);
|
||||
const media = asRecord(tools?.media);
|
||||
const audio = asRecord(media?.audio);
|
||||
const audioModels = audio?.models;
|
||||
const audioModelEntry = Array.isArray(audioModels) ? asRecord(audioModels[0]) : undefined;
|
||||
if (audioModelEntry) {
|
||||
const providerId: string = audioModelEntry?.provider || "openai";
|
||||
const providerCfg = c?.models?.providers?.[providerId];
|
||||
const baseUrl: string | undefined = audioModelEntry?.baseUrl || providerCfg?.baseUrl;
|
||||
const apiKey: string | undefined = audioModelEntry?.apiKey || providerCfg?.apiKey;
|
||||
const model: string = audioModelEntry?.model || "whisper-1";
|
||||
const providerId = readString(audioModelEntry, "provider") ?? "openai";
|
||||
const providerCfg = asRecord(providers?.[providerId]);
|
||||
const baseUrl = readString(audioModelEntry, "baseUrl") ?? readString(providerCfg, "baseUrl");
|
||||
const apiKey = readString(audioModelEntry, "apiKey") ?? readString(providerCfg, "apiKey");
|
||||
const model = readString(audioModelEntry, "model") ?? "whisper-1";
|
||||
if (baseUrl && apiKey) {
|
||||
return { baseUrl: baseUrl.replace(/\/+$/, ""), apiKey, model };
|
||||
}
|
||||
|
||||
@@ -44,8 +44,19 @@ let _log:
|
||||
| { info: (msg: string) => void; error: (msg: string) => void; debug?: (msg: string) => void }
|
||||
| undefined;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return typeof value === "object" && value !== null
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readString(record: Record<string, unknown> | undefined, key: string): string | undefined {
|
||||
const value = record?.[key];
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
function fetchJson(url: string, timeoutMs: number): Promise<unknown> {
|
||||
@@ -84,18 +95,16 @@ async function fetchDistTags(): Promise<Record<string, string>> {
|
||||
for (const url of REGISTRIES) {
|
||||
try {
|
||||
const json = await fetchJson(url, 10_000);
|
||||
const tags = isRecord(json) ? json["dist-tags"] : undefined;
|
||||
if (isRecord(tags)) {
|
||||
const tags = asRecord(asRecord(json)?.["dist-tags"]);
|
||||
if (tags) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(tags).filter((entry): entry is [string, string] => {
|
||||
return typeof entry[1] === "string";
|
||||
}),
|
||||
Object.entries(tags).flatMap(([key, value]) =>
|
||||
typeof value === "string" ? [[key, value]] : [],
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
_log?.debug?.(
|
||||
`[qqbot:update-checker] ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
_log?.debug?.(`[qqbot:update-checker] ${url} failed: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
throw new Error("all registries failed");
|
||||
@@ -151,8 +160,8 @@ export async function getUpdateInfo(): Promise<UpdateInfo> {
|
||||
const tags = await fetchDistTags();
|
||||
return buildUpdateInfo(tags);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
_log?.debug?.(`[qqbot:update-checker] check failed: ${message}`);
|
||||
const errorMessage = getErrorMessage(err);
|
||||
_log?.debug?.(`[qqbot:update-checker] check failed: ${errorMessage}`);
|
||||
return {
|
||||
current: CURRENT_VERSION,
|
||||
latest: null,
|
||||
@@ -160,7 +169,7 @@ export async function getUpdateInfo(): Promise<UpdateInfo> {
|
||||
alpha: null,
|
||||
hasUpdate: false,
|
||||
checkedAt: Date.now(),
|
||||
error: message,
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -173,7 +182,7 @@ export async function checkVersionExists(version: string): Promise<boolean> {
|
||||
try {
|
||||
const url = `${baseUrl}/${version}`;
|
||||
const json = await fetchJson(url, 10_000);
|
||||
if (isRecord(json) && json.version === version) {
|
||||
if (readString(asRecord(json), "version") === version) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -227,27 +227,55 @@ type QQBotTtsConfigRoot = {
|
||||
};
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return typeof value === "object" && value !== null
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function readString(record: Record<string, unknown> | undefined, key: string): string | undefined {
|
||||
const value = record?.[key];
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function readNumber(record: Record<string, unknown> | undefined, key: string): number | undefined {
|
||||
const value = record?.[key];
|
||||
return typeof value === "number" ? value : undefined;
|
||||
}
|
||||
|
||||
function readStringMap(value: unknown): Record<string, string> {
|
||||
const record = asRecord(value);
|
||||
if (!record) {
|
||||
return {};
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(record).flatMap(([key, entryValue]) =>
|
||||
typeof entryValue === "string" ? [[key, entryValue]] : [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveTTSFromBlock(
|
||||
block: QQBotTtsBlock,
|
||||
providerCfg: QQBotTtsProviderConfig | undefined,
|
||||
): TTSConfig | null {
|
||||
const baseUrl: string | undefined = block?.baseUrl || providerCfg?.baseUrl;
|
||||
const apiKey: string | undefined = block?.apiKey || providerCfg?.apiKey;
|
||||
const model: string = block?.model || "tts-1";
|
||||
const voice: string = block?.voice || "alloy";
|
||||
const baseUrl = readString(block, "baseUrl") ?? readString(providerCfg, "baseUrl");
|
||||
const apiKey = readString(block, "apiKey") ?? readString(providerCfg, "apiKey");
|
||||
const model = readString(block, "model") ?? "tts-1";
|
||||
const voice = readString(block, "voice") ?? "alloy";
|
||||
if (!baseUrl || !apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const authStyle =
|
||||
(block?.authStyle || providerCfg?.authStyle) === "api-key"
|
||||
(readString(block, "authStyle") ?? readString(providerCfg, "authStyle")) === "api-key"
|
||||
? ("api-key" as const)
|
||||
: ("bearer" as const);
|
||||
const queryParams: Record<string, string> = {
|
||||
...providerCfg?.queryParams,
|
||||
...block?.queryParams,
|
||||
...readStringMap(providerCfg?.queryParams),
|
||||
...readStringMap(block.queryParams),
|
||||
};
|
||||
const speed: number | undefined = block?.speed;
|
||||
const speed = readNumber(block, "speed");
|
||||
|
||||
return {
|
||||
baseUrl: baseUrl.replace(/\/+$/, ""),
|
||||
@@ -261,13 +289,16 @@ function resolveTTSFromBlock(
|
||||
}
|
||||
|
||||
export function resolveTTSConfig(cfg: Record<string, unknown>): TTSConfig | null {
|
||||
const c = cfg as QQBotTtsConfigRoot;
|
||||
const models = asRecord(cfg.models);
|
||||
const providers = asRecord(models?.providers);
|
||||
|
||||
// Prefer plugin-specific TTS config first.
|
||||
const channelTts = c?.channels?.qqbot?.tts;
|
||||
const channels = asRecord(cfg.channels);
|
||||
const qqbot = asRecord(channels?.qqbot);
|
||||
const channelTts = asRecord(qqbot?.tts);
|
||||
if (channelTts && channelTts.enabled !== false) {
|
||||
const providerId: string = channelTts?.provider || "openai";
|
||||
const providerCfg = c?.models?.providers?.[providerId];
|
||||
const providerId = readString(channelTts, "provider") ?? "openai";
|
||||
const providerCfg = asRecord(providers?.[providerId]);
|
||||
const result = resolveTTSFromBlock(channelTts, providerCfg);
|
||||
if (result) {
|
||||
return result;
|
||||
@@ -275,12 +306,14 @@ export function resolveTTSConfig(cfg: Record<string, unknown>): TTSConfig | null
|
||||
}
|
||||
|
||||
// Fall back to framework-level TTS config.
|
||||
const msgTts = c?.messages?.tts;
|
||||
if (msgTts && msgTts.auto !== "off" && msgTts.auto !== "disabled") {
|
||||
const providerId: string = msgTts?.provider || "openai";
|
||||
const providerBlock = msgTts?.[providerId] as QQBotTtsBlock | undefined;
|
||||
const providerCfg = c?.models?.providers?.[providerId];
|
||||
const result = resolveTTSFromBlock(providerBlock ?? {}, providerCfg);
|
||||
const messages = asRecord(cfg.messages);
|
||||
const msgTts = asRecord(messages?.tts);
|
||||
const autoMode = readString(msgTts, "auto");
|
||||
if (msgTts && autoMode !== "off" && autoMode !== "disabled") {
|
||||
const providerId = readString(msgTts, "provider") ?? "openai";
|
||||
const providerBlock = asRecord(msgTts[providerId]) ?? {};
|
||||
const providerCfg = asRecord(providers?.[providerId]);
|
||||
const result = resolveTTSFromBlock(providerBlock, providerCfg);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -752,10 +752,13 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
return;
|
||||
}
|
||||
|
||||
const contentBody = content.content;
|
||||
const sentAt = readNumber(content, "sent") ?? Date.now();
|
||||
|
||||
cacheMessage(nest, {
|
||||
author: senderShip,
|
||||
content: rawText,
|
||||
timestamp: readNumber(content, "sent") ?? Date.now(),
|
||||
timestamp: sentAt,
|
||||
id: messageId,
|
||||
});
|
||||
|
||||
@@ -797,8 +800,8 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
originalMessage: {
|
||||
messageId: messageId ?? "",
|
||||
messageText: rawText,
|
||||
messageContent: content.content,
|
||||
timestamp: readNumber(content, "sent") ?? Date.now(),
|
||||
messageContent: contentBody,
|
||||
timestamp: sentAt,
|
||||
parentId: parentId ?? undefined,
|
||||
isThreadReply,
|
||||
},
|
||||
@@ -816,7 +819,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
|
||||
const messageText = await resolveAuthorizedMessageText({
|
||||
rawText,
|
||||
content: content.content,
|
||||
content: contentBody,
|
||||
authorizedForCites: true,
|
||||
resolveAllCites,
|
||||
});
|
||||
@@ -826,12 +829,12 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
messageId: messageId ?? "",
|
||||
senderShip,
|
||||
messageText,
|
||||
messageContent: content.content, // Pass raw content for media extraction
|
||||
messageContent: contentBody, // Pass raw content for media extraction
|
||||
isGroup: true,
|
||||
channelNest: nest,
|
||||
hostShip: parsed?.hostShip,
|
||||
channelName: parsed?.channelName,
|
||||
timestamp: readNumber(content, "sent") ?? Date.now(),
|
||||
timestamp: sentAt,
|
||||
parentId,
|
||||
isThreadReply,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user