import type { MonitoringSourceItem } from "@geo/shared-types"; import { browser } from "wxt/browser"; import type { StoredMonitoringPlatformState } from "../monitoring-platforms"; import { getExtensionState } from "../storage"; import { closeSilentAutomationPage, hasAnyPlatformCookie, isPlatformReachable, normalizeDetectResult, openSilentAutomationPage, } from "./common"; import type { MonitoringAdapter, MonitoringAdapterQueryResult } from "./types"; const DOUBAO_APP_ID = "497858"; const DOUBAO_BOT_ID = "7338286299411103781"; const DOUBAO_CHAT_URL = "https://www.doubao.com/chat/completion"; const DOUBAO_REFERER = "https://www.doubao.com/chat/"; const DOUBAO_BOOTSTRAP_URL = DOUBAO_REFERER; const DOUBAO_FP_COOKIE_NAME = "s_v_web_id"; const DOUBAO_COOKIE_URLS = ["https://www.doubao.com/", "https://doubao.com/"]; const DOUBAO_TAB_URL_PATTERNS = ["https://www.doubao.com/*", "https://doubao.com/*"]; const DOUBAO_RUNTIME_SYNC_MESSAGE = "doubao runtime passive sync pending"; const DOUBAO_RUNTIME_SYNC_HINT = "插件正在后台静默拉起豆包并自动同步"; const DOUBAO_RUNTIME_BOOTSTRAP_TIMEOUT_MS = 15_000; const DOUBAO_EXISTING_TAB_BOOTSTRAP_TIMEOUT_MS = 5_000; const DOUBAO_RUNTIME_BOOTSTRAP_POLL_INTERVAL_MS = 500; const DOUBAO_RUNTIME_BOOTSTRAP_COOLDOWN_MS = 30_000; const DOUBAO_EXISTING_TAB_PROBE_LIMIT = 2; const DOUBAO_REQUIRED_RUNTIME_KEYS: Array = [ "fp", "ms_token", "device_id", "web_id", "tea_uuid", ]; const SEARCH_RESULT_KEYS = new Set([ "references", "reference_list", "referenceList", "reference_cards", "referenceCards", "reference_docs", "referenceDocs", "sources", "source_list", "sourceList", "source_cards", "sourceCards", "search_results", "searchResults", "search_result_list", "searchResultList", "search_refs", "searchRefs", "web_results", "webResults", "grounding", "grounding_results", "groundingResults", "browse_results", "browseResults", "browse_references", "browseReferences", ]); const CONTENT_CITATION_KEYS = new Set([ "citations", "citation_list", "citationList", "content_citations", "contentCitations", "answer_citations", "answerCitations", ]); const SEARCH_RESULT_KEYWORDS = [ "reference", "source", "searchresult", "searchref", "webresult", "grounding", "browse", ]; const CONTENT_CITATION_KEYWORDS = ["citation", "contentcitation", "answercitation"]; type SourceCollectionBucket = "search" | "citation" | null; type DoubaoRequestRuntimeState = { fp: string; ms_token: string; device_id: string; web_id: string; tea_uuid: string; }; type DoubaoStreamEvent = { event: string; payload: unknown; }; type DoubaoStreamSummary = { answer: string | null; provider_request_id: string | null; request_id: string | null; conversation_id: string | null; citations: MonitoringSourceItem[]; search_results: MonitoringSourceItem[]; events: DoubaoStreamEvent[]; error_message: string | null; }; type DoubaoRuntimeSyncResponse = { ok?: boolean; error?: string; }; let doubaoRuntimeBootstrapInFlight: Promise | null = null; let lastDoubaoRuntimeBootstrapAt = 0; function normalizeText(value: unknown): string | null { if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed ? trimmed : null; } function normalizeAnswerFragment(value: unknown): string | null { if (typeof value !== "string") { return null; } const normalized = value.replace(/\r/g, ""); return normalized.trim() ? normalized : null; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function getDirectString(record: Record, keys: string[]): string | null { for (const key of keys) { const value = normalizeText(record[key]); if (value) { return value; } } return null; } function readNestedString(source: unknown, path: string[]): string | null { let current: unknown = source; for (const key of path) { if (!isRecord(current) || !(key in current)) { return null; } current = current[key]; } return normalizeText(current); } function findFirstStringByKey(source: unknown, keys: string[], depth = 0): string | null { if (depth > 8 || source == null) { return null; } if (Array.isArray(source)) { for (const item of source) { const found = findFirstStringByKey(item, keys, depth + 1); if (found) { return found; } } return null; } if (!isRecord(source)) { return null; } for (const key of keys) { const direct = normalizeText(source[key]); if (direct) { return direct; } } for (const value of Object.values(source)) { const found = findFirstStringByKey(value, keys, depth + 1); if (found) { return found; } } return null; } function mergeAnswerText(current: string | null, nextFragment: string | null): string | null { if (!nextFragment) { return current; } if (!current) { return nextFragment; } if (current === nextFragment || current.includes(nextFragment)) { return current; } if (nextFragment.startsWith(current)) { return nextFragment; } return `${current}${nextFragment}`; } function safeParseJSON(raw: string): unknown { try { return JSON.parse(raw); } catch { return raw; } } function normalizeSourceUrl(value: string | null): string | null { const input = normalizeText(value); if (!input) { return null; } try { const url = new URL(input); url.hash = ""; return url.toString(); } catch { return input; } } function normalizeSourceKey(key: string): string { return key.replace(/[^a-z0-9]/gi, "").toLowerCase(); } function resolveSourceBucket(key: string): SourceCollectionBucket { const normalized = normalizeSourceKey(key); if (!normalized) { return null; } if (CONTENT_CITATION_KEYS.has(key) || CONTENT_CITATION_KEYWORDS.some((keyword) => normalized.includes(keyword))) { return "citation"; } if (SEARCH_RESULT_KEYS.has(key) || SEARCH_RESULT_KEYWORDS.some((keyword) => normalized.includes(keyword))) { return "search"; } return null; } function buildSourceItem(input: unknown): MonitoringSourceItem | null { if (typeof input === "string") { const url = normalizeSourceUrl(input); return url ? { url, normalized_url: url } : null; } if (!isRecord(input)) { return null; } const url = normalizeSourceUrl( getDirectString(input, [ "url", "link", "uri", "href", "cited_url", "target_url", "targetUrl", "source_url", "sourceUrl", "page_url", "pageUrl", "jump_url", "jumpUrl", ]), ); if (!url) { return null; } let host: string | null = null; try { host = new URL(url).hostname || null; } catch { host = null; } return { url, title: getDirectString(input, ["title", "name", "cited_title", "page_title", "display_title", "page_name"]), site_name: getDirectString(input, ["site_name", "siteName", "source", "platform_name", "domain", "site", "host_name"]), site_key: getDirectString(input, ["site_key", "siteKey", "domain_key"]), normalized_url: url, host, resolution_status: getDirectString(input, ["resolution_status", "resolutionStatus"]), resolution_confidence: getDirectString(input, ["resolution_confidence", "resolutionConfidence"]), }; } function looksExplicitlyLikeSourceRecord(input: Record): boolean { return ( getDirectString(input, [ "title", "name", "cited_title", "page_title", "display_title", "page_name", "site_name", "siteName", "source", "platform_name", "domain", "host_name", ]) !== null ); } function isSourceLikeArray(value: unknown): value is unknown[] { if (!Array.isArray(value) || value.length === 0) { return false; } return value.some((item) => buildSourceItem(item) !== null); } function dedupeSourceItems(items: MonitoringSourceItem[]): MonitoringSourceItem[] { const keyed = new Map(); for (const item of items) { const key = normalizeSourceUrl(item.normalized_url ?? item.url); if (!key) { continue; } const existing = keyed.get(key); if (!existing) { keyed.set(key, { ...item, normalized_url: key, }); continue; } keyed.set(key, { ...existing, title: existing.title ?? item.title ?? null, site_name: existing.site_name ?? item.site_name ?? null, site_key: existing.site_key ?? item.site_key ?? null, host: existing.host ?? item.host ?? null, resolution_status: existing.resolution_status ?? item.resolution_status ?? null, resolution_confidence: existing.resolution_confidence ?? item.resolution_confidence ?? null, }); } return Array.from(keyed.values()); } function collectSources( payload: unknown, searchResults: MonitoringSourceItem[], citations: MonitoringSourceItem[], bucket: SourceCollectionBucket = null, depth = 0, ): void { if (depth > 12 || payload == null) { return; } const pushToBucket = (item: MonitoringSourceItem, targetBucket: SourceCollectionBucket) => { if (targetBucket === "citation") { citations.push(item); return; } searchResults.push(item); }; if (typeof payload === "string") { if (!bucket) { return; } const item = buildSourceItem(payload); if (item) { pushToBucket(item, bucket); } return; } if (Array.isArray(payload)) { if (bucket && isSourceLikeArray(payload)) { for (const item of payload) { const sourceItem = buildSourceItem(item); if (sourceItem) { pushToBucket(sourceItem, bucket); } } } for (const item of payload) { collectSources(item, searchResults, citations, bucket, depth + 1); } return; } if (!isRecord(payload)) { return; } const directSourceItem = buildSourceItem(payload); if (directSourceItem && (bucket || looksExplicitlyLikeSourceRecord(payload))) { pushToBucket(directSourceItem, bucket ?? "search"); } for (const [key, value] of Object.entries(payload)) { const nextBucket = resolveSourceBucket(key) ?? bucket; if (nextBucket && isSourceLikeArray(value)) { const sourceItems = value.map((item) => buildSourceItem(item)).filter((item): item is MonitoringSourceItem => item !== null); for (const item of sourceItems) { pushToBucket(item, nextBucket); } } if (nextBucket && typeof value === "string") { const item = buildSourceItem(value); if (item) { pushToBucket(item, nextBucket); } } if (Array.isArray(value) || isRecord(value) || typeof value === "string") { collectSources(value, searchResults, citations, nextBucket, depth + 1); } } } function collectAnswerFragments(payload: unknown, result: string[] = []): string[] { if (payload == null) { return result; } if (Array.isArray(payload)) { for (const item of payload) { collectAnswerFragments(item, result); } return result; } if (!isRecord(payload)) { return result; } const textBlockText = readNestedString(payload, ["text_block", "text"]); if (textBlockText) { result.push(textBlockText); } const deltaText = normalizeAnswerFragment(payload.delta); if (deltaText) { result.push(deltaText); } const nestedDeltaText = readNestedString(payload, ["delta", "text"]); if (nestedDeltaText) { result.push(nestedDeltaText); } const assistantText = (normalizeText(payload.role) === "assistant" || normalizeText(payload.message_type) === "assistant") && normalizeAnswerFragment(payload.text); if (assistantText) { result.push(assistantText); } for (const [key, value] of Object.entries(payload)) { if (key === "text_block" || key === "delta") { continue; } if (Array.isArray(value) || isRecord(value)) { collectAnswerFragments(value, result); } } return result; } function formatDoubaoStreamError(payload: unknown): string { const errorCode = findFirstStringByKey(payload, ["error_code", "code"]); const errorMessage = findFirstStringByKey(payload, ["error_msg", "message", "detail"]); const verifyScene = findFirstStringByKey(payload, ["verify_scene"]); if (verifyScene) { return `doubao verification required (${verifyScene})`; } if (errorCode && errorMessage) { return `doubao stream error ${errorCode}: ${errorMessage}`; } if (errorCode) { return `doubao stream error ${errorCode}`; } if (errorMessage) { return `doubao stream error: ${errorMessage}`; } return "doubao stream error"; } function parseSSERecord(record: string): DoubaoStreamEvent | null { const lines = record.split(/\r?\n/); let eventName = "message"; const dataLines: string[] = []; for (const line of lines) { if (!line || line.startsWith(":")) { continue; } if (line.startsWith("event:")) { eventName = line.slice(6).trim() || eventName; continue; } if (line.startsWith("data:")) { dataLines.push(line.slice(5).trimStart()); } } if (dataLines.length === 0) { return null; } return { event: eventName, payload: safeParseJSON(dataLines.join("\n")), }; } async function parseDoubaoStream(response: Response): Promise { const summary: DoubaoStreamSummary = { answer: null, provider_request_id: null, request_id: null, conversation_id: null, citations: [], search_results: [], events: [], error_message: null, }; const body = response.body; if (!body) { throw new Error("doubao response stream missing"); } const decoder = new TextDecoder(); const reader = body.getReader(); let buffer = ""; const consumeRecord = (rawRecord: string) => { const parsed = parseSSERecord(rawRecord); if (!parsed) { return; } summary.events.push(parsed); const upperEvent = parsed.event.toUpperCase(); if (upperEvent.includes("ERROR")) { summary.error_message = formatDoubaoStreamError(parsed.payload); return; } if (!summary.conversation_id) { summary.conversation_id = readNestedString(parsed.payload, ["ack_client_meta", "conversation_id"]) ?? readNestedString(parsed.payload, ["client_meta", "conversation_id"]) ?? findFirstStringByKey(parsed.payload, ["conversation_id"]); } if (!summary.provider_request_id) { summary.provider_request_id = findFirstStringByKey(parsed.payload, ["log_id", "request_id", "req_id"]); } if (!summary.request_id) { summary.request_id = findFirstStringByKey(parsed.payload, ["message_id", "local_message_id", "reply_id"]); } const fragments = collectAnswerFragments(parsed.payload); for (const fragment of fragments) { summary.answer = mergeAnswerText(summary.answer, fragment); } collectSources(parsed.payload, summary.search_results, summary.citations); }; while (true) { const { done, value } = await reader.read(); if (done) { buffer += decoder.decode(); buffer = buffer.replace(/\r\n/g, "\n"); break; } buffer += decoder.decode(value, { stream: true }); buffer = buffer.replace(/\r\n/g, "\n"); let separatorIndex = buffer.indexOf("\n\n"); while (separatorIndex !== -1) { const rawRecord = buffer.slice(0, separatorIndex).trim(); buffer = buffer.slice(separatorIndex + 2); if (rawRecord) { consumeRecord(rawRecord); } separatorIndex = buffer.indexOf("\n\n"); } } const tail = buffer.trim(); if (tail) { consumeRecord(tail); } summary.citations = dedupeSourceItems(summary.citations); summary.search_results = dedupeSourceItems(summary.search_results); return summary; } async function getDoubaoCookieValue(name: string): Promise { for (const url of DOUBAO_COOKIE_URLS) { try { const cookie = await browser.cookies.get({ url, name }); const value = normalizeText(cookie?.value); if (value) { return value; } } catch { continue; } } return null; } async function readCurrentDoubaoRequestRuntimeState(): Promise> { const state = await getExtensionState(); const runtimeState = state.monitoring_runtime.doubao; return { fp: runtimeState?.fp ?? (await getDoubaoCookieValue(DOUBAO_FP_COOKIE_NAME)) ?? undefined, ms_token: runtimeState?.ms_token ?? undefined, device_id: runtimeState?.device_id ?? undefined, web_id: runtimeState?.web_id ?? undefined, tea_uuid: runtimeState?.tea_uuid ?? undefined, }; } function getDoubaoMissingRuntimeKeys( runtimeState: Partial, ): Array { return DOUBAO_REQUIRED_RUNTIME_KEYS.filter((key) => !normalizeText(runtimeState[key])); } function finalizeDoubaoRequestRuntimeState( runtimeState: Partial, ): DoubaoRequestRuntimeState { const missingKeys = getDoubaoMissingRuntimeKeys(runtimeState); if (missingKeys.length > 0) { throw new Error(`${DOUBAO_RUNTIME_SYNC_MESSAGE}: ${missingKeys.join(", ")}`); } return { fp: runtimeState.fp as string, ms_token: runtimeState.ms_token as string, device_id: runtimeState.device_id as string, web_id: runtimeState.web_id as string, tea_uuid: runtimeState.tea_uuid as string, }; } function delay(ms: number): Promise { return new Promise((resolve) => { globalThis.setTimeout(resolve, ms); }); } function isDoubaoTabUrl(urlValue: string | undefined): boolean { if (!urlValue) { return false; } try { const url = new URL(urlValue); return url.hostname === "doubao.com" || url.hostname === "www.doubao.com" || url.hostname.endsWith(".doubao.com"); } catch { return false; } } function isDoubaoRuntimeSyncResponse(value: unknown): value is DoubaoRuntimeSyncResponse { return typeof value === "object" && value !== null; } async function hasReadyDoubaoRequestRuntimeState(): Promise { const runtimeState = await readCurrentDoubaoRequestRuntimeState(); return getDoubaoMissingRuntimeKeys(runtimeState).length === 0; } async function waitForDoubaoRequestRuntimeState(timeoutMs: number): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() <= deadline) { if (await hasReadyDoubaoRequestRuntimeState()) { return true; } await delay(DOUBAO_RUNTIME_BOOTSTRAP_POLL_INTERVAL_MS); } return await hasReadyDoubaoRequestRuntimeState(); } async function waitForTabComplete(tabId: number, timeoutMs: number): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() <= deadline) { const tab = await browser.tabs.get(tabId).catch(() => null); if (!tab) { return false; } if (tab.status === "complete") { return true; } await delay(DOUBAO_RUNTIME_BOOTSTRAP_POLL_INTERVAL_MS); } const tab = await browser.tabs.get(tabId).catch(() => null); return tab?.status === "complete"; } async function requestDoubaoRuntimeSync(tabId: number): Promise { try { const response = await browser.tabs.sendMessage(tabId, { type: "geo.monitoring.runtime.sync", platform: "doubao", }); if (isDoubaoRuntimeSyncResponse(response) && response.ok === false) { throw new Error(response.error || "monitoring_runtime_sync_failed"); } } catch { // The content script also syncs on page load, so a missed message is safe to ignore here. } } function sortDoubaoTabs(tabs: chrome.tabs.Tab[]): chrome.tabs.Tab[] { return [...tabs].sort((left, right) => { if (Boolean(left.active) !== Boolean(right.active)) { return left.active ? -1 : 1; } if ((left.status === "complete") !== (right.status === "complete")) { return left.status === "complete" ? -1 : 1; } return (left.id ?? Number.MAX_SAFE_INTEGER) - (right.id ?? Number.MAX_SAFE_INTEGER); }); } async function bootstrapDoubaoRuntimeFromTab(tabId: number, timeoutMs: number): Promise { const tabReady = await waitForTabComplete(tabId, timeoutMs); if (!tabReady) { return await waitForDoubaoRequestRuntimeState(timeoutMs); } await delay(DOUBAO_RUNTIME_BOOTSTRAP_POLL_INTERVAL_MS); await requestDoubaoRuntimeSync(tabId); return await waitForDoubaoRequestRuntimeState(timeoutMs); } async function bootstrapDoubaoRuntimeWithExistingTabs(): Promise { const tabs = await browser.tabs.query({ url: DOUBAO_TAB_URL_PATTERNS }).catch(() => []); for (const tab of sortDoubaoTabs(tabs).filter((candidate) => typeof candidate.id === "number").slice(0, DOUBAO_EXISTING_TAB_PROBE_LIMIT)) { if (await bootstrapDoubaoRuntimeFromTab(tab.id as number, DOUBAO_EXISTING_TAB_BOOTSTRAP_TIMEOUT_MS)) { return true; } } return false; } async function bootstrapDoubaoRuntimeWithSilentTab(): Promise { const page = await openSilentAutomationPage(DOUBAO_BOOTSTRAP_URL).catch(() => null); if (!page) { return false; } try { return await bootstrapDoubaoRuntimeFromTab(page.tabId, DOUBAO_RUNTIME_BOOTSTRAP_TIMEOUT_MS); } finally { await closeSilentAutomationPage(page); } } async function ensureDoubaoRequestRuntimeStateReady(): Promise { if (await hasReadyDoubaoRequestRuntimeState()) { return true; } if (doubaoRuntimeBootstrapInFlight) { return await doubaoRuntimeBootstrapInFlight; } if (Date.now() - lastDoubaoRuntimeBootstrapAt < DOUBAO_RUNTIME_BOOTSTRAP_COOLDOWN_MS) { return false; } lastDoubaoRuntimeBootstrapAt = Date.now(); doubaoRuntimeBootstrapInFlight = (async () => { if (await bootstrapDoubaoRuntimeWithExistingTabs()) { return true; } if (await hasReadyDoubaoRequestRuntimeState()) { return true; } return await bootstrapDoubaoRuntimeWithSilentTab(); })().finally(() => { doubaoRuntimeBootstrapInFlight = null; }); return await doubaoRuntimeBootstrapInFlight; } async function loadDoubaoRequestRuntimeState(): Promise { const currentState = await readCurrentDoubaoRequestRuntimeState(); try { return finalizeDoubaoRequestRuntimeState(currentState); } catch { const ready = await ensureDoubaoRequestRuntimeStateReady(); if (!ready) { throw new Error( `${DOUBAO_RUNTIME_SYNC_MESSAGE}: ${getDoubaoMissingRuntimeKeys(await readCurrentDoubaoRequestRuntimeState()).join(", ")}`, ); } return finalizeDoubaoRequestRuntimeState(await readCurrentDoubaoRequestRuntimeState()); } } function buildDoubaoRequestUrl(runtimeState: DoubaoRequestRuntimeState): string { const url = new URL(DOUBAO_CHAT_URL); url.searchParams.set("aid", DOUBAO_APP_ID); url.searchParams.set("device_platform", "web"); url.searchParams.set("device_id", runtimeState.device_id); url.searchParams.set("web_id", runtimeState.web_id); url.searchParams.set("tea_uuid", runtimeState.tea_uuid); url.searchParams.set("msToken", runtimeState.ms_token); url.searchParams.set("fp", runtimeState.fp); return url.toString(); } function buildDoubaoRequestBody(questionText: string, runtimeState: DoubaoRequestRuntimeState): Record { return { client_meta: { local_conversation_id: `local_${Date.now()}${Math.floor(Math.random() * 1_000_000)}`, conversation_id: "", bot_id: DOUBAO_BOT_ID, last_section_id: "", last_message_index: null, }, messages: [ { local_message_id: crypto.randomUUID(), content_block: [ { block_type: 10000, content: { text_block: { text: questionText, icon_url: "", icon_url_dark: "", summary: "", }, pc_event_block: "", }, block_id: crypto.randomUUID(), parent_id: "", meta_info: [], append_fields: [], }, ], message_status: 0, }, ], option: { need_deep_think: 1, need_create_conversation: true, conversation_init_option: { need_ack_conversation: true, }, sse_recv_event_options: { support_chunk_delta: true, }, }, ext: { use_deep_think: "1", fp: runtimeState.fp, conversation_init_option: JSON.stringify({ need_ack_conversation: true, }), commerce_credit_config_enable: "0", sub_conv_firstmet_type: "1", }, }; } async function queryDoubao(platform: StoredMonitoringPlatformState, questionText: string): Promise { const runtimeState = await loadDoubaoRequestRuntimeState(); const requestBody = buildDoubaoRequestBody(questionText, runtimeState); const response = await fetch(buildDoubaoRequestUrl(runtimeState), { method: "POST", credentials: "include", headers: { Accept: "text/event-stream", "Content-Type": "application/json", }, referrer: DOUBAO_REFERER, body: JSON.stringify(requestBody), }); if (!response.ok) { const bodyText = await response.text().catch(() => ""); const detail = normalizeText(bodyText)?.slice(0, 200); throw new Error(detail ? `doubao request failed ${response.status}: ${detail}` : `doubao request failed ${response.status}`); } const streamSummary = await parseDoubaoStream(response); if (streamSummary.error_message) { throw new Error(streamSummary.error_message); } if (!streamSummary.answer && streamSummary.citations.length === 0 && streamSummary.search_results.length === 0) { throw new Error("doubao empty response"); } return { provider_model: `${platform.ai_platform_id}-web-thinking`, provider_request_id: streamSummary.provider_request_id, request_id: streamSummary.request_id ?? streamSummary.conversation_id, answer: streamSummary.answer, raw_response_json: { platform: platform.ai_platform_id, mode: "deep_think", conversation_id: streamSummary.conversation_id, provider_request_id: streamSummary.provider_request_id, request_id: streamSummary.request_id, answer: streamSummary.answer, citation_count: streamSummary.citations.length, thought_reference_count: streamSummary.search_results.length, reference_count: streamSummary.search_results.length, search_result_count: streamSummary.search_results.length, event_count: streamSummary.events.length, citations: streamSummary.citations, thought_references: streamSummary.search_results, thinking_references: streamSummary.search_results, references: streamSummary.search_results, search_results: streamSummary.search_results, sources: streamSummary.search_results, events: streamSummary.events, status: "succeeded", }, citations: streamSummary.citations, search_results: streamSummary.search_results, }; } export const doubaoMonitoringAdapter: MonitoringAdapter = { async detect(platform) { const connected = await hasAnyPlatformCookie(platform); const reachable = await isPlatformReachable(platform); if (!reachable) { return normalizeDetectResult(connected, false, "unavailable", "平台当前不可访问"); } let runtimeState = await readCurrentDoubaoRequestRuntimeState(); if (getDoubaoMissingRuntimeKeys(runtimeState).length > 0) { await ensureDoubaoRequestRuntimeStateReady(); runtimeState = await readCurrentDoubaoRequestRuntimeState(); } if (getDoubaoMissingRuntimeKeys(runtimeState).length > 0) { return { ...normalizeDetectResult(connected, true, "runtime_sync_pending", DOUBAO_RUNTIME_SYNC_HINT), message: DOUBAO_RUNTIME_SYNC_HINT, }; } return normalizeDetectResult(connected, true, null, null); }, async canQuery() { try { await loadDoubaoRequestRuntimeState(); return true; } catch { return false; } }, async query(platform, task) { return await queryDoubao(platform, task.question_text); }, };