dsh-connect-gemini
把本机已登录的 Gemini 桌面客户端接入 DeepSeek Harness 的 DSH 插件:无需 API Key,复用本机 Gemini 的登录态作为 DSH 的模型提供方,并提供常驻用量挂件与连通性检测。
- 提供方 id:
gemini-web - 模型(默认第一个 = Flash-Lite):
gemini-flash-lite(3.5 Flash-Lite,最快最稳)、gemini-web(3.8 Flash)、gemini-pro(3.1 Pro)、gemini-argon(4 Argon,需账号有 Argon 权益,否则服务端静默降级)—— 全部文本输入、1M 上下文,支持 DSH 的思考等级选择(低 / 中 / 高) - 插件类型:
modelprovider


特性
| 能力 | 说明 |
|---|---|
| 零配置接入 | 自动定位 Gemini 桌面客户端数据目录,解密其中的登录 Cookie,无需 API Key / OAuth 授权 |
| 代理可选 | 设置页填 http://127.0.0.1:7897 之类即走代理;留空则强制直连(不读取 HTTP_PROXY 等环境变量) |
| 用量挂件 | 页面右下角常驻挂件,每次刷新页面自动读取模型用量与额度刷新倒计时;可拖动到任意位置并记住位置,双击复位 |
| 额度刷新时间 | 展示两个额度池(5 小时滚动 / 每周)的百分比、重置时刻与剩余时间 |
| 连通性检测 | 设置页一键检测,四步逐项报告:数据目录 → Cookie 解密 → 登录态 → 用量接口 |
| 模型与思考等级 | 四个模型可选,默认第一个(Flash-Lite,最快最稳);思考等级按模型各自动映射(4 Argon 的中档与服务端原生值不同)。响应回显的真实模型名会显示在设置页,用来发现服务端静默降级 |
| 完全本机 | 上游请求全部由 DSH 主进程发出;loopback 代理仅监听 127.0.0.1 且用进程内随机令牌鉴权 |
前置条件
- 已安装 Gemini 桌面客户端并已登录(gemini.google.com 的官方桌面版)。
- DSH 版本满足
>=0.2.0-rc.1(package.json的engines.dsh与dsh.compatibility已声明)。 - Node
^22.19.0 || >=24.0.0(DSH 自带运行时即可)。
插件读取的是 Gemini 桌面客户端的 Chromium 数据目录,不是
geminiCLI、也不是浏览器目录:
- Windows:
%APPDATA%\gemini- macOS:
~/Library/Application Support/gemini- Linux:
~/.config/gemini也可以用设置项
userDataDir手动指定其他 Chromium 用户目录(需含Network/Cookies与Local State)。
安装
从本地目录安装
dsh plugin --profile desktop add E:\DSH-WORK\dsh-connect-gemini
安装后重启 DSH(或在「插件」页重载),打开 设置 → Gemini (本机登录) 确认已出现该区块。
从市场安装
在 DSH 的插件市场搜索 dsh-connect-gemini 安装即可。
使用
- 打开 设置 → Gemini (本机登录)。
- 点击 检测连通性,应看到四步全绿:
- 定位 Gemini 客户端数据
- 解密登录 Cookie
- 访问 gemini.google.com
- 读取用量与额度
- 需要代理时在 代理地址 填入
http://127.0.0.1:7897(保存后重新检测)。留空 = 直连。 - 在 DSH 的模型选择器里选 Gemini (本机登录) 即可对话。
- 页面右下角挂件显示用量百分比;点击展开可看各额度池的重置时间与倒计时。 挂件可拖到页面任意位置(位置记在浏览器本地,刷新后仍在),双击胶囊复位到右下角。
配置项
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
是否把 Gemini 注册为可用提供方 |
proxyUrl |
"" |
HTTP 代理地址;空 = 直连(显式忽略环境变量代理) |
userDataDir |
"" |
Gemini 客户端数据目录;空 = 自动探测 |
cookieDbPath |
"" |
手工指定的 Cookies 文件;空 = 用数据目录里的那份 |
language |
"en" |
发给 Gemini Web 的 hl 参数 |
requestTimeoutMs |
60000 |
普通请求(页面 / 用量)超时 |
streamTimeoutMs |
900000 |
流式对话的静默上限:上游连续这么久一个字节都不发才会中断 |
connectTimeoutMs |
15000 |
建立连接的超时;系统默认要等约 21 秒才放弃 |
maxOutputTokens |
32768 |
向 DSH 声明的输出上限 |
contextWindow |
1048576 |
向 DSH 声明的上下文窗口 |
usageCacheTtlMs |
60000 |
用量缓存时长;0 = 每次刷新都重新读取 |
showWidget |
true |
是否显示网页挂件 |
widgetRefreshMs |
0 |
挂件定时刷新间隔;0 = 仅页面刷新时读取一次 |
工作原理
DSH ──pi-messages──▶ 127.0.0.1:<随机端口> (loopback shim, 进程内随机令牌)
│
├─ 读取并解密 Gemini 客户端 Cookie(AES-256-GCM + DPAPI)
├─ GET gemini.google.com/app ← 取 at / bl / f.sid 令牌
├─ POST .../StreamGenerate ← 流式对话,逐块翻译成 pi-messages 事件
└─ GET gemini.google.com/usage ← 解析 bg3-relay preload,取用量与刷新时间
│
▼
可选 HTTP 代理(CONNECT 隧道)
关键点:
- 真实 Cookie 不交给 pi-ai。pi-ai 只拿到一个进程内随机令牌,所有带凭据的请求都在 shim 里完成 —— 与
dsh-connect-minimax/dsh-connect-workbuddy的安全模型一致。 - pi-ai 要求必须提供 API Key,因此 shim 用一个只在本进程内有效、随机生成、loopback 校验的令牌充当
apiKey。 - Gemini Web 的流式响应文本是累积快照而非增量,shim 侧自行 diff 出增量再推送
text_delta。 - 心跳必须产出 chunk,宿主才续命。宿主用
idleWatchdog逐条next()拉 SSE 事件,只有迭代器真的吐出一个 chunk 时才重置空闲计时器,所以 SSE 注释(: ping)救不了场;start事件也救不了——pi-ai 与宿主侧对它都是case "start": break;,不产出任何 chunk。shim 最终用空的text_delta(delta: "")做心跳:它会产出 chunk(续期),而内容为空所以isVisibleChunk判定为不可见,用户完全看不到。实测长上下文下 3.8 Flash 首字节可能要 6 分钟,没有心跳就必然被判定为空闲超时。 - 上游「200 但一个字都不给」(无权益模型的表现)会被转成明确的
EMPTY_UPSTREAM_ANSWER错误,而不是一条空回答。 - 上游把代码执行工具的内部引用块(
```python?code_reference&...)写进正文时会被增量清洗掉,用户自己写的代码围栏不受影响。 - 响应头需要
maxHeaderSize调到 1 MB(Gemini 的alt-svc头远超 Node 默认的 16 KB)。 - 走代理时必须显式写回不带默认端口的
Host头,否则 Google 判定主机不匹配会静默丢弃登录 Cookie(返回 200 的未登录页面)。 - shim 监听的端口会避开
fetch规范的禁用端口(undici 会在建连前就拒绝,报bad port)。
本机 API
插件在 DSH 的本地服务上暴露三个只读/诊断端点(仅接受回环地址):
| 端点 | 用途 |
|---|---|
GET /plugins/dsh-connect-gemini/api/status |
插件是否启用、当前代理、已注册模型、最近一次实际使用的上游模型 |
GET /plugins/dsh-connect-gemini/api/usage?refresh=1 |
用量百分比与各额度池的刷新时间 |
POST /plugins/dsh-connect-gemini/api/connectivity |
逐项连通性检测(目录 → Cookie → 登录态 → 代理 → 网络) |
GET /plugins/dsh-connect-gemini/api/diag |
最近 40 次对话请求的形状与结果:模型、思考等级、提示字符数、首字节耗时、总耗时、正文长度、上游回显模型、错误码 |
/diag 是排查「模型没反应」的自证手段:首字节耗时能区分上游慢,正文长度与错误码能区分上游拒绝与插件自身问题。
故障排查
| 现象 | 原因与处理 |
|---|---|
检测在第 2 步失败 COOKIES_NOT_FOUND |
没找到 Gemini 客户端数据目录;确认客户端已安装并登录过,或用 userDataDir 指定 |
检测在第 2 步失败 COOKIES_LOCKED |
Gemini 客户端正在运行,Windows 不让别的进程读它的 Cookies 库(独占锁)。完全退出客户端(含托盘)后点一次「检测连通性」,插件就会留下加密快照,之后客户端开着也能用;或在设置里用 cookieDbPath 指向你自己复制的 Cookies 文件 |
检测在第 2 步失败 APPE_BOUND_ENCRYPTION |
客户端启用了 App-Bound 加密(v20 前缀),当前版本暂不支持;请在客户端里关闭该选项或等待更新 |
检测在第 3 步失败 NOT_LOGGED_IN |
Cookie 已过期,打开 Gemini 桌面客户端重新登录 |
| 检测在第 3 步失败且提示「连接超时」 | 直连模式下本机网络无法连接 Google(常见于 DNS 污染)。在设置里填写可用代理后重试;这是本机网络问题,不是插件缺陷 |
| 设置代理后仍是未登录 | 代理本身不可用或未做 CONNECT 透传;先用 curl -x http://127.0.0.1:7897 https://gemini.google.com 验证代理 |
模型列表里没有 gemini-web |
检查「插件」页该插件是否被兼容性门禁跳过(stderr 会打印 skippedBundles) |
选了 4 Argon 但设置页显示「最近一次实际使用:3.8 Flash」 |
账号没有 4 Argon 权益。Gemini 网页端对这种请求不报错,而是静默换成更弱的模型。用浏览器打开 gemini.google.com 看模型菜单里有没有「4 Argon」:没有就是这个原因,与插件无关;有的话请反馈 |
| 回答被重复输出了两三遍 | 旧版本的已知缺陷(Gemini 对部分问题会返回多个 candidate,每个都带一份完整快照,被当成增量重复拼接)。升级到 0.1.1 即修复 |
| 设置页改完代理/路径后没有「已保存」提示 | 旧版本在 configForms 的 namespace 懒加载完成前拿不到可写 scope,保存被静默跳过。0.1.1 起会订阅 namespace 变化并自动重绑;若看到「设置未就绪,请重新打开本页」,把设置页关掉再打开即可 |
用 3.8 Flash / 3.1 Pro 时「思考很久」甚至一直转圈 |
两种可能,都是上游行为,不是插件问题:① 账号被限流,见下一行;② Gemini 对这类问题先触发代码执行工具(原始帧里能看到 data_analysis_tool),等它跑完才吐正文。loopback 侧每 5 秒补发一条空的 text_delta 做心跳(宿主只在拿到 chunk 时才续期,SSE 注释和 start 事件都不算数),所以长时间等待不会再被误判成空闲超时;嫌慢就换成默认的 Flash-Lite(实测稳定 2~4s,且不计入额度消耗) |
| 选某个模型后报错「Gemini 返回了空回答」 | 该模型在当前账号下没有权益。上游会先假装成功(HTTP 200),再静默地一个字都不给。改用 Flash-Lite 或 3.1 Pro;需要该模型的话先在 gemini.google.com 确认账号能用 |
回答里出现 ```python?code_reference&... 这类奇怪代码块 |
上游把代码执行工具的内部引用写进了正文。0.1.4 起会被自动清洗;若仍看到请反馈 |
| 请求发出后马上失败,提示「上游错误码 1095」 | 账号在短时间内请求过多被 Gemini 限流。触发条件与 Google 官方说明一致(连续发送、频繁 regenerate、频繁切换模型)。等几分钟到一小时会自行恢复,其间改用 Flash-Lite 或把思考等级调低即可立刻缓解;额度用量看右下角挂件 |
关于「直连」:本插件在
proxyUrl为空时真正直连,不会读取HTTP_PROXY/HTTPS_PROXY。 这点和很多工具相反,是有意为之 —— DSH 宿主自身会带着这些变量,静默继承会让「我明明没配代理」变得难以解释。 代价是:如果你的网络本身必须靠代理才能访问 Google,就必须在这里显式填写代理地址。
隐私与安全
- 插件不发送任何遥测,不联网到除
gemini.google.com(及用户配置的代理)以外的地址。 - Cookie 只在内存中解密,不写入任何明文件;解密所需的 32 字节主密钥同样只在内存中。
- 临时复制的 Cookie 数据库在读完后立即删除。
- 加密快照:客户端运行时独占锁定 Cookies 库(任何
FILE_SHARE_*组合都拿不到句柄),因此插件会在能读到库的那些时刻,把仍然保持v10/v11密文的库复制到~/.dsh/cache/gemini-cookies/<hash>.Cookies,再附一份只记录拷贝时间的.json。快照里没有明文,解密依旧要 DPAPI 主密钥(用户登录态派生)。删掉该目录即彻底清除。 - loopback shim 只监听
127.0.0.1,且校验Host头与本机地址、用常数时间比较校验随机令牌。
开发
npm test # node --test "tests/*.test.js"
npm run check # 语法检查 + 单测
代码结构:
lib/net.js HTTP/HTTPS 传输层(直连 + HTTP 代理 CONNECT 隧道)
lib/gemini.js Cookie 解密、StreamGenerate、用量抓取、连通性检测
lib/proxy.js loopback pi-messages SSE 服务
lib/core.js 常量与纯函数(模型清单、思考等级映射、降级判定)
lib/config.js schemastery 配置 schema
lib/index.js 宿主入口(provider 注册 / HTTP 路由 / 生命周期)
lib/client.js 前端(设置区块 + shell.overlay 挂件)
模型与思考等级怎么选
模型不是按 URL 选的,而是靠四个 x-goog-ext-*-jspb 侧信道请求头下发:
| 头 | 内容 |
|---|---|
x-goog-ext-525001261-jspb |
[1,null,null,null,"<哈希>",null,null,0,[4,5,6,8],null,null,<legacyMode>,null,null,<mode>,<思考档>,"<UUID>"] |
x-goog-ext-525005358-jspb |
[<UUID>, 1] |
x-goog-ext-73010989-jspb |
[0] |
x-goog-ext-73010990-jspb |
[0,0,0] |
对应关系:
| DSH 模型 id | 上游哈希 | mode | 上游显示名 |
|---|---|---|---|
gemini-flash-lite |
8c46e95b1a07cecc |
6 | 3.5 Flash-Lite |
gemini-web |
56fdd199312815e2 |
1 | 3.8 Flash |
gemini-pro |
e6fa609c3fa255c0 |
3 | 3.1 Pro |
gemini-argon |
95a4c0abcba24364 |
8 | 4 Argon |
⚠ mode 才是权威开关,哈希只作提示。实测(2026-10-30,本机账号 + 代理,各 10 次采样): 给 Flash-Lite 配旧哈希
cf41b0e0dd7d53e5仍会得到 mode 6 对应的8c46e95b1a07cecc; Argon 的 mode=8 在无权益账号上同样被忽略。因此下表的哈希仅供对照,排查时请以响应 回显的模型名(inner[42])为准。
4 Argon 的两个特殊点:
- 开关是
mode = 8而不是哈希。要把 mode 同时写进inner[79]并补齐 97 槽的 inner 数组(webModel.extendedInner)。 - 思考档位不同:低 =
1、中 =4、高 =2,且没有minimal档(thinkingLevelMapFor())。
⚠ 4 Argon 需要账号具备相应权益。没有权益时 Gemini 不报错,直接静默换成更弱的模型回 HTTP 200。 插件会从响应
inner[39](哈希)与inner[42](显示名)取回服务端实际使用的模型,在done事件里给出upstreamModel/downgraded,并在设置页「可用模型」卡片展示「最近一次实际使用」。这是目前唯一能发现降级的判据。
许可证
免责声明
本项目通过 Gemini 网页版接口访问模型,属于非官方接入方式,可能随上游变更而失效,也可能触及 Google 的服务条款。请自行评估使用风险。本项目与 Google 无任何关联。
No comments yet. Be the first to write one.