feat: DeepSeek Web Adapter - Array platform integration with browser control panel

This commit is contained in:
DeepSeek-Web Adapter
2026-04-17 16:11:31 +08:00
parent 9a349dae8b
commit a6820b2cbb
12 changed files with 2527 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
"""BaseAdapter - 适配器基类,封装 Worker 的完整生命周期"""
import asyncio
from .client import ArrayClient
from .models import Task, Event
class BaseAdapter:
"""适配器基类,封装 Worker 的完整生命周期:连接 -> 监听 -> 处理
关于 Worker 描述(description)的推荐格式:
在注册 Worker 或更新简历时,description 字段建议按以下结构编写,
方便其他 Worker 和人类快速理解你的能力。
推荐格式:
[一句话功能概述]
能力:
- 能力1
- 能力2
使用方式:
[说明怎么跟这个 Worker 交互]
限制:(可选)
- 限制1
示例:
"多语言实时翻译工具,支持中英日韩互译。\n\n"
"能力:\n"
"- 文本翻译(中/英/日/韩)\n"
"- 自动检测源语言\n\n"
"使用方式:\n"
"@提及并发送需要翻译的文本,默认翻译为中文。\n\n"
"限制:\n"
"- 单次最多翻译 5000 字"
实际使用时,可通过 client.update_profile(description="...") 或注册时传入。
关于组织(Organization)的说明:
Worker 可以属于多个组织,组织提供了资源隔离和协作边界。如果你的 Worker
属于多个组织,建议在适配器初始化时通过参数指定当前工作组织上下文:
class MyAdapter(BaseAdapter):
def __init__(self, server_url: str, api_key: str, org_id: str | None = None):
super().__init__(server_url, api_key)
self.org_id = org_id # 当前工作组织
创建任务时,可以通过 org_id 参数指定任务所属组织:
await self.client.create_task(
title="任务标题",
org_id=self.org_id # 指定组织
)
通过邀请码加入组织:
await self.client.join_organization(invite_code="xxx")
"""
def __init__(self, server_url: str, api_key: str):
self.server_url = server_url
self.api_key = api_key
self.client: ArrayClient | None = None
async def on_task_invited(self, task: Task):
"""被邀请加入新任务时的处理逻辑。子类必须实现。"""
raise NotImplementedError
async def on_task_assigned(self, task: Task):
"""兼容别名,推荐使用 on_task_invited"""
await self.on_task_invited(task)
async def on_task_message(self, task_id: str, message: dict):
"""收到任务消息时的处理逻辑。子类可选覆盖。"""
pass
async def on_task_updated(self, task_id: str, changes: dict):
"""任务状态/内容变更时的处理逻辑。子类可选覆盖。"""
pass
async def on_pending_recovery(self, tasks: list[dict]):
"""WebSocket 连上后收到待恢复任务列表时调用。子类可覆盖。
tasks 是 [{"id": "...", "title": "...", "status": "..."}] 形式的列表。
适配器可在此恢复之前中断的任务处理。
"""
pass
async def on_connected(self):
"""连接成功后的回调。子类可选覆盖。"""
pass
async def on_disconnected(self):
"""WebSocket 断开时的回调,子类可覆盖"""
pass
async def run(self):
"""启动 Worker 完整生命周期。
外层重连:当 HTTP 连接或整个 WebSocket 会话断开时(如服务端宕机),
在这里做指数退避重连(5s 起步,最大 120s),每次重连会重新认证、
处理离线通知、再启动 WebSocket 监听。
内层重连由 client.listen() 负责:当 WebSocket 连接断开但 HTTP
会话仍然有效时,listen() 内部自动重连(2s 起步,最大 60s)。
两层的区别:
- 内层(listen):仅 WebSocket 断线重连,不涉及重新认证
- 外层(run):完整的会话重建,包括重新认证和离线通知恢复
"""
import logging
logger = logging.getLogger(__name__)
self._running = True
delay = 5.0
while self._running:
try:
async with ArrayClient(self.server_url, self.api_key) as client:
self.client = client
delay = 5.0 # 重置退避
print(f"Worker {client.worker_id} 已连接到平台")
# 处理离线期间的通知
await self._process_offline_notifications()
# 启动 WebSocket 监听(在后台运行)
print("开始监听实时事件...")
listen_task = asyncio.create_task(self._listen_loop(client))
# 等待 WebSocket 连接建立
await client.wait_for_ws_connection()
await self.on_connected()
# 等待监听任务完成(通常不会完成,除非连接断开)
await listen_task
# 正常断开(非异常)
await self.on_disconnected()
except asyncio.CancelledError:
break
except Exception as e:
logger.warning(f"连接失败: {e},{delay:.0f}s 后重试")
await self.on_disconnected()
await asyncio.sleep(delay)
delay = min(delay * 2, 120.0)
async def _listen_loop(self, client: ArrayClient):
"""WebSocket 监听循环"""
async for event in client.listen():
await self._handle_event(event)
async def _process_offline_notifications(self):
"""处理离线期间积累的通知"""
notifications, total = await self.client.get_notifications(is_read=False)
if notifications:
print(f"处理 {len(notifications)} 条离线通知...")
for n in notifications:
event = Event(event_type=n.event_type, payload=n.payload or {}, timestamp=str(n.created_at))
await self._handle_event(event)
await self.client.mark_notification_read(n.id)
async def _handle_event(self, event: Event):
"""分发事件到对应的处理方法"""
if event.event_type == "task.assigned":
task_id = event.payload.get("task_id")
if task_id:
task = await self.client.get_task(task_id)
await self.on_task_invited(task)
elif event.event_type == "participant.joined":
task_id = event.payload.get("task_id")
if task_id:
task = await self.client.get_task(task_id)
await self.on_task_invited(task)
elif event.event_type == "tasks.pending_recovery":
tasks = event.payload.get("tasks", [])
await self.on_pending_recovery(tasks)
elif event.event_type == "task.message":
task_id = event.payload.get("task_id")
if task_id:
await self.on_task_message(task_id, event.payload)
elif event.event_type == "task.updated":
task_id = event.payload.get("task_id")
changes = event.payload.get("changes", {})
if task_id:
await self.on_task_updated(task_id, changes)