sideeffffect/m365_openai_proxy.py

GitHub: sideeffffect/m365_openai_proxy.py

一个零第三方依赖的单文件 Python 脚本,将 M365 Copilot 后端桥接为 OpenAI 兼容的本地 HTTP API。

Stars: 0 | Forks: 0

# m365_openai_proxy.py 一个单一、自包含、仅依赖标准库的 Python 3 脚本,它提供了一个由 `https://m365.cloud.microsoft` 的 Copilot 聊天后端支持的、兼容 OpenAI 的 HTTP API(`/v1/chat/completions`、`/v1/models`)。 **完全自包含。** 整个项目就是这一个文件,`m365_openai_proxy.py`。它仅使用 Python 3 标准库——不需要为任何功能进行任何 `pip install`。这包括 MSAL 加密缓存的解密路径,该路径需要 AES-256-GCM:脚本在纯 Python 中自行实现了它(已通过 FIPS-197 AES-256 测试向量验证,并在开发期间与参考实现进行了交叉检查,最终实现了无依赖发布)。将这一个文件放到任何带有 Python 3 解释器的机器上并运行它——无需安装任何其他东西。 **HTTP API 的调用者不需要 Authorization 头或 API key。** 所有 Microsoft 身份验证均由代理在内部处理,并在启动时根据脚本旁边的四个纯文本凭据文件进行一次性配置(见下文)。每个文件只是一个 `#` 注释头,解释了它是什么以及在浏览器的什么位置(Local Storage / Cookies / Network 标签页)获取它,然后是在底部粘贴的原始值——无需 JSON 转义,即使其中两个值本身就是直接从 DevTools 复制出来的 JSON 代码片段。 这四个文件分别命名为 `m365_openai_proxy..conf`: - `m365_openai_proxy.refresh_token.conf` - `m365_openai_proxy.encrypted_refresh_token.conf` - `m365_openai_proxy.cache_encryption_key.conf` - `m365_openai_proxy.local_storage_key.conf` 实际上只需要填写以下两种组合之一(两者均已验证可用): - 仅 `refresh_token`(一个明文 refresh token),或 - `encrypted_refresh_token` + `cache_encryption_key` + `local_storage_key` 三者一起(MSAL Browser v4+ 使用 HKDF 派生的 AES-GCM 对其缓存条目进行加密,这是从 MSAL 自己的源码中逆向工程出来的,并在 `_decrypt_msal_cache_entry()` 中实现)。 **日志记录。** 每次运行都会在脚本旁边写入一份详细日志到 `m365_openai_proxy.log`。在正常运行期间,这仅限于文件记录——启动/关闭、使用了哪个凭据文件、每次 token 交换和 Chathub 连接、每个传入的 HTTP 请求,以及每轮对话的结果。Secret、token 和密码永远不会被写入其中——只有长度、ID 和其他非敏感的元数据。控制台保持静默,除非在代理确实无法继续运行的情况下(启动失败或完全崩溃停止)——此时它会打印一条简短的非技术性提示消息,告诉你将 `m365_openai_proxy.log` 发送给该程序的支持人员,因为该文件包含了诊断问题所需的一切信息。有关具体记录的内容,请参阅脚本模块 docstring 中的 LOGGING 部分。 有关以下内容,请参阅 `m365_openai_proxy.py` 模块 docstring 的顶部: - 逐字段的完整凭据文件格式以及确切的获取每个值的方法; - 完整的 HKDF+AES-GCM 解密算法,使用纯 Python 从头实现; - `exchange_refresh_token()` 发送的准确请求结构(特定于租户的 token endpoint、MSAL 身份/遥测字段)以及原因——事实证明精确匹配这一点很重要:早期省略了这些内容的修订版会导致原本有效的 token 被 Entra ID 拒绝,就好像它们已经过期了,但实际并没有(完整的说明请参阅 REVERSE_ENGINEERING.md); - 完整的身份验证模型,以及为什么绑定地址默认为 `127.0.0.1`。 ## 已知限制 - **没有多轮对话记忆。** 仅发送 `messages` 中的最后一条 `"user"` 角色消息;每次调用都会开启一个全新的 Sydney 对话。之前的对话轮次和系统提示词会被静默丢弃。 - **不支持工具/函数调用。** Sydney 确实有一个真正的基于 MCP 的工具调用机制,这是从 officeweb 客户端逆向工程出来的,并记录在 `REVERSE_ENGINEERING.md` 的“Local MCP tool-calling bridge”部分——但它**并未在此实现**。将其桥接到 OpenAI 的工具调用规范遇到了真正的架构不匹配问题(Sydney 的调用是同步的且发生在对话中途;而 OpenAI 工具调用是在不同的 HTTP 请求之间异步进行的)。如果你打算将其用于像 OpenCode 这样需要通过工具调用编辑文件/运行命令的智能编程客户端,请假定这无法实现。 - 每次响应中的 `usage`(token 计数)始终为零——未实现 token 计数功能。 - 每个 HTTP 请求都会打开并关闭一个 Chathub WebSocket——没有连接池或重用。 - Sydney 自身针对单个对话的速率限制未进行特殊处理或展示;如果你遇到了配额限制,Sydney 返回的任何内容都将按原样透传。 - 凭据值(尤其是从 Network 标签页获取的明文 refresh token)可能会在你完成粘贴之前,因 MSAL 在后台轮换它们而失效——在时间上尽量紧凑地捕获凭据文件是一个好习惯,尽管这是一个次要风险,而不是曾经认为的主要失败模式(参见 REVERSE_ENGINEERING.md)。 ## 快速开始 ``` # 1. 为所有四个凭证文件编写起始模板(每个文件仅包含一个 # 注释头,说明要粘贴什么以及从何处获取): python3 m365_openai_proxy.py --init-credentials # 2. 打开 m365_openai_proxy.refresh_token.conf 并在注释块下方粘贴你的 refresh # token —— 或者,如果不使用此方式而是使用 encrypted-cache # 选项,则以相同方式填写 m365_openai_proxy.encrypted_refresh_token.conf # + .cache_encryption_key.conf + .local_storage_key.conf。 # 每个文件自己的注释确切说明了其值的获取来源。 # 3. 运行它(在启动时会针对 Entra ID 验证凭证,并 # 开始记录到 m365_openai_proxy.log): python3 m365_openai_proxy.py --port 8000 # 4. 像对待任何兼容 OpenAI 的 endpoint 一样与它交互 —— 无需 auth header: curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model": "m365-copilot", "messages": [{"role": "user", "content": "hello"}]}' ``` 代理在每次与 Entra 进行 token 交换后,都会使用 Entra 新轮换的 refresh token 覆盖 `m365_openai_proxy.refresh_token.conf` 文件(Entra 会在每次兑换时使前一个 token 失效)——其他三个文件保持不变。 运行 `python3 m365_openai_proxy.py --help` 查看所有标志,包括 `--host`/`--port`、`--credentials-prefix`、`--log-file`/`--log-level`。 有关此实现所基于的完整协议逆向工程说明,请参阅 `REVERSE_ENGINEERING.md`(Chathub/SignalR 通信格式、FOCI token 系列身份验证链、MSAL 缓存加密算法等)。 ## 环境要求 仅需 Python 3 标准库,无需第三方包。基于 Python 3.12 进行开发和测试;虽然未刻意使用特定版本的标准库特性,但目前仅验证过 3.12 版本。 ## 许可证 Apache License 2.0 — 见 [`LICENSE`](LICENSE)。
标签:API代理, Microsoft Copilot, OpenAI兼容, Petitpotam, Python标准库, StruQ, 云资产清单, 人工智能, 用户模式Hook绕过, 认证绕过, 逆向工程