1. 先复现问题:Chrome 开关灰色,Codex 调度不了浏览器
在 Codex 桌面端里准备跑 Chrome 扩展自动化任务,最大的拦路虎不是任务本身,而是那个一直灰着的「Google Chrome」开关。由于应用商店区域不可达,扩展只能靠「开发者模式 → 加载已解压的扩展程序」装进去,Chrome 随手分配一个路径派生 ID,桌面端的 Native Messaging 握手直接失败。要修这个问题得走 CRX 离线包 + manifest key 注入;修好之后,Codex 还需要一把能调模型的 Key——打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建 Key,TaoToken 会给你一个统一的兼容通道,让模型请求从 Codex 顺畅发出去。
1.1 从「扩展已加载」到「开关灰色」只差一个 ID
第一次遇到这个问题的人,大概率和我一样先怀疑扩展本身。明明chrome://extensions里能看到扩展卡片,开关也打开了,甚至浏览器里已经出现了扩展的图标,但 Codex 桌面端设置里的「Google Chrome」开关就是灰色,点不动。边栏里那些「打开网页」「读取页面内容」「在页面上执行 JS」的浏览器任务按钮,点了也没有任何反应。
问题不在扩展能否弹出,而在桌面端根本认不出这个扩展。Codex 桌面端通过 Native Messaging 和 Chrome 扩展通信。Native Messaging host 在安装时会把一条通道注册到系统里,通道名称里写死了官方扩展 ID。浏览器侧加载的扩展如果 ID 对不上,桌面端向这条通道发消息时,握手会静默失败,于是界面就停留在「扩展未安装」的状态。
1.2 这条路绕不过官方扩展 ID
所以核心结论是:手动安装的关键不是把扩展文件塞进 Chrome,而是让加载后的扩展拥有和商店版一模一样的扩展 ID。Chrome 对「有没有 key 字段」的扩展,用了完全不同的 ID 派生方式。
带 key 的扩展,Chrome 用 key 里的公钥做 SHA-256,取前 16 字节,再按 0-9a-f 到 a-p 的映射表换成 32 个字母,得到稳定的扩展 ID。不带 key 的扩展,Chrome 直接对加载路径算哈希,路径一换 ID 就变。桌面端只认前者。明白了这一层,后面的 CRX 离线包 + 公钥注入方案就顺理成章了。
2. 修复思路:用官方 CRX 的公钥把扩展 ID 固定下来
既然要得到官方 ID,最干净的办法就是拿到官方签名的 CRX 包,从里面把开发者的公钥提取出来,写进manifest.json的key字段。这样 Chrome 加载解压目录时,会按公钥重新计算 ID,计算结果正好等于官方商店版的 ID。
2.1 两种扩展 ID 派生方式对照
应用商店版扩展下载时会携带开发者的公钥信息,Chrome 安装后按公钥推导出一个固定 ID。路径派生版扩展没有key字段,Chrome 只能根据当前文件夹的绝对路径做哈希,路径一变 ID 就变。
| 加载方式 | 是否携带 key | ID 稳定性 | 桌面端能否识别 |
|---|---|---|---|
| 商店安装 | 是 | 固定不变 | 能 |
| 开发者模式加载解压目录 | 否 | 随路径变化 | 不能 |
| 注入 key 后再加载解压目录 | 是 | 固定为官方 ID | 能 |
这也解释了为什么直接把manifest.json里的name改成官方名字没用。ID 由公钥算出,和显示名称没有关系。
2.2 修复扩展 ID 只是第一步,模型通道也得接上
扩展 ID 修好之后,Codex 桌面端的开关会亮起,但这只代表 Native Messaging 握手成功。真正跑一条「打开某网页并把内容存下来」的自动化任务时,Codex 还需要把任务拆解成步骤、生成要执行的指令,这一步要调用大模型。模型请求从哪里发出去,同样需要配置。
这时可以把 Key 换成 TaoToken 的:打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册并创建 API Key,把 Codex 的 Base URL 指向 https://taotoken.net/api。TaoToken 在这个场景里充当的是「统一接入」的兼容通道,Codex 负责调度浏览器自动化任务,模型请求走这条通道发出去,两边各管一摊。
3. 准备材料:Python、Chrome SxS、TaoToken 的 API Key
动手之前把材料备齐,后面每一步才不会卡壳。以下是本文环境对应的清单,Windows 11 / Chrome SxS (Canary) / Codex 桌面端 / Python 3.13。
3.1 本机需要准备的软件清单
Python 3 用来解析 CRX3 文件、改写manifest.json。Chrome 浏览器用 SxS、Canary 或 Stable 都可以,本文以 SxS 为例,但后面会提到 Stable 在 Native Messaging 注册上更稳。Codex 桌面端需要提前安装并登录。另外下载 CRX 时,网络需要能访问到clients2.google.com这个 Google 官方扩展更新接口;如果下载失败,可以让能正常访问该接口的机器把codex.crx文件下载好再带回本机,后续解压、注入、加载这几个步骤都不需要再访问外网。
3.2 到 TaoToken 创建 API Key,分清官网和接口两个地址
准备材料里最重要的一步是拿到 API Key。打开 TaoToken,注册账号后在控制台创建 Key,创建好之后会得到一串形如sk-...的字符串,本文统一用YOUR_API_KEY占位。复制后立刻存好,关闭页面可能就看不到了。
这里要特别注意区分两个地址:在浏览器里打开官网页面、创建 Key、看模型广场,用的是https://taotoken.net/?utm_source=taotoken_aicg_blog_end;而填进 Codex 配置文件的 Base URL,是https://taotoken.net/api。后者末尾不带/v1,更不要带utm_source之类的追踪参数,那只是给人点链接用的。
4. 完整操作:从下载 CRX 到 Codex 走 TaoToken 的兼容通道
4.1 确认官方扩展 ID
先确认目标扩展的官方 ID,Codex 浏览器扩展的官方 ID 是hehggadaopoacecdllhhajmbjkdcmajg。你可以在 Codex 官方文档里找到对应链接确认,也可以直接看 Chrome 应用商店地址中/detail/后面那一段。这个 ID 稍后会在多个环节用到:下载 CRX 的请求参数、公钥算完后的校验值、以及最后chrome://extensions里核对扩展卡片。
4.2 用 curl.exe 从 Google 官方更新接口拉取 CRX
Google 的扩展更新接口是公开的,Chrome 浏览器自己更新扩展时走的就是这条通道。手动构造一个请求,就能拿到带官方签名的 CRX 安装包。Windows 11 自带curl.exe,在 PowerShell 里执行时最好写成curl.exe,避免和Invoke-WebRequest的别名冲突:
curl.exe -sSL -o codex.crx "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=154.0.8016.0&acceptformat=crx3&x=id%3Dhehggadaopoacecdllhhajmbjkdcmajg%26uc"参数说明:prodversion填你当前 Chrome 的版本号;acceptformat=crx3要求返回 CRX3 格式;x=id%3D...%26uc是扩展 ID 加uc标志。下载后可以用Format-Hex确认文件头是Cr24:
Format-Hex -Path codex.crx | Select-Object -First 1看到输出前四个字节是43 72 32 34,也就是 ASCII 的Cr24,说明文件结构正确。
4.3 用 Python 解压 CRX3
CRX3 文件的结构是:固定 12 字节文件头加上一段 protobuf 编码的 header,最后面跟着一个标准 ZIP 包。先写一个解析函数把 ZIP payload 取出来:
import struct import zipfile import io from pathlib import Path def unpack_crx_payload(crx_path: str, out_dir: str): data = Path(crx_path).read_bytes() magic, version, header_len = struct.unpack("<4sII", data[:12]) assert magic == b"Cr24", "不是预期 CRX 文件" assert version == 3, "当前只处理 CRX3 格式" zip_start = 12 + header_len with zipfile.ZipFile(io.BytesIO(data[zip_start:])) as zf: zf.extractall(out_dir) print("解压完成,输出目录:", out_dir) unpack_crx_payload("codex.crx", "codex-chrome-extension")这段代码只做解压,不影响原文件。执行后会在当前目录生成codex-chrome-extension文件夹,里面就是完整的扩展源码。
4.4 提取公钥并注入 manifest.json 的 key 字段
这是整个修复流程的关键一步。CRX3 的 header 里有一段 protobuf 结构,其中包含开发者的SubjectPublicKeyInfo(DER 格式)。要把这段二进制公钥提取出来,base64 编码后写进manifest.json的key字段,Chrome 就会据此重新计算扩展 ID。
import base64 import hashlib import json import struct from pathlib import Path EXPECTED_ID = "hehggadaopoacecdllhhajmbjkdcmajg" def read_varint(buf: bytes, pos: int): result = 0 shift = 0 while True: byte = buf[pos] result |= (byte & 0x7F) << shift pos += 1 if not (byte & 0x80): break shift += 7 return result, pos def walk_fields(buf: bytes): fields = {} pos = 0 end = len(buf) while pos < end: tag, pos = read_varint(buf, pos) field_no = tag >> 3 wire = tag & 0x7 if wire == 2: length, pos = read_varint(buf, pos) fields[field_no] = buf[pos:pos + length] pos += length elif wire == 0: _, pos = read_varint(buf, pos) elif wire == 1: pos += 8 elif wire == 5: pos += 4 else: raise ValueError(f"不支持的 wire type: {wire}") return fields def compute_id_from_pubkey(pubkey_der: bytes) -> str: digest = hashlib.sha256(pubkey_der).hexdigest()[:32] table = str.maketrans("0123456789abcdef", "abcdefghijklmnop") return digest.translate(table) data = Path("codex.crx").read_bytes() header_len = struct.unpack("<I", data[8:12])[0] header = data[12:12 + header_len] top_fields = walk_fields(header) proof = top_fields[2] proof_fields = walk_fields(proof) pubkey_der = proof_fields[1] computed_id = compute_id_from_pubkey(pubkey_der) assert computed_id == EXPECTED_ID, f"公钥算出的 ID 是 {computed_id},与预期不符" print("扩展 ID 校验通过:", computed_id) manifest_path = Path("codex-chrome-extension/manifest.json") manifest = json.loads(manifest_path.read_text(encoding="utf-8")) manifest = { "update_url": manifest.pop("update_url", "https://clients2.google.com/service/update2/crx"), "key": base64.b64encode(pubkey_der).decode("ascii"), **manifest, } manifest_path.write_text( json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8", ) print("manifest.json 已注入 key")脚本最后一步做了断言:如果公钥算出来的 ID 和官方 ID 不一致,直接报错,避免把错误的扩展加载进浏览器。执行成功后,打开manifest.json能看到顶部多了key字段,值是一长串MIIBIjAN...开头的 base64 字符串。
4.5 在 Chrome 里侧载扩展并核对 ID
打开 Chrome,地址栏输入chrome://extensions,右上角开启「开发者模式」,点击「加载已解压的扩展程序」,选择codex-chrome-extension文件夹。加载后扩展卡片上会显示 ID,这时要核对它是不是hehggadaopoacecdllhhajmbjkdcmajg。
如果 ID 不对,最大的嫌疑是key字段没写对。移除扩展,重新执行 4.4 步骤,确认公钥提取无误后再加载。ID 正确后,先不要急着跑任务,重启一次 Codex 桌面端,让 Native Messaging 通道重新初始化。
4.6 重启 Codex 后,把模型请求指向 TaoToken 的 Base URL
扩展侧修好,桌面端的「Google Chrome」开关应该已经亮了。此时 Codex 已经能操控浏览器,但它还需要一个模型后端来解析你的指令、编排自动化步骤。打开 Codex 的配置文件~/.codex/config.toml,把模型提供方切成 TaoToken:
# ~/.codex/config.toml model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"然后在系统环境变量里配置 API Key。PowerShell 里运行(临时生效):
$env:TAOTOKEN_API_KEY = "YOUR_API_KEY"想永久写入用户环境变量,用setx:
setx TAOTOKEN_API_KEY "YOUR_API_KEY"设置完环境变量后,完全退出并重启 Codex 桌面端。模型 ID 不要凭空猜,打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的模型广场看当前列表,选一个对应模型 ID 填进 Codex 的model配置。base_url不要加/v1,也不要填官网首页地址,两处用途不同。
4.7 发一条浏览器自动化任务做冒烟测试
配置全部就位后,在 Codex 里发一条最简单的浏览器任务,例如:
打开 https://example.com ,把页面标题写到 D:\codex-browser-test.txt正常路径下,Codex 会先调用模型理解任务,再通过 Native Messaging 指挥扩展打开指定网页,读取页面标题,最后在本地生成文件。观察两个位置:桌面端设置里的「Google Chrome」开关是否保持开启;边栏里的浏览器任务按钮是否从灰色变成可点击。两者都正常,就说明扩展 ID 修复和模型通道配置都成功了。
5. 踩坑对照:这几处最容易翻车
5.1 扩展 ID 明明对了,开关依然灰色
最常见的原因是 Native Messaging host 没有重新注册。修改扩展后,桌面端和浏览器之间旧握手状态可能还残留在内存里。Solution:关掉所有 Chrome 窗口,托盘里也完全退出 Codex,再重新打开两侧。另一个可能性是桌面端只在 Stable Chrome 的注册表路径下注册了 Native Messaging host,SxS 里扩展 ID 正确但通道不在注册表内,这时需要在 Stable Chrome 里也加载一次同一份解压目录。
5.2 SxS 能加载,Stable 反而没反应
部分桌面端对浏览器的检测只写死在 Stable 版 Chrome 的注册表位置。SxS 或 Canary 虽然能手动加载扩展,但 Native Messaging host 配置里没有对应的浏览器路径,握手仍会失败。最省事的做法是两个浏览器都加载一次解压目录,哪一个开关亮了就用哪一个。
5.3 开发者模式加载的扩展不会自动更新
通过 Load unpacked 加载的扩展不会走应用商店的自动更新通道。官方发布新版本后,你需要重新下载 CRX、重新解压、重新注入 key,然后在chrome://extensions里点「刷新」按钮。建议把原始codex.crx文件留档,每次更新时重复 4.2 到 4.4 的步骤即可。
5.4 模型请求报 401 或 404
Codex 配置好 TaoToken 后,如果任务在第一步就报401 Unauthorized,多半是TAOTOKEN_API_KEY环境变量没生效,或者 Key 复制时少了字符。如果报错接近路由不存在,检查base_url:正确的写法是https://taotoken.net/api,末尾没有/v1。模型 ID 也要和模型广场上的标识完全一致,别用文章里看到的过期名字。
6. 验证清单:从扩展 ID 到模型账单逐条对照
6.1 chrome://extensions 逐项核对
打开chrome://extensions,确认三件事:扩展 ID 是hehggadaopoacecdllhhajmbjkdcmajg;扩展版本和官方版本一致;「已加载」状态正常。任何一项不符合,回到第 4 步重新走一遍。
6.2 在 Codex 里跑通一条完整任务
冒烟测试的任务越简单越好,能覆盖「打开页面、读取内容、写文件」三个动作就可以。跑通后可以尝试稍微复杂的任务,比如打开一个带表单的页面、填写输入框、点击提交按钮,确认扩展的自动化能力是完整的。
6.3 回 TaoToken 控制台对一下这次调用
配置保存后,先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息,确认模型 ID 和 Base URL 没填错。若要长期跑浏览器自动化任务,可以打开 Coding Plan 看套餐是否够用;Key 的创建和管理在 控制台 API Keys 里完成。跑完几轮任务后回控制台看用量记录,能查到对应调用,才算整条链路闭环。