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
- function responseText(event, value) {
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() { this.socket?.close(); this.socket = undefined; for (const pending of this.pending.values())
63
- pending.reject(new Error("豆包 CDP 已关闭")); this.pending.clear(); }
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, signal);
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&region=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('STREAM_ERROR ' + data.slice(0, 500)); 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); for (const text of ${responseText.toString()}(event, 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 {} };
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(',') });
@@ -6,7 +6,7 @@ const plugin_1 = require("../../plugin");
6
6
  const desktop_launch_1 = require("../desktop-launch");
7
7
  exports.doubaoManifest = {
8
8
  id: "doubao",
9
- version: "1.0.7",
9
+ version: "1.0.9",
10
10
  displayName: "豆包",
11
11
  entry: "dist/index.js",
12
12
  apiVersion: "1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-doubao",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "agent-bridge 的豆包插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "doubao",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "type": "agent",
5
5
  "displayName": "豆包",
6
6
  "entry": "dist/src-ts/plugins/doubao/index.js",