@bsbofmusic/cdper-doubao 1.0.2 → 1.0.4
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 +15 -4
- package/package.json +3 -3
- package/src/doubao.js +16 -154
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ npm install -g @bsbofmusic/cdper-doubao
|
|
|
11
11
|
Requirements:
|
|
12
12
|
|
|
13
13
|
- Node.js 18+
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- CDP WS
|
|
14
|
+
- A real Chrome/Edge profile with Doubao logged in
|
|
15
|
+
- Either local Chrome DevTools on `127.0.0.1:9222` or a remote CDP Bridge
|
|
16
|
+
- Optional explicit CDP WS through `CDP_WS`, `~/.cdp-auth.json`, or `~/.cdp-bridge/config.json`
|
|
17
17
|
|
|
18
18
|
## Use
|
|
19
19
|
|
|
@@ -54,6 +54,17 @@ const result = await queryDoubao('请用一句话回答:1+1等于几?', {
|
|
|
54
54
|
});
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
|
|
58
|
+
## Release notes — 1.0.3
|
|
59
|
+
|
|
60
|
+
- Depends on `@bsbofmusic/cdper-core@^1.2.7`, so `status --json` correctly reports healthy local CDP as `ok=true`.
|
|
61
|
+
- Removed the dead `url-action` query branch from the live Doubao query path. Fresh/followup queries now consistently use the composer path: open clean Doubao home, switch `快速 → 专家`, insert prompt text, click send, and verify submission.
|
|
62
|
+
- Kept `url-action` only as a URL-pattern guard for conversation-id parsing (`/chat/url-action` is not a real followup anchor).
|
|
63
|
+
- Pack/install smoke was verified from the generated tarball in an isolated npm prefix before publish.
|
|
64
|
+
|
|
65
|
+
Design note: Doubao link reading is more reliable when URLs are included in the composer prompt and handled by the page’s native preview/search flow. A hidden url-action branch created false confidence and made fallback logic unreachable.
|
|
66
|
+
|
|
67
|
+
---
|
|
57
68
|
## Security
|
|
58
69
|
|
|
59
|
-
All browser control happens through the user's CDP Bridge. Tokens are redacted from diagnostic output. Do not commit `CDP_WS` or full WebSocket URLs containing `token=`.
|
|
70
|
+
All browser control happens through the user's real browser via local CDP or CDP Bridge. Tokens are redacted from diagnostic output. Do not commit `CDP_WS` or full WebSocket URLs containing `token=`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsbofmusic/cdper-doubao",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Standalone Doubao CLI controlled through a user's real Chrome/Edge via CDP Bridge",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "src/public-api.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=18.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@bsbofmusic/cdper-core": "
|
|
42
|
+
"@bsbofmusic/cdper-core": "~1.2.8",
|
|
43
43
|
"yargs": "^17.7.2"
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
package/src/doubao.js
CHANGED
|
@@ -12,8 +12,8 @@ const { runState } = require('@bsbofmusic/cdper-core/state_machine');
|
|
|
12
12
|
const { getRuntimeStatus } = require('@bsbofmusic/cdper-core/runtime_status');
|
|
13
13
|
const { WRAPPER_STATUSES, buildWrapperResult } = require('@bsbofmusic/cdper-core/wrapper_result');
|
|
14
14
|
const {
|
|
15
|
-
randomInt, humanPause, withLocalTimeout,
|
|
16
|
-
computeSendTimeoutMs,
|
|
15
|
+
randomInt, humanPause, withLocalTimeout, throwIfAborted, ensureMinimumPageDwell, humanWarmup,
|
|
16
|
+
computeSendTimeoutMs, insertTextViaCdp,
|
|
17
17
|
buildWaitPlan, createProgressRecorder,
|
|
18
18
|
resolveConversationPolicy, buildConversationAnchor,
|
|
19
19
|
prefersShortAnswer, handlePluginQueryError,
|
|
@@ -22,7 +22,6 @@ const {
|
|
|
22
22
|
|
|
23
23
|
const PLATFORM = 'doubao';
|
|
24
24
|
const STATE_TIMEOUTS = { open: 150000, ensure_session: 30000, ensure_mode: 45000, send: 45000, extract: 10000 };
|
|
25
|
-
const FALLBACK_TIMEOUT_MS = 90000;
|
|
26
25
|
const WAIT_PROFILES = {
|
|
27
26
|
short: { minWait: 8000, maxWait: 90000, maxExtends: 2, pollInterval: 2500 },
|
|
28
27
|
normal: { minWait: 12000, maxWait: 300000, maxExtends: 6, pollInterval: 2500 },
|
|
@@ -30,40 +29,11 @@ const WAIT_PROFILES = {
|
|
|
30
29
|
very_long: { minWait: 30000, maxWait: 45 * 60 * 1000, maxExtends: 10, pollInterval: 2500 },
|
|
31
30
|
};
|
|
32
31
|
|
|
33
|
-
function directActionMode(mode) {
|
|
34
|
-
return mode === 'quick' ? 'quick' : 'deep';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function buildDoubaoUrlAction(queryText, mode = 'deep') {
|
|
38
|
-
const payload = { text: String(queryText || '') };
|
|
39
|
-
const action = { pluginId: 'Send_Message', payload };
|
|
40
|
-
if (directActionMode(mode) === 'deep') {
|
|
41
|
-
payload.extraExt = {
|
|
42
|
-
input_skill: JSON.stringify({ skill_type: 20 }),
|
|
43
|
-
use_deep_think: '1',
|
|
44
|
-
};
|
|
45
|
-
action.options = {
|
|
46
|
-
deepThinkingActiveType: '2',
|
|
47
|
-
superTaskStatus: { switchValue: 0, taskMode: 0, safeToken: '' },
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
const url = new URL('https://www.doubao.com/chat/url-action');
|
|
51
|
-
url.searchParams.set('action', JSON.stringify(action));
|
|
52
|
-
return url.toString();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
32
|
function emptyDoubaoSnapshot() {
|
|
56
33
|
return { text: '', normalized: '', length: 0, candidateCount: 0 };
|
|
57
34
|
}
|
|
58
35
|
|
|
59
|
-
function buildDoubaoModeState(
|
|
60
|
-
if (usedUrlAction) {
|
|
61
|
-
return {
|
|
62
|
-
modePath: urlActionMode === 'quick' ? 'url_action_quick' : 'url_action_deep',
|
|
63
|
-
modeConfirmed: true,
|
|
64
|
-
modeConfidence: 'high',
|
|
65
|
-
};
|
|
66
|
-
}
|
|
36
|
+
function buildDoubaoModeState(expertModeConfirmed) {
|
|
67
37
|
return {
|
|
68
38
|
modePath: expertModeConfirmed ? 'composer_expert' : 'composer_quick',
|
|
69
39
|
modeConfirmed: Boolean(expertModeConfirmed),
|
|
@@ -369,68 +339,6 @@ async function openDoubaoHome(page, sessionId) {
|
|
|
369
339
|
throw new Error('Doubao 页面未准备好:输入框未出现');
|
|
370
340
|
}
|
|
371
341
|
|
|
372
|
-
async function getDoubaoDirectState(page) {
|
|
373
|
-
return page.evaluate(() => {
|
|
374
|
-
const bodyText = document.body?.innerText || '';
|
|
375
|
-
const inputs = Array.from(document.querySelectorAll('textarea, [contenteditable="true"], [role="textbox"]'));
|
|
376
|
-
const input = inputs.find((element) => {
|
|
377
|
-
const rect = element.getBoundingClientRect();
|
|
378
|
-
return rect.width >= 80 && rect.height >= 20 && element.getAttribute('aria-hidden') !== 'true' && !element.disabled;
|
|
379
|
-
});
|
|
380
|
-
const inputText = String(input?.value || input?.innerText || input?.textContent || '').trim();
|
|
381
|
-
const isVisible = (element) => {
|
|
382
|
-
if (!element) return false;
|
|
383
|
-
const rect = element.getBoundingClientRect();
|
|
384
|
-
const style = window.getComputedStyle(element);
|
|
385
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none' && element.getAttribute('aria-hidden') !== 'true';
|
|
386
|
-
};
|
|
387
|
-
const stopButton = Array.from(document.querySelectorAll('button,[role="button"]')).find((button) => {
|
|
388
|
-
if (!isVisible(button) || button.disabled || button.getAttribute('aria-disabled') === 'true') return false;
|
|
389
|
-
const text = button.innerText || '';
|
|
390
|
-
const aria = button.getAttribute('aria-label') || '';
|
|
391
|
-
return /停止生成|停止回答|Stop generating|stop-button/i.test(`${text} ${aria}`) || !!button.querySelector('svg[icon="stop"]');
|
|
392
|
-
});
|
|
393
|
-
const loading = Array.from(document.querySelectorAll('[class*="loading"], [class*="spinner"], [class*="skeleton"]')).find((element) => isVisible(element) && /loading|spinner|生成中|思考中|回答中/i.test(String(element.className || '') + ' ' + (element.innerText || '')));
|
|
394
|
-
const replyContainers = Array.from(document.querySelectorAll('[class*="flow-markdown-body"], [class*="container-P2rR72"], [class*="mdbox-theme"], [class*="message"], [class*="response"], [class*="answer"], .prose'))
|
|
395
|
-
.filter((element) => {
|
|
396
|
-
const cls = String(element.className || '');
|
|
397
|
-
if (/message-list|suggest|recommend|bg-g-send/i.test(cls)) return false;
|
|
398
|
-
const rect = element.getBoundingClientRect();
|
|
399
|
-
const text = (element.innerText || '').trim();
|
|
400
|
-
return rect.width > 0 && rect.height > 0 && text && !/历史对话|内容由豆包 AI 生成/.test(text);
|
|
401
|
-
});
|
|
402
|
-
return {
|
|
403
|
-
url: location.href,
|
|
404
|
-
title: document.title || '',
|
|
405
|
-
bodySample: bodyText.slice(0, 1200),
|
|
406
|
-
hasInput: !!input,
|
|
407
|
-
inputText,
|
|
408
|
-
isGenerating: !!stopButton || !!loading,
|
|
409
|
-
candidateCount: replyContainers.length,
|
|
410
|
-
unavailable: bodyText.includes('该页面暂时不可用'),
|
|
411
|
-
};
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async function openDoubaoUrlAction(page, queryText, mode, sessionId) {
|
|
416
|
-
const targetUrl = buildDoubaoUrlAction(queryText, mode);
|
|
417
|
-
await page.goto(targetUrl, { timeout: 60000, waitUntil: 'domcontentloaded' });
|
|
418
|
-
await humanPause(1800, 3800);
|
|
419
|
-
await recoverDoubaoUnavailablePage(page);
|
|
420
|
-
await assertNoChallenge(page, sessionId);
|
|
421
|
-
const deadline = Date.now() + 30000;
|
|
422
|
-
let state = null;
|
|
423
|
-
while (Date.now() < deadline) {
|
|
424
|
-
state = await getDoubaoDirectState(page);
|
|
425
|
-
if (state.unavailable) await recoverDoubaoUnavailablePage(page);
|
|
426
|
-
if (state.candidateCount > 0 || state.isGenerating) return { accepted: true, needsManualSubmit: false, state, url: targetUrl };
|
|
427
|
-
if (state.hasInput && state.inputText) return { accepted: false, needsManualSubmit: true, state, url: targetUrl };
|
|
428
|
-
await humanPause(400, 800);
|
|
429
|
-
}
|
|
430
|
-
state = state || await getDoubaoDirectState(page).catch(() => null);
|
|
431
|
-
return { accepted: false, needsManualSubmit: true, state, url: targetUrl };
|
|
432
|
-
}
|
|
433
|
-
|
|
434
342
|
async function hasDoubaoConversationContent(page) {
|
|
435
343
|
const snapshot = await captureDoubaoReplySnapshot(page).catch(() => null);
|
|
436
344
|
return Number(snapshot?.length || 0) > 0;
|
|
@@ -635,7 +543,7 @@ async function switchToExpert(page, sessionId) {
|
|
|
635
543
|
|
|
636
544
|
// ─── Main query ───────────────────────────────────────────────────────────────
|
|
637
545
|
async function queryDoubao(queryText, opts = {}) {
|
|
638
|
-
const { mode = 'expert', sessionId, _retried = false
|
|
546
|
+
const { mode = 'expert', sessionId, _retried = false } = opts;
|
|
639
547
|
const abortSignal = opts.abortSignal || null;
|
|
640
548
|
const fallbackMeta = opts._fallbackMeta || null;
|
|
641
549
|
throwIfAborted(abortSignal, 'Doubao query');
|
|
@@ -654,9 +562,6 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
654
562
|
let initialReplySnapshot = null;
|
|
655
563
|
let inputReadyAfterOpen = false;
|
|
656
564
|
let promptSubmitted = false;
|
|
657
|
-
let usedUrlAction = false;
|
|
658
|
-
let urlActionNeedsManualSubmit = false;
|
|
659
|
-
let urlActionMode = directActionMode(mode);
|
|
660
565
|
let expertModeConfirmed = false;
|
|
661
566
|
let page, browser;
|
|
662
567
|
|
|
@@ -713,7 +618,6 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
713
618
|
|
|
714
619
|
throwIfAborted(abortSignal, 'Doubao query');
|
|
715
620
|
await runState(flow, 'ensure_session', async () => {
|
|
716
|
-
if (usedUrlAction && promptSubmitted) return;
|
|
717
621
|
if (inputReadyAfterOpen && await hasDoubaoInput(page, 1800)) return;
|
|
718
622
|
const existingInput = await hasDoubaoInput(page, 12000);
|
|
719
623
|
if (!existingInput) {
|
|
@@ -730,46 +634,29 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
730
634
|
|
|
731
635
|
throwIfAborted(abortSignal, 'Doubao query');
|
|
732
636
|
expertModeConfirmed = await runState(flow, 'ensure_mode', async () => {
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
if (usedUrlAction && promptSubmitted && !urlActionNeedsManualSubmit) return false;
|
|
736
|
-
// For all other cases (including url-action that still needs manual
|
|
737
|
-
// submission), verify and switch to expert mode via the 快速→专家 menu.
|
|
637
|
+
// Fresh/followup queries always use the composer path; verify and switch
|
|
638
|
+
// to expert mode via the 快速→专家 menu before sending.
|
|
738
639
|
const confirmed = await switchToExpert(page, session.sessionId);
|
|
739
640
|
await assertNoChallenge(page, session.sessionId);
|
|
740
641
|
if (confirmed) await waitForDoubaoModeSettled(page, { timeoutMs: 6000, sessionId: session.sessionId }).catch(() => false);
|
|
741
642
|
return confirmed;
|
|
742
643
|
}, { timeoutMs: STATE_TIMEOUTS.ensure_mode });
|
|
743
|
-
log('INFO', '[doubao]', { component: 'doubao', plugin: 'cdper-doubao', session_id: session.sessionId, step: 'flow', action: 'stage_complete', stage: 'ensure_mode', status: 'ok', ...buildDoubaoModeState(
|
|
744
|
-
// Block sending
|
|
745
|
-
//
|
|
746
|
-
|
|
747
|
-
const modeBlockingBypass = usedUrlAction && promptSubmitted && !urlActionNeedsManualSubmit;
|
|
748
|
-
if (!modeBlockingBypass && !expertModeConfirmed) {
|
|
644
|
+
log('INFO', '[doubao]', { component: 'doubao', plugin: 'cdper-doubao', session_id: session.sessionId, step: 'flow', action: 'stage_complete', stage: 'ensure_mode', status: 'ok', ...buildDoubaoModeState(expertModeConfirmed), expertModeConfirmed });
|
|
645
|
+
// Block sending when expert mode could not be confirmed; composer path
|
|
646
|
+
// should not silently degrade into the quick model.
|
|
647
|
+
if (!expertModeConfirmed) {
|
|
749
648
|
log('WARN', '[doubao]', { component: 'doubao', plugin: 'cdper-doubao', session_id: session.sessionId, step: 'mode', action: 'expert_confirm', status: 'blocked', message: '未确认进入专家模式,停止发送以避免错误模式输出' });
|
|
750
649
|
throw new Error('Doubao 专家模式未确认,需要人工接管');
|
|
751
650
|
}
|
|
752
651
|
|
|
753
652
|
throwIfAborted(abortSignal, 'Doubao query');
|
|
754
653
|
await runState(flow, 'send', async () => {
|
|
755
|
-
if (usedUrlAction) {
|
|
756
|
-
if (!initialReplySnapshot) initialReplySnapshot = emptyDoubaoSnapshot();
|
|
757
|
-
if (promptSubmitted && !urlActionNeedsManualSubmit) {
|
|
758
|
-
flow.enter('send', { submitVerified: true, submitSignal: 'url_action_accepted', submitElapsed: 0, mode: urlActionMode });
|
|
759
|
-
log('INFO', '[doubao]', { component: 'doubao', plugin: 'cdper-doubao', session_id: session.sessionId, step: 'query', action: 'submit', status: 'ok', message: '问题已通过 url-action 发送' });
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
654
|
const input = await hasDoubaoInput(page, 18000);
|
|
764
655
|
if (!input) throw new Error('Doubao 输入框未就绪');
|
|
765
656
|
await assertNoChallenge(page, session.sessionId);
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
await typeIntoDoubaoInput(page, input.handle, queryText);
|
|
770
|
-
} else {
|
|
771
|
-
await input.handle.focus();
|
|
772
|
-
}
|
|
657
|
+
initialReplySnapshot = await captureDoubaoReplySnapshot(page).catch(() => null);
|
|
658
|
+
await clearDoubaoInput(page, input.handle);
|
|
659
|
+
await typeIntoDoubaoInput(page, input.handle, queryText);
|
|
773
660
|
await assertNoChallenge(page, session.sessionId);
|
|
774
661
|
await humanPause(900, 1800);
|
|
775
662
|
const clicked = await clickDoubaoSend(page);
|
|
@@ -848,18 +735,6 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
848
735
|
|
|
849
736
|
const completenessScore = result.completeness?.score || 0;
|
|
850
737
|
const contentMarkedIncomplete = result.completeness?.isComplete === false;
|
|
851
|
-
if (!_quickFallback && conversationPlan.effective === 'fresh' && usedUrlAction && urlActionMode === 'deep' && completenessScore < 70 && contentMarkedIncomplete && !prefersShortAnswer(queryText)) {
|
|
852
|
-
log('WARN', '[doubao]', { component: 'doubao', plugin: 'cdper-doubao', session_id: session.sessionId, step: 'query', action: 'fallback', status: 'degraded', message: 'deep url-action produced low-completeness content; retrying quick url-action (score=' + completenessScore + ')' });
|
|
853
|
-
if (page || browser) await disconnect(page, browser).catch(() => undefined);
|
|
854
|
-
page = null;
|
|
855
|
-
browser = null;
|
|
856
|
-
return await withAbortableTimeout(
|
|
857
|
-
(signal) => queryDoubao(queryText, { ...opts, mode: 'quick', conversationPolicy: 'fresh', _quickFallback: true, abortSignal: signal, _fallbackMeta: { attempted: true, fromMode: urlActionMode, toMode: 'quick', reason: 'low_completeness', timeoutMs: FALLBACK_TIMEOUT_MS } }),
|
|
858
|
-
'quick url-action fallback',
|
|
859
|
-
FALLBACK_TIMEOUT_MS
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
738
|
if (wrapperStatus === WRAPPER_STATUSES.PARTIAL_TIMEOUT && result.content) {
|
|
864
739
|
return buildWrapperResult({
|
|
865
740
|
status: WRAPPER_STATUSES.PARTIAL_TIMEOUT,
|
|
@@ -875,7 +750,7 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
875
750
|
return updateSession(session.sessionId, { turns: session.turns + 1, conversation });
|
|
876
751
|
}, { timeoutMs: STATE_TIMEOUTS.extract });
|
|
877
752
|
|
|
878
|
-
const modeState = buildDoubaoModeState(
|
|
753
|
+
const modeState = buildDoubaoModeState(expertModeConfirmed);
|
|
879
754
|
const contentIsComplete = result.completeness?.isComplete === true;
|
|
880
755
|
const finalStatus = ['complete', 'complete_short'].includes(result.exitReason) && contentIsComplete ? 'ok' : 'degraded';
|
|
881
756
|
log('INFO', '[doubao]', { component: 'doubao', plugin: 'cdper-doubao', session_id: session.sessionId, step: 'query', action: 'complete', status: finalStatus, message: '回复完成:' + result.content.length + ' 字,完整度 ' + (result.completeness?.score || 0) + '分,exitReason=' + (result.exitReason || 'unknown') });
|
|
@@ -885,7 +760,7 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
885
760
|
session: activeSession,
|
|
886
761
|
flow,
|
|
887
762
|
runtimeStatus: await getRuntimeStatus('doubao'),
|
|
888
|
-
doneMeta: { status: finalStatus, turn: activeSession.turns, ...modeState,
|
|
763
|
+
doneMeta: { status: finalStatus, turn: activeSession.turns, ...modeState, expertModeConfirmed, conversationPolicy: conversationPlan.effective },
|
|
889
764
|
payload: {
|
|
890
765
|
content: result.content,
|
|
891
766
|
score: completenessScore,
|
|
@@ -894,9 +769,7 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
894
769
|
fallback: fallbackMeta,
|
|
895
770
|
exitReason: result.exitReason || 'unknown',
|
|
896
771
|
...modeState,
|
|
897
|
-
|
|
898
|
-
usedUrlAction,
|
|
899
|
-
urlActionMode: usedUrlAction ? urlActionMode : null,
|
|
772
|
+
expertModeConfirmed,
|
|
900
773
|
detectedChallenge: result.detectedChallenge || null,
|
|
901
774
|
conversationPolicy: conversationPlan.requested,
|
|
902
775
|
effectiveConversationPolicy: conversationPlan.effective,
|
|
@@ -906,17 +779,6 @@ async function queryDoubao(queryText, opts = {}) {
|
|
|
906
779
|
},
|
|
907
780
|
});
|
|
908
781
|
} catch (error) {
|
|
909
|
-
if (!_quickFallback && error.code !== 'FALLBACK_TIMEOUT' && conversationPlan.effective === 'fresh' && usedUrlAction && urlActionMode === 'deep') {
|
|
910
|
-
log('WARN', '[doubao]', { component: 'doubao', plugin: 'cdper-doubao', session_id: session.sessionId, step: 'query', action: 'fallback', status: 'degraded', message: 'deep url-action failed; retrying quick url-action: ' + error.message });
|
|
911
|
-
if (page || browser) await disconnect(page, browser).catch(() => undefined);
|
|
912
|
-
page = null;
|
|
913
|
-
browser = null;
|
|
914
|
-
return await withAbortableTimeout(
|
|
915
|
-
(signal) => queryDoubao(queryText, { ...opts, mode: 'quick', conversationPolicy: 'fresh', _quickFallback: true, abortSignal: signal, _fallbackMeta: { attempted: true, fromMode: urlActionMode, toMode: 'quick', reason: error.message, timeoutMs: FALLBACK_TIMEOUT_MS } }),
|
|
916
|
-
'quick url-action fallback',
|
|
917
|
-
FALLBACK_TIMEOUT_MS
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
782
|
return handlePluginQueryError(error, {
|
|
921
783
|
flow, pluginName: 'doubao', retried: _retried,
|
|
922
784
|
retryFn: () => queryDoubao(queryText, { ...opts, _retried: true }),
|