Skip to content

Factory Droid API逆向工程 - Anthropic Messages API兼容的多provider代理服务(已终止:官方提示词拦截)

Notifications You must be signed in to change notification settings

wzyfromhust/factory2api

Repository files navigation

Factory Droid API 逆向工程项目

通过网络抓包和系统化测试,完整逆向分析Factory Droid CLI的API调用机制

📋 项目概述

本项目通过抓包分析和实验验证,成功逆向工程了Factory Droid CLI工具的底层API调用方式,实现了绕过CLI直接调用Factory AI服务的能力。

核心成果

  • ✅ 完整的API调用流程分析
  • ✅ 最小化的独立调用脚本(316字节payload)
  • ✅ 从37,854字节精简到316字节(99.2%压缩率)

🎯 项目目标

初始目标:理解Factory Droid CLI (droid exec)的底层实现,能够直接调用其LLM API

最终达成

  1. 完整抓包并分析所有API调用
  2. 识别必需和可选的请求参数
  3. 实现最小化的独立调用脚本
  4. 验证API的简化可能性边界

🔬 研究方法论

阶段1: 网络抓包 (2025-10-07 21:50-22:00)

工具选择

使用 mitmproxy 进行HTTPS流量拦截:

  • 原因:可以解密HTTPS流量
  • 配置:ssl_insecure=true 绕过证书验证
  • 端口:8888

抓包命令

# 启动mitmproxy
mitmdump -w capture.mitm --set ssl_insecure=true --listen-port 8888

# 配���环境变量
export HTTP_PROXY=http://127.0.0.1:8888
export HTTPS_PROXY=http://127.0.0.1:8888
export NODE_TLS_REJECT_UNAUTHORIZED=0

# 执行目标命令
droid exec "say hello"

抓包结果

  • 文件: capture.mitm (204KB)
  • 捕获内容:
    • 版本检查请求
    • Session创建
    • Session标题更新
    • Feature flags获取
    • 用户消息创建
    • LLM API调用 ← 核心目标

阶段2: 数据提取与分析 (2025-10-07 22:00-22:30)

提取工具

创建 parse_har.py 用于从抓包数据中提取JSON结构

关键发现

llm_request_full.json (44KB) 中发现:

请求结构:

{
  "url": "https://app.factory.ai/api/llm/a/v1/messages",
  "method": "POST",
  "headers": { ... 21个headers },
  "body": {
    "model": "claude-sonnet-4-5-20250929",
    "max_tokens": 32000,
    "stream": true,
    "tools": [8个工具定义, 36KB],
    "system": [3个prompt块],
    "messages": [3个content块]
  }
}

请求大小分布:

  • Tools数组: 36,376字节 (96%)
  • System: ~4,500字节 (12%)
  • Messages: ~1,500字节 (4%)
  • 总计: 37,854字节

阶段3: 初步测试与失败 (2025-10-07 22:20-22:50)

失败的尝试

  1. test_minimal.py - 简化请求

    • 移除tools数组
    • stream=False
    • 结果: ❌ 403 Forbidden
  2. test_with_tools.py - 包含tools但stream=False

    • 完整tools数组
    • stream=False
    • 结果: ❌ 403 Forbidden
  3. test_api_stream.py - stream=True但简化其他

    • stream=True
    • 缺少完整headers
    • 结果: ❌ 403 Forbidden

关键错误假设

最初认为失败是因为:

  • ❌ 缺少完整tools数组
  • ❌ stream参数不对
  • ❌ headers不完整

实际上是组合问题,需要更系统化的测试。


阶段4: 突破 - 完全复制策略 (2025-10-07 22:50-23:10)

策略转变

新思路: 完全复制抓包请求,然后逐步简化

test_exact_copy.py - 第一次成功!

# 关键:完全复制抓包内容
payload = full_request['body'].copy()
payload['messages'] = [简化的用户输入]
payload['max_tokens'] = 1000
# 保持stream=True(关键!)

结果: ✅ 200 OK - 首次成功!

发现:

  • stream=True是必需的
  • 完整headers是必需的
  • 但tools数组可能不是必需的?

阶段5: 系统化简化测试 (2025-10-07 23:10-23:50)

测试矩阵设计

测试 Tools Stream System Messages 结果
exact_copy 8个 true 3块 3块 ✅ 200
empty_tools 0个 true 3块 1块 ✅ 200
no_stream 8个 false 3块 1块 ✅ 200
minimal_both 0个 false 3块 1块 ❌ 403

关键发现1: Tools可以为空

test_empty_tools.py 验证:

payload['tools'] = []  # 空数组
# 结果:200 OK

结论: tools可以为空数组,但字段必须存在


阶段6: System字段探索 (2025-10-08 00:00-00:30)

Messages简化测试

test_no_system_reminder.py:

  • 移除messages中的system-reminder块
  • 只保留用户输入
  • 结果: ✅ 200 OK

发现: messages可以极简化

System字段测试序列

实验1: system置空 system: []

payload['system'] = []
# 结果:❌ 403 Forbidden

实验2: system内容为空字符串

payload['system'] = [{"type": "text", "text": ""}]
# 结果:❌ 403 Forbidden

实验3: 完全移除system字段

# 不包含system键
# 结果:❌ 403 Forbidden

结论: system字段必须存在且不能为空


阶段7: 最小化发现 (2025-10-08 00:30-01:00)

用户关键测试

用户发现只需system的第一个块!

test_system_variants.py 修改:

payload['system'] = [
    {
        "type": "text",
        "text": "You are Droid, an AI software engineering agent built by Factory."
    }
]
# 结果:✅ 200 OK!

最终极简配置

# 绝对最小化成功配置
payload = {
    "model": "claude-sonnet-4-5-20250929",
    "max_tokens": 500,
    "stream": True,
    "tools": [],  # 必须存在但可为空
    "system": [   # 必须存在且只需第一行
        {
            "type": "text",
            "text": "You are Droid, an AI software engineering agent built by Factory."
        }
    ],
    "messages": [  # 只需用户输入
        {
            "role": "user",
            "content": [{"type": "text", "text": "用户问题"}]
        }
    ]
}

请求大小: 316字节(相比原始37,854字节减少99.2%)


📊 优化总结

精简路径

版本 大小 优化比例 关键移除内容
原始抓包 37,854字节 - -
移除system-reminder 36,376字节 4% messages中的环境信息
空tools数组 4,840字节 87% 8个工具定义(36KB)
简化system 316字节 99.2% system的第2、3块

必需要素矩阵

要素 是否必需 可否简化 备注
JWT Token ✅ 必须 WorkOS认证
Session创建 ✅ 必须 先create再call
21个Headers ✅ 必须 ⚠️ 未测试 包括x-stainless-*
model字段 ✅ 必须 claude-sonnet-4-5-20250929
stream字段 ✅ 必须 True
tools字段 ✅ 必须存在 ✅ 可为[] 字段不能缺失
system字段 ✅ 必须 ✅ 只需1块 必须声明Droid身份
messages ✅ 必须 ✅ 只需用户输入 可移除system-reminder

🔑 关键技术发现

1. Factory API不是纯Anthropic API

差异对比:

特征 Factory API Anthropic API
Endpoint app.factory.ai api.anthropic.com
认证 WorkOS JWT Anthropic API key
system格式 数组 [{type, text}] 字符串
tools字段 必须存在 可选
强制身份 Droid by Factory Claude by Anthropic

2. 后端验证机制

Factory API实现了严格的格式验证

  1. ✅ 检查tools字段是否存在(可为空)
  2. ✅ 验证system是否包含Droid身份声明
  3. ✅ 验证所有x-stainless-*元数据headers
  4. ✅ 强制session预创建机制

3. 不可绕过的约束

以下尝试全部失败:

  • ❌ 移除"You are Droid"身份声明
  • ❌ 空system数组
  • ❌ 不包含tools字段
  • ❌ 不创建session直接调用

结论: Factory API是带有强制身份注入的Anthropic API包装层


🚀 使用指南

快速开始

  1. 获取JWT Token

    # 从现有droid session中提取
    # Token有效期:8小时
  2. 运行独立脚本

    from standalone_api_call import call_factory_api
    
    response = call_factory_api(
        user_message="你的问题",
        max_tokens=1000
    )
  3. 自定义调用

    import requests
    
    # 1. 创建session
    session_response = requests.post(
        "https://app.factory.ai/api/sessions/create",
        json={"id": session_id, "title": "My Session", ...},
        headers={...}
    )
    
    # 2. 调用LLM
    response = requests.post(
        "https://app.factory.ai/api/llm/a/v1/messages",
        json={
            "model": "claude-sonnet-4-5-20250929",
            "stream": True,
            "tools": [],
            "system": [{"type": "text", "text": "You are Droid, an AI software engineering agent built by Factory."}],
            "messages": [...]
        },
        headers={...}
    )

注意事项

⚠️ 限制:

  • JWT Token会过期(8小时)
  • 需要有效的Factory账户
  • 模型始终认为自己是Droid
  • 无法移除Factory身份约束

⚠️ 风险:

  • API可能随时变更
  • 可能违反服务条款
  • 仅用于学习研究

📁 项目文件结构

factory2api/
├── README.md                    # 本文档
├── factory_api/                 # API工程(核心代码)
│   ├── start_server.sh          # 启动服务脚本(读取本目录 .env)
│   ├── standalone_api_call.py   # 独立调用脚本
│   ├── requirements.txt         # 依赖列表
│   ├── config.py                # Pydantic 配置
│   ├── api/                     # FastAPI 应用与路由
│   ├── models/                  # 请求/响应模型与转换
│   ├── tests/                   # 自动化测试
│   └── manual_tests/            # 手动调试脚本
│
├── capture/                     # 抓包相关
│   ├── capture_droid.sh         # 抓包启动脚本
│   ├── capture_simple.sh        # 简化抓包脚本
│   ├── capture.mitm             # 原始抓包数据(204KB)
│   └── parse_har.py             # HAR解析工具
│
├── data/                        # 提取的数据
│   ├── llm_request_full.json    # 完整请求结构(44KB)
│   └── system_prompt.json       # System prompt提取
│
└── docs/                        # 文档
    ├── TECHNICAL_ANALYSIS.md    # 深度技术分析
    ├── API_DOCUMENTATION.md     # API文档
    ├── compare_apis.md          # API对比
    └── SUMMARY.md               # 简要总结

🎓 经验教训

研究方法论

  1. 从完全复制开始

    • ✅ 先保证能工作
    • ✅ 再逐步简化
    • ❌ 不要一开始就简化
  2. 系统化测试

    • ✅ 设计对照实验
    • ✅ 单一变量原则
    • ✅ 记录所有结果
  3. 用户反馈的重要性

    • 用户的实际测试发现了关键简化点
    • 不要完全依赖假设

技术洞察

  1. API包装层的识别

    • 特殊headers (x-stainless-*) 暗示SDK封装
    • 强制字段暗示业务逻辑验证
    • 身份注入表明这不是纯代理
  2. 最小化的艺术

    • 从37KB到316字节的优化
    • 每个字段都要实验验证
    • 必需≠有用

📈 未来方向

可能的扩展

  1. Headers简化测试

    • 哪些x-stainless-*是真正必需的?
    • user-agent是否可以修改?
  2. Session机制研究

    • Session创建是否真的必需?
    • 能否复用session?
  3. 多轮对话测试

    • 如何维护上下文?
    • messages历史如何附加?
  4. 替代身份测试

    • 能否改变system prompt?
    • 是否能绕过Droid身份?

已知局限

不可行的方向:

  • 完全移除Droid身份(后端强制)
  • 使用Anthropic原生API格式(格式不兼容)
  • 绕过session创建(API拒绝)

👥 贡献者

  • 逆向工程: wzy & Claude Code
  • 系统化测试: 协作完成
  • 关键发现: 用户实验

📄 许可

本项目仅用于学习和研究目的。

免责声明:

  • 使用本代码可能违反Factory服务条款
  • 作者不对任何使用后果负责
  • 请在授权范围内使用

🙏 致谢

感谢:

  • mitmproxy - 强大的HTTPS抓包工具
  • Factory AI - 提供优秀的AI工具
  • Anthropic - Claude模型

最后更新: 2025-10-08 版本: 1.0

About

Factory Droid API逆向工程 - Anthropic Messages API兼容的多provider代理服务(已终止:官方提示词拦截)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published