跳转至

Python MVP vs TypeScript 代理实现差异分析

1. 项目定位差异

维度 Python MVP TypeScript Proxy
定位 协议验证工具 生产级反向代理
语言 Python 3 TypeScript 5
HTTP 框架 http.server.HTTPServer Express 4
WS 库 websocket-client (thread-based) ws (async event-based)
典型部署 测试环境 python3 proxy_real.py 生产环境 node dist/server.js
启动依赖 MonkeyCodeClient 单例 AuthManager + 号池全功能

2. 服务器架构对比

Python MVP — 基于线程的 http.server

# mvp/proxy_real.py — HTTP 服务器
class RealProxyHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        # 每次请求独立线程处理
        content_length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(content_length)
        req = json.loads(body)

        if path == "/v1/chat/completions":
            self._handle_chat_completions(req)

    def _handle_stream_response(self, task_id, prompt, model, chat_id, client):
        # 线程内用回调函数处理 ACP 事件
        def on_acp_event(acp: dict):
            text = acp.get("text") or acp.get("content") or ""
            self._send_sse({
                "id": chat_id,
                "choices": [{"delta": {"content": text}}],
            })

        client.connect_task_stream(task_id, prompt,
            on_acp_event=on_acp_event,
            on_task_ended=on_task_ended)

TypeScript Proxy — 基于 Express 中间件

// proxy/src/server.ts — Express 服务器
const app = express()
app.use(cors())  // 全局 CORS
app.use(express.json({ limit: "10mb" }))  // JSON 解析中间件
app.use(createAPIRouter(modelManager, taskRunner, accountPool, conversationManager))

// proxy/src/api-routes.ts — Express 路由
router.post("/v1/chat/completions", async (req, res) => {
  try {
    const model = await modelManager.resolveModel(body.model)
    const taskId = await taskRunner.createTask(model, prompt, { systemPrompt })
    // 流式或非流式处理
    await handleStreamResponse(res, taskRunner, taskId, model, prompt, pool, auth)
  } catch (err: any) {
    if (!res.headersSent) {
      res.status(500).json({ error: { message: err.message, type: "internal_error" } })
    }
  }
})

架构差异: - Python: BaseHTTPRequestHandler 是同步的,多请求靠线程池 - TypeScript: Express 是异步事件驱动的,一个进程处理所有请求 - Python 的 send_headerend_headers 需要手动管理 - TypeScript 的 res.json() / res.status() 自动处理

3. WebSocket 连接模型对比

Python — 线程阻塞模型

# mvp/client.py — WS 连接
def connect_task_stream(self, task_id, prompt, ...):
    self._ws = websocket.WebSocketApp(ws_url, ...)

    self._ws_thread = threading.Thread(
        target=self._ws.run_forever, daemon=True
    )
    self._ws_thread.start()

    # 主线程阻塞等待完成
    done_event.wait(timeout=timeout_s)
    self.close_stream()
    return result  # 同步阻塞直到任务完成

TypeScript — 异步事件模型

// proxy/src/task-runner.ts — WS 连接
async streamTask(taskId, prompt, onChunk, signal, authOverride): Promise<void> {
  return new Promise((resolve, reject) => {
    const ws = new WebSocket(wsUrl, { headers })

    ws.on("open", () => {
      ws.send(JSON.stringify({ type: "auto-approve" }))
      ws.send(JSON.stringify({ type: "user-input", data: prompt }))
    })

    ws.on("message", (raw) => {
      this.handleStreamMessage(msg, taskId, onChunk, accumulatedUsage, ws)
    })

    ws.on("close", () => { if (!resolved) { resolved = true; resolve() } })
    ws.on("error", (err) => { if (!resolved) { resolved = true; reject(err) } })
  })
}
特性 Python TypeScript
WS 事件循环 run_forever() 在独立线程 主事件循环异步
阻塞 done_event.wait() 阻塞 await streamTask() 非阻塞
超时 wait(timeout=X) setTimeout(→resolve)
连接关闭 close_stream() 手动 ws.close() 自动
多连接 多线程管理 单线程事件驱动

4. 用户输入编码差异

Python — base64 编码用户输入

# mvp/client.py:579-591
def send_user_input(self, text: str):
    payload = json.dumps({"content": text})
    encoded = base64.b64encode(payload.encode()).decode()

    message = json.dumps({
        "type": "user_input",
        "data": {"content": encoded},
    })
    self._ws.send(message)

TypeScript — 直接发送纯文本

// proxy/src/task-runner.ts:139-143
const userMsg = {
  type: "user-input",
  data: prompt,  // 纯文本,无 base64 编码
}
ws.send(JSON.stringify(userMsg))

重要差异: Python MVP 使用了 base64 编码的 user_input(带下划线!)消息格式,而 TypeScript 代理使用纯文本的 user-input(连字符!)格式。这可能是后端 API 版本兼容问题。

5. 模型管理对比

Python — 每次请求重新获取

# mvp/proxy_real.py:81-103
def fetch_models(client):
    """全局缓存"""
    now = time.time()
    if ProxyState.models_cache and (now - ProxyState.models_cache_time) < 300:
        return ProxyState.models_cache
    # ... 刷新缓存

TypeScript — 按需刷新 + 6 层回退

// proxy/src/models.ts:64-90
async resolveModel(openaiModelId: string): Promise<MonkeyCodeModel | null> {
  const models = await this.fetchModels()  // 自动管理 5 分钟缓存

  // 6 层回退解析
  const exact = models.find((m) => this.toOpenAIModelId(m) === openaiModelId)
  if (exact) return exact                    // 1. 精确匹配
  const byProviderModel = models.find(...)   // 2. provider/model
  const byModelName = models.find(...)       // 3. model 名称
  const byDisplayName = models.find(...)     // 4. display_name
  const defaultModel = models.find(...)      // 5. 默认模型
  return models[0] || null                   // 6. 最后回退
}

6. ACP 事件处理对比

Python — 回调函数模型

# mvp/proxy_real.py:558-650
def on_acp_event(acp: dict):
    acp_type = acp.get("type", "")

    if acp_type in ("agent_message_chunk", "agent_thought_chunk"):
        text = acp.get("text") or acp.get("content") or ""
        if text:
            # 首次文本时需要发送 output_item.added
            if not text_opened:
                self._send_sse_event("response.output_item.added", ...)
                text_opened = True

            prefix = "[Thinking] " if acp_type == "agent_thought_chunk" else ""
            self._send_sse_event("response.output_text.delta", {
                "delta": {"text": prefix + text},
            })

TypeScript — 类方法模型

// proxy/src/task-runner.ts:262-339
private handleACPEvent(acp, chatId, now, onChunk, usage): void {
  switch (acp.type) {
    case "agent_message_chunk": {
      const text = acp.text || acp.content || ""
      if (text) {
        onChunk({
          id: chatId, choices: [{ delta: { content: text } }]
        })
      }
      break
    }
    // ...
  }
}

7. 错误处理对比

场景 Python TypeScript
WS 消息解析失败 try/except json.JSONDecodeError: pass catch { }
WS 超时 done_event.wait(timeout=X) setTimeout(→resolve)
WS 连接错误 on_error → done_event.set() ws.on("error") → reject
HTTP 500 self._send_json(500, ...) res.status(500).json(...)
SSE 流结束 self._send_sse({"choices": []}) sendSSE({object: "done"})
响应头已发送 未检查 if (!res.headersSent)

8. Responses API 实现差异

ACP → SSE 事件 Python MVP TypeScript Proxy
agent_message_chunk → 首次 发送 output_item.added + content_part.added 发送 output_item.added + content_part.added
agent_message_chunk → delta response.output_text.delta response.output_text.delta
tool_call function_call 输出项 function_call 输出项
tool_call_update → 完成 function_call_arguments.done + output_item.done function_call_arguments.done + output_item.done
无输出时 发送空 message 发送空 message
错误时 response.completed{status:"failed"} response.completed{status:"failed"}

完全一致的映射逻辑 — 说明 Responses API 的事件映射受到同一个协议规范的约束。

9. 各自特有的功能

Python MVP 特有

# 模型分类查询
def get_public_models(self) -> list: ...
def get_free_models(self) -> list: ...
def _is_owner_public(owner) -> bool: ...

TypeScript Proxy 特有

// 号池功能
// proxy/src/account-pool.ts
acquireHttp(): AuthManager | null {}  // HTTP 共享模式
acquireWs(): AuthManager | null {}    // WS 独占模式

// 多轮对话
// proxy/src/conversation-manager.ts
create(): Conversation {}  // 创建对话
connectToTask(): void {}   // mode=attach 复用

// 浏览器头伪装
// proxy/src/browser-headers.ts
mkHeaders()     // MonkeyCode API
bzHeaders()     // 百智云 API
navHeaders()    // 页面导航
wsHeaders()     // WebSocket

相关章节