agent-bridge-doubao 1.0.7 → 1.0.9
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.
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DoubaoCdpClient = void 0;
|
|
3
|
+
exports.DoubaoCdpClient = exports.responseTextScript = void 0;
|
|
4
|
+
exports.doubaoStreamErrorMessage = doubaoStreamErrorMessage;
|
|
4
5
|
exports.requestBody = requestBody;
|
|
5
|
-
exports.responseText = responseText;
|
|
6
6
|
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
function doubaoStreamErrorMessage(data) {
|
|
8
|
+
let code = "";
|
|
9
|
+
let subtype = "";
|
|
10
|
+
try {
|
|
11
|
+
const value = JSON.parse(data);
|
|
12
|
+
code = String(value.code ?? value.error?.code ?? "");
|
|
13
|
+
subtype = String(value.subtype ?? value.error?.subtype ?? "").toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
catch { /* 平台可能返回非 JSON 错误 */ }
|
|
16
|
+
if (code === "710022004" || subtype === "slide" || /rate limited|verify|slide/i.test(data)) {
|
|
17
|
+
return `豆包平台触发滑块验证${code ? `(错误码 ${code})` : ""},请在豆包客户端完成验证后重试`;
|
|
18
|
+
}
|
|
19
|
+
return `豆包平台返回流式错误${code ? `(错误码 ${code})` : ""},请稍后重试`;
|
|
20
|
+
}
|
|
7
21
|
const defaults = { port: Number(process.env.DOUBAO_CDP_PORT ?? "9227"), conversationId: process.env.DOUBAO_CONVERSATION_ID ?? "759450172544514", botId: process.env.DOUBAO_BOT_ID ?? "7338286299411103781", timeoutMs: Number(process.env.DOUBAO_TIMEOUT_MS ?? "180000") };
|
|
8
22
|
function sleep(ms, signal) {
|
|
9
23
|
if (signal?.aborted)
|
|
@@ -15,31 +29,11 @@ function requestBody(text, conversationId, botId, pluginList = []) {
|
|
|
15
29
|
const block = { block_type: 10000, content: { text_block: { text, icon_url: "", icon_url_dark: "", summary: "" }, pc_event_block: "" }, block_id: (0, node_crypto_1.randomUUID)(), parent_id: "", meta_info: [], append_fields: [] };
|
|
16
30
|
return JSON.stringify({ client_meta: { conversation_id: conversationId, bot_id: botId, last_section_id: "", last_message_index: null, local_permissions: [{ permission_name: "ACCESS_COARSE_LOCATION", status: 1 }, { permission_name: "ACCESS_FINE_LOCATION", status: 1 }, { permission_name: "ACCESS_BACKGROUND_LOCATION", status: 1 }] }, messages: [{ local_message_id: (0, node_crypto_1.randomUUID)(), content_block: [block], message_status: 0 }], option: { send_message_scene: "", create_time_ms: Date.now(), collect_id: "", is_audio: false, answer_with_suggest: false, agent_mode: 2, tts_switch: false, need_deep_think: 0, click_clear_context: false, is_regen: false, is_replace: false, is_from_click_option: false, is_from_click_softlink: false, disable_sse_cache: false, scene_type: 0, unique_key: (0, node_crypto_1.randomUUID)(), start_seq: 0, need_create_conversation: false, regen_query_id: [], edit_query_id: [], message_from: 0, sse_recv_event_options: { support_chunk_delta: true }, support_lazy_fetch_stream: true, is_ai_playground: false, is_old_user: true, general_task_param: general, recovery_option: { is_recovery: false, req_create_time_sec: Math.floor(Date.now() / 1000), append_sse_event_scene: 0 }, message_storage_type: 0, related_deleted_message_ids: {}, connector_info_list: [], model_config: { model_item_key: "0", model_extra_params: {}, reasoning_effort: 5 }, aggregate_params: { mention_skill_list: "[]", mention_plugin_list: JSON.stringify(pluginList), mention_ext: "[{}]", conversation_mode: "", mode_id: "1", model_item_key: "0", agent_mode: "2", reasoning_effort: "5", provider_id: "" } }, user_context: [], ext: { agent_mode: "2", use_deep_think: "0", general_task_param: JSON.stringify(general), collection_id: "", is_finish: "1", commerce_credit_config_enable: "0" } });
|
|
17
31
|
}
|
|
18
|
-
|
|
19
|
-
if (event !== "STREAM_MSG_NOTIFY" && event !== "FULL_MSG_NOTIFY")
|
|
20
|
-
return [];
|
|
21
|
-
const texts = [];
|
|
22
|
-
const visit = (item) => {
|
|
23
|
-
if (Array.isArray(item)) {
|
|
24
|
-
for (const child of item)
|
|
25
|
-
visit(child);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (!item || typeof item !== "object")
|
|
29
|
-
return;
|
|
30
|
-
const record = item;
|
|
31
|
-
const block = record.text_block;
|
|
32
|
-
if (block && typeof block === "object" && typeof block.text === "string")
|
|
33
|
-
texts.push(block.text);
|
|
34
|
-
for (const child of Object.values(record))
|
|
35
|
-
visit(child);
|
|
36
|
-
};
|
|
37
|
-
visit(value);
|
|
38
|
-
return texts;
|
|
39
|
-
}
|
|
32
|
+
exports.responseTextScript = `(value) => { const texts = []; const visit = (item) => { if (Array.isArray(item)) { for (const child of item) visit(child); return; } if (!item || typeof item !== 'object') return; if (item.text_block && typeof item.text_block.text === 'string') texts.push(item.text_block.text); for (const child of Object.values(item)) visit(child); }; visit(value); return texts; }`;
|
|
40
33
|
class DoubaoCdpClient {
|
|
41
34
|
socket;
|
|
42
35
|
sequence = 0;
|
|
36
|
+
closeController = new AbortController();
|
|
43
37
|
pending = new Map();
|
|
44
38
|
options;
|
|
45
39
|
constructor(options = {}) { this.options = { port: options.port ?? defaults.port, conversationId: options.conversationId ?? defaults.conversationId, botId: options.botId ?? defaults.botId, pluginList: options.pluginList ?? [], timeoutMs: options.timeoutMs ?? defaults.timeoutMs, fetchImpl: options.fetchImpl }; }
|
|
@@ -59,13 +53,22 @@ class DoubaoCdpClient {
|
|
|
59
53
|
socket.onmessage = event => this.onMessage(String(event.data));
|
|
60
54
|
await new Promise((resolve, reject) => { socket.onopen = () => resolve(); socket.onerror = () => reject(new Error("豆包 CDP WebSocket 连接失败")); });
|
|
61
55
|
}
|
|
62
|
-
close() {
|
|
63
|
-
|
|
56
|
+
close() {
|
|
57
|
+
this.closeController.abort();
|
|
58
|
+
if (this.socket)
|
|
59
|
+
this.socket.send(JSON.stringify({ id: ++this.sequence, method: "Runtime.evaluate", params: { expression: "window.__mab_doubao_controller?.abort()" } }));
|
|
60
|
+
this.socket?.close();
|
|
61
|
+
this.socket = undefined;
|
|
62
|
+
for (const pending of this.pending.values())
|
|
63
|
+
pending.reject(new Error("豆包 CDP 已关闭"));
|
|
64
|
+
this.pending.clear();
|
|
65
|
+
}
|
|
64
66
|
async direct(text, signal) {
|
|
65
67
|
await this.evaluate(buildInjection(requestBody(text, this.options.conversationId, this.options.botId, this.options.pluginList)));
|
|
66
68
|
const deadline = Date.now() + this.options.timeoutMs;
|
|
69
|
+
const requestSignal = signal ? AbortSignal.any([signal, this.closeController.signal]) : this.closeController.signal;
|
|
67
70
|
while (Date.now() < deadline) {
|
|
68
|
-
await sleep(1000,
|
|
71
|
+
await sleep(1000, requestSignal);
|
|
69
72
|
const result = await this.evaluate("window.__mab_doubao_result");
|
|
70
73
|
if (typeof result !== "string")
|
|
71
74
|
continue;
|
|
@@ -95,6 +98,8 @@ function buildInjection(body) {
|
|
|
95
98
|
const url = "https://www.doubao.com/chat/completion?aid=582478&channel=win&chromium_version=147.0.7727.149&client_platform=pc_client&device_id=2750296938275162&device_platform=web&doubao_device_platform=desktop&doubao_pc_version=2.30.4&fp=verify_2750296938275162&language=zh&pc_version=2.30.4&pkg_type=release_version&real_aid=582478®ion=CN&runtime=web&runtime_version=3.37.7&samantha_web=1&sys_region=CN&tea_uuid=2750296938275162&tz_name=Asia%2FShanghai&use-olympus-account=1&version_code=20800&web_platform=desktop";
|
|
96
99
|
return String.raw `(() => {
|
|
97
100
|
window.__mab_doubao_result = null;
|
|
101
|
+
window.__mab_doubao_controller?.abort();
|
|
102
|
+
window.__mab_doubao_controller = new AbortController();
|
|
98
103
|
const merge = (a, b) => { if (!b || a.endsWith(b)) return a; const n = Math.min(a.length, b.length); let k = 0; for (let i = 1; i <= n; i++) if (a.endsWith(b.slice(0, i))) k = i; return a + (k >= 4 ? b.slice(k) : b); };
|
|
99
104
|
(async () => {
|
|
100
105
|
try {
|
|
@@ -103,11 +108,11 @@ function buildInjection(body) {
|
|
|
103
108
|
webId ||= '7689385646880933426'; tabId ||= '98f6fc8e-a1f7-4585-aa08-89b9a10aa451';
|
|
104
109
|
let requestUrl = ${JSON.stringify(url)} + '&web_id=' + encodeURIComponent(webId) + '&web_tab_id=' + encodeURIComponent(tabId);
|
|
105
110
|
for (const entry of performance.getEntriesByType('resource')) if (entry.name.includes('/chat/completion?')) requestUrl = entry.name;
|
|
106
|
-
const response = await fetch(requestUrl, { method: 'POST', headers: { 'content-type': 'application/json' }, credentials: 'include', body: ${JSON.stringify(body)} });
|
|
111
|
+
const response = await fetch(requestUrl, { method: 'POST', headers: { 'content-type': 'application/json' }, credentials: 'include', body: ${JSON.stringify(body)}, signal: window.__mab_doubao_controller.signal });
|
|
107
112
|
if (!response.ok) throw new Error('HTTP ' + response.status);
|
|
108
113
|
const reader = response.body?.getReader(); if (!reader) throw new Error('响应无 body');
|
|
109
114
|
const decoder = new TextDecoder(); let buffer = '', reply = '', events = [];
|
|
110
|
-
const consume = (block) => { const event = (block.match(/^event:\s*(.+)$/m) || [])[1] || ''; const data = (block.match(/^data:\s*(.+)$/m) || [])[1] || ''; if (event) events.push(event); if (event === 'STREAM_ERROR') throw new Error(
|
|
115
|
+
const consume = (block) => { const event = (block.match(/^event:\s*(.+)$/m) || [])[1] || ''; const data = (block.match(/^data:\s*(.+)$/m) || [])[1] || ''; if (event) events.push(event); if (event === 'STREAM_ERROR') throw new Error((${doubaoStreamErrorMessage.toString()})(data)); if (!data) return; try { const json = JSON.parse(data); if (event === 'STREAM_CHUNK') for (const patch of (json.patch_op || [])) reply = merge(reply, patch.patch_value?.tts_content || ''); if (event === 'CHUNK_DELTA' && typeof json.text === 'string') reply = merge(reply, json.text); if (event === 'STREAM_MSG_NOTIFY' || event === 'FULL_MSG_NOTIFY') for (const text of (${exports.responseTextScript})(json)) reply = merge(reply, text); if (event === 'SSE_REPLY_END') reply = merge(reply, json.msg_finish_attr?.brief || json.data?.msg_finish_attr?.brief || ''); } catch {} };
|
|
111
116
|
for (;;) { const item = await reader.read(); if (item.done) break; buffer += decoder.decode(item.value, { stream: true }).replace(/\r\n/g, '\n'); let index; while ((index = buffer.indexOf('\n\n')) >= 0) { consume(buffer.slice(0, index)); buffer = buffer.slice(index + 2); } }
|
|
112
117
|
if (buffer.trim()) consume(buffer);
|
|
113
118
|
window.__mab_doubao_result = JSON.stringify({ ok: Boolean(reply.trim()), reply, reason: reply.trim() ? '' : 'SSE 未提取到文本; events=' + events.slice(0, 20).join(',') });
|
package/package.json
CHANGED