magpie-gemini-web 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +31 -19
- package/README.md +31 -19
- package/docs/backend-release.md +31 -0
- package/index.mjs +15 -1
- package/lib/backend-release.json +36 -0
- package/lib/backend.mjs +5 -3
- package/lib/native.mjs +50 -18
- package/lib/task.mjs +87 -0
- package/native-task/LICENSE +21 -0
- package/native-task/README.md +89 -0
- package/native-task/bridge/gemini_native_bridge/__init__.py +3 -0
- package/native-task/bridge/gemini_native_bridge/__main__.py +235 -0
- package/native-task/bridge/gemini_native_bridge/auth.py +143 -0
- package/native-task/bridge/gemini_native_bridge/config.py +222 -0
- package/native-task/bridge/gemini_native_bridge/contract.py +283 -0
- package/native-task/bridge/gemini_native_bridge/native_auth_keychain.swift +111 -0
- package/native-task/bridge/gemini_native_bridge/oauth.py +154 -0
- package/native-task/bridge/gemini_native_bridge/protocol.py +363 -0
- package/native-task/bridge/gemini_native_bridge/server.py +441 -0
- package/native-task/bridge/gemini_native_bridge/state.py +806 -0
- package/native-task/bridge/gemini_native_bridge/transport.py +531 -0
- package/native-task/index.mjs +76 -0
- package/native-task/lib/runtime.mjs +189 -0
- package/native-task/pyproject.toml +18 -0
- package/native-task/requirements.txt +2 -0
- package/native-task/scripts/bridge.mjs +97 -0
- package/package.json +15 -3
- package/scripts/backend-release.mjs +111 -0
- package/scripts/backend.mjs +4 -1
package/README.en.md
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
Use Gemini on the web through your Google account in [Magpie](https://usemagpie.ai/). The plugin runs the [gemini-web2api-go](https://github.com/zexadev/gemini-web2api-go) backend on your machine. The backend turns the Gemini web app into an OpenAI-compatible API.
|
|
6
6
|
|
|
7
|
+
> **2026-10-16 update:** Full-price Google AI Pro accounts can now use `gemini-4-argon` directly in Antigravity, and can likewise be proxied directly with Magpie.
|
|
8
|
+
|
|
7
9
|
## Getting started
|
|
8
10
|
|
|
9
11
|
1. Install **magpie-gemini-web** from the Magpie plugin market.
|
|
10
|
-
2. Sign in to Gemini Web and choose **一键配置本地 Gemini Web (one-click local setup, recommended)**. The plugin downloads
|
|
12
|
+
2. Sign in to Gemini Web and choose **一键配置本地 Gemini Web (one-click local setup, recommended)**. The plugin downloads this project's optimized backend, verifies its SHA-256 and starts it in the background. No Go installation or manual patching is needed.
|
|
11
13
|
3. Copy your cookie when prompted. **Use a private / incognito window** (Firefox recommended):
|
|
12
14
|
1. In the private window, open <https://gemini.google.com/app> and sign in to the Google account you want to use.
|
|
13
15
|
2. Press F12 (⌥⌘I on macOS), switch to **Network** and reload the page.
|
|
@@ -26,15 +28,15 @@ From then on, Magpie starts the backend automatically every time it launches. To
|
|
|
26
28
|
|
|
27
29
|
## Plugin options
|
|
28
30
|
|
|
29
|
-
Tool calls
|
|
31
|
+
Tool calls are on by default (coding agents send tools with every request); image input is off. To allow images:
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
magpie plugin options magpie-gemini-web '{"
|
|
34
|
+
magpie plugin options magpie-gemini-web '{"allowImages":true}'
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
| Option | Default | Description |
|
|
36
38
|
| :--- | :--- | :--- |
|
|
37
|
-
| `allowTools` | `
|
|
39
|
+
| `allowTools` | `true` | Allows tool calls (experimental; upstream emulates them with prompts). When `false`, requests with tools get a 400 |
|
|
38
40
|
| `allowImages` | `false` | Allows image input |
|
|
39
41
|
| `baseURL` | `http://127.0.0.1:8083/v1` | Default backend URL |
|
|
40
42
|
| `contextLimit` / `outputLimit` | `1000000` / `8192` | Context and output limits reported to clients |
|
|
@@ -48,7 +50,7 @@ You can switch a model between `low` and `high` effort in Magpie if it has a `-t
|
|
|
48
50
|
## Security and data
|
|
49
51
|
|
|
50
52
|
- The backend listens on `127.0.0.1` only. Your cookie goes only to that local backend, and Magpie stores a locally generated API key instead.
|
|
51
|
-
- The
|
|
53
|
+
- The default backend is this project's **4.22.0-magpie.1**, built from pinned upstream v4.22.0 sources with this repository's patch. SHA-256 is checked on download and before launching a new process. Builds cover macOS, Linux and Windows on x64 and arm64; the release includes sources, build metadata and checksums.
|
|
52
54
|
- The data directory is `~/.local/share/magpie-gemini-web` (set `GEMINI_WEB_DATA_DIR` to change it). The backend writes its log to `backend.log` there.
|
|
53
55
|
- The plugin sends one request at a time, but this can't guarantee that Google won't flag your account. Use it at your own risk.
|
|
54
56
|
|
|
@@ -75,30 +77,40 @@ So with a proxy pool, an account just added by one-click setup has no email yet.
|
|
|
75
77
|
- Upstream emulates tool calls with prompts, so they aren't always reliable. Flash models often don't call tools.
|
|
76
78
|
- Only text models whose names start with `gemini-` are exposed. A `cost` of 0 doesn't reflect your actual Google quota.
|
|
77
79
|
|
|
78
|
-
##
|
|
80
|
+
## Native Task on macOS (experimental)
|
|
81
|
+
|
|
82
|
+
On macOS, sign-in offers one more option, "macOS 原生 Task(实验性)": requests skip the Gemini Web backend and go to a local daemon that runs them in Gemini.app's own Task mode, with tool calls on its native Function Calling channel, as the model `gemini-task-argon`. Windows and Linux don't offer it; keep using one-click setup there.
|
|
83
|
+
|
|
84
|
+
This is an alpha: it supports only the verified Gemini.app build, needs a local setup first (Gemini.app signed in, plus session templates), has passed only one live tool round trip against real Argon, and shows no usage; if an agent is interrupted before it returns a tool result, that call holds the only Task slot for up to about 4 minutes. See [native-task/README.md](native-task/README.md) for setup, session requirements and limitations.
|
|
85
|
+
|
|
86
|
+
## Default optimized backend and rollback
|
|
87
|
+
|
|
88
|
+
Starting with plugin **0.4.1**, one-click setup uses [4.22.0-magpie.1](https://github.com/BUKOWSKIREAL/magpie-gemini-web/releases/tag/backend-v4.22.0-magpie.1). It includes the changes that previously required a manual build: stable XSRF caching, cancellation propagation for the main generation and upload requests, early completion after complete tool calls, browser headers, and verification of the returned Argon model. Your Google account still needs access to the requested model.
|
|
89
|
+
|
|
90
|
+
**Upgrading an existing installation:** the plugin leaves a running backend alone. Stop the existing backend process (its process ID is in `backend.pid` in the data directory), then start Magpie or send a request. A stopped old official backend is upgraded automatically, preserving configuration, the cookie database and API keys. Explicitly installed local Argon builds are kept; to replace one with the published build, stop it and run `backend:install` below.
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
To roll back, stop the backend and run this from the plugin directory:
|
|
81
93
|
|
|
82
94
|
```bash
|
|
83
|
-
|
|
84
|
-
git clone https://github.com/zexadev/gemini-web2api-go.git backend-source
|
|
85
|
-
git -C backend-source checkout a65b1d1a5ac614c1b455857334b4f1c73ab8fcfb
|
|
86
|
-
git -C backend-source apply ../patches/argon.patch
|
|
87
|
-
npm run backend:build:argon
|
|
88
|
-
npm run backend:install:argon
|
|
95
|
+
npm run backend:install:upstream
|
|
89
96
|
```
|
|
90
97
|
|
|
91
|
-
|
|
98
|
+
This installs the checksum-verified official v4.22.0 and saves the selection, so automatic starts keep using upstream. To restore the default optimized build:
|
|
92
99
|
|
|
93
|
-
|
|
100
|
+
```bash
|
|
101
|
+
npm run backend:install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A failed download or checksum verification keeps the previous binary. On Windows, stop the old process before replacing its executable.
|
|
105
|
+
|
|
106
|
+
The backend release includes source and build metadata. Developers with Git, Node 22+ and Go 1.27.1 can reproduce all six builds and compare the pinned hashes from this repository:
|
|
94
107
|
|
|
95
108
|
```bash
|
|
96
|
-
|
|
97
|
-
git -C backend-source apply ../patches/argon.patch
|
|
98
|
-
npm run backend:build:argon && npm run backend:install:argon
|
|
99
|
-
kill $(cat ~/.local/share/magpie-gemini-web/backend.pid)
|
|
109
|
+
npm run backend:release:build
|
|
100
110
|
```
|
|
101
111
|
|
|
112
|
+
The script extracts clean sources from the pinned upstream commit and applies `patches/argon.patch`; it never changes the developer's `backend-source` working tree. See [docs/backend-release.md](docs/backend-release.md) for the release procedure.
|
|
113
|
+
|
|
102
114
|
## License
|
|
103
115
|
|
|
104
116
|
MIT. The upstream gemini-web2api-go project is also MIT (see `LICENSE-UPSTREAM`).
|
package/README.md
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
让 [Magpie](https://usemagpie.ai/) 通过你的 Google 账号使用 Gemini 网页版。插件在本机运行 [gemini-web2api-go](https://github.com/zexadev/gemini-web2api-go) 后端,把 Gemini 网页转成 OpenAI 兼容接口。
|
|
6
6
|
|
|
7
|
+
> **2026-10-16 更新**:现在正价支付的 Google AI Pro 可以直接在 Antigravity 中使用 `gemini-4-argon`,同理也可以直接用 Magpie 反代。
|
|
8
|
+
|
|
7
9
|
## 开始使用
|
|
8
10
|
|
|
9
11
|
1. 在 Magpie 插件市场安装 **magpie-gemini-web**。
|
|
10
|
-
2. 登录 Gemini Web,选 **「一键配置本地 Gemini Web
|
|
12
|
+
2. 登录 Gemini Web,选 **「一键配置本地 Gemini Web(推荐)」**。插件会自动下载本项目发布的优化后端、校验 SHA-256,并在后台启动;不需要安装 Go 或手动打补丁。
|
|
11
13
|
3. 按提示复制 Cookie,**一定要用无痕 / 隐私窗口**(推荐 Firefox):
|
|
12
14
|
1. 在无痕窗口打开 <https://gemini.google.com/app>,登录要使用的 Google 账号;
|
|
13
15
|
2. 按 F12(macOS 按 ⌥⌘I),切到 **Network(网络)**,刷新页面;
|
|
@@ -26,15 +28,15 @@
|
|
|
26
28
|
|
|
27
29
|
## 插件选项
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
工具调用默认开启(Coding Agent 每个请求都带工具),图片输入默认关闭。需要图片输入时:
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
magpie plugin options magpie-gemini-web '{"
|
|
34
|
+
magpie plugin options magpie-gemini-web '{"allowImages":true}'
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
| 选项 | 默认值 | 说明 |
|
|
36
38
|
| :--- | :--- | :--- |
|
|
37
|
-
| `allowTools` | `
|
|
39
|
+
| `allowTools` | `true` | 允许工具调用(实验性,由上游用提示词模拟)。设为 `false` 时带工具的请求会返回 400 |
|
|
38
40
|
| `allowImages` | `false` | 允许图片输入 |
|
|
39
41
|
| `baseURL` | `http://127.0.0.1:8083/v1` | 默认后端地址 |
|
|
40
42
|
| `contextLimit` / `outputLimit` | `1000000` / `8192` | 向客户端声明的上下文 / 输出上限 |
|
|
@@ -48,7 +50,7 @@ magpie plugin options magpie-gemini-web '{"allowTools":true,"allowImages":true}'
|
|
|
48
50
|
## 安全与数据
|
|
49
51
|
|
|
50
52
|
- 后端只监听 `127.0.0.1`;Cookie 只交给本机后端,Magpie 保存的是本地生成的 API key。
|
|
51
|
-
-
|
|
53
|
+
- 默认后端为本项目发布的 **4.22.0-magpie.1**,基于固定的上游 v4.22.0 源码应用本仓库补丁。下载和启动新进程前校验 SHA-256。支持 macOS、Linux、Windows(x64 / arm64),发布页提供源码、构建记录和校验和。
|
|
52
54
|
- 数据目录:`~/.local/share/magpie-gemini-web`(可用 `GEMINI_WEB_DATA_DIR` 修改),日志在 `backend.log`。
|
|
53
55
|
- 插件限制并发为 1,但无法保证账号不被 Google 风控,请自行承担风险。
|
|
54
56
|
|
|
@@ -75,30 +77,40 @@ Magpie 的额度卡片会显示 Google 账号的 **5 小时** 和 **每周** 用
|
|
|
75
77
|
- 工具调用由上游用提示词模拟,不一定可靠,Flash 系列经常不调用工具。
|
|
76
78
|
- 只开放 `gemini-` 开头的文本模型;`cost` 显示为 0 不代表 Google 的实际配额。
|
|
77
79
|
|
|
78
|
-
##
|
|
80
|
+
## macOS 原生 Task(实验性)
|
|
81
|
+
|
|
82
|
+
在 macOS 上登录时会多出一个「macOS 原生 Task(实验性)」选项:请求不走 Gemini Web 后端,而是交给本机常驻服务,由 Gemini.app 自己的 Task 模式执行,工具调用走原生的 Function Calling 通道,模型为 `gemini-task-argon`。Windows 和 Linux 上不会出现这个选项,请继续使用上面的一键配置。
|
|
83
|
+
|
|
84
|
+
当前为 Alpha:仅支持已验证的 Gemini.app 版本,选这个选项之前需要先在本机完成 setup(需要 Gemini.app 已登录,以及会话模板),只完成过一次真实 Argon 的工具往返验证,读不到额度;Agent 中途打断时,未回传结果的工具调用会占住唯一的 Task 名额最长约 4 分钟。配置步骤、会话要求和限制见 [native-task/README.md](native-task/README.md)。
|
|
85
|
+
|
|
86
|
+
## 默认优化后端与回退
|
|
87
|
+
|
|
88
|
+
从插件 **0.4.1** 起,一键配置默认使用 [4.22.0-magpie.1](https://github.com/BUKOWSKIREAL/magpie-gemini-web/releases/tag/backend-v4.22.0-magpie.1)。它包含原先需要手动编译的优化:稳定的 XSRF 缓存、主要生成与上传请求的取消传播、完整工具调用后的提前收尾、浏览器请求头,以及 Argon 返回模型校验。Google 账号仍需具备对应模型权限。
|
|
89
|
+
|
|
90
|
+
**已有用户升级**:插件不会中断正在运行的后端。先停止现有后端进程(进程 ID 在数据目录的 `backend.pid`),再启动 Magpie 或发送请求;停止状态的旧官方后端会自动更新,配置、Cookie 数据库和 API key 保留。已经手动安装的本地 Argon 构建会继续复用;要切到发布版,可在停止后端后运行下面的 `backend:install`。
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
需要回退时,在插件目录停止后端后运行:
|
|
81
93
|
|
|
82
94
|
```bash
|
|
83
|
-
|
|
84
|
-
git clone https://github.com/zexadev/gemini-web2api-go.git backend-source
|
|
85
|
-
git -C backend-source checkout a65b1d1a5ac614c1b455857334b4f1c73ab8fcfb
|
|
86
|
-
git -C backend-source apply ../patches/argon.patch
|
|
87
|
-
npm run backend:build:argon
|
|
88
|
-
npm run backend:install:argon
|
|
95
|
+
npm run backend:install:upstream
|
|
89
96
|
```
|
|
90
97
|
|
|
91
|
-
|
|
98
|
+
这会安装经过 SHA-256 校验的官方 v4.22.0,并记住回退选择,之后自动启动也使用官方版。恢复默认优化版:
|
|
92
99
|
|
|
93
|
-
|
|
100
|
+
```bash
|
|
101
|
+
npm run backend:install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
下载失败或校验不通过时保留旧二进制。Windows 上必须先停止旧进程,才能替换正在使用的可执行文件。
|
|
105
|
+
|
|
106
|
+
源码与构建记录随后端 Release 提供。开发者使用 Git、Node 22+、Go 1.27.1,可从本仓库复现全部六个平台的构建并核对哈希:
|
|
94
107
|
|
|
95
108
|
```bash
|
|
96
|
-
|
|
97
|
-
git -C backend-source apply ../patches/argon.patch
|
|
98
|
-
npm run backend:build:argon && npm run backend:install:argon
|
|
99
|
-
kill $(cat ~/.local/share/magpie-gemini-web/backend.pid)
|
|
109
|
+
npm run backend:release:build
|
|
100
110
|
```
|
|
101
111
|
|
|
112
|
+
构建脚本从固定的上游提交提取干净源码、应用 `patches/argon.patch`,不会修改本地 `backend-source` 的工作内容。发布流程见 [docs/backend-release.md](docs/backend-release.md)。
|
|
113
|
+
|
|
102
114
|
## 友链
|
|
103
115
|
|
|
104
116
|
[LINUX DO](https://linux.do/) — 真诚、友善、团结、专业的技术社区。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Backend releases
|
|
2
|
+
|
|
3
|
+
The plugin's default backend is built from the upstream commit and toolchain recorded in `lib/backend-release.json`, with `patches/argon.patch` applied. The builder overrides the backend's displayed version with the release version. Six binaries are pinned by SHA-256 in the same JSON file. The build does not include the developer's uncommitted backend sources, local data or credentials.
|
|
4
|
+
|
|
5
|
+
For a new backend release:
|
|
6
|
+
|
|
7
|
+
1. Choose a new backend version and tag in `lib/backend-release.json`. Never replace files under an already published backend tag: older plugins pin those hashes. Update the upstream commit or patch when needed.
|
|
8
|
+
2. Run `node scripts/backend-release.mjs --write-pins`. It extracts a clean upstream tree, applies the patch, runs Go tests, cross-compiles all six targets and writes the resulting pins. It uses a local `backend-source` Git object database if available; otherwise it fetches the pinned public commit in a temporary directory.
|
|
9
|
+
3. Run `npm test` and `node scripts/backend-smoke.mjs`. The smoke test uses a temporary data directory, checks the actual executable's version and hash, starts it on a spare loopback port, checks authentication and model discovery, and verifies reuse. It uses no Google account and takes the tokenizer's offline fallback.
|
|
10
|
+
4. Commit the release metadata, installer changes, tests and documentation. Run the **Verify backend release** GitHub Actions workflow at that commit. It rebuilds on native macOS, Linux and Windows runners for both architectures, compares the pinned hashes, and runs the installation/startup smoke test. Wait for all jobs to pass.
|
|
11
|
+
5. Create a private draft under the pinned backend tag. Attach all six binaries, `SHA256SUMS.txt`, `build.json`, the patched source archive and `LICENSE-UPSTREAM` from `backend-build/release`. If local binary uploads are unreliable, run **Upload verified backend release** with the successful verification run ID: it downloads the six CI artifacts, checks every committed hash, and uploads missing executables to the draft. Existing assets must match exactly; published releases cannot be modified by this workflow. Include the upstream commit and patch SHA-256 in the release notes, then publish the complete draft. Keep backend-only releases from replacing the plugin release as GitHub's latest release.
|
|
12
|
+
6. Verify public asset downloads against the committed pins. Run `node scripts/backend-smoke.mjs --download` to test the actual default download URL on the host platform.
|
|
13
|
+
7. Bump and publish the plugin package only after the backend assets are publicly available. Inspect `npm pack --dry-run`; the npm package contains release metadata and patch/build scripts, but no executables, local databases or credentials. Attach the exact published npm tarball to the plugin's GitHub release as well.
|
|
14
|
+
|
|
15
|
+
Reproduce a release without changing its pins:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run backend:release:build
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Build just the host target and compare its hash:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node scripts/backend-release.mjs --target=host --verify
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The metadata records `CGO_ENABLED=0`, the exact Go toolchain, upstream commit, patch hash and build flags. The binary hashes are reproducible; the source archive is supplied for inspection and is not promised to have a reproducible gzip timestamp. The source archive includes upstream's license, and `LICENSE-UPSTREAM` is also supplied as a separate asset.
|
|
28
|
+
|
|
29
|
+
Existing running processes are left alone to avoid interrupting requests. Once an old official backend is stopped, automatic startup installs the optimized build while preserving configuration, secrets and the database. A download or checksum failure keeps the old file and never starts an unverified replacement.
|
|
30
|
+
|
|
31
|
+
An explicit `npm run backend:install:upstream` installs the pinned upstream v4.22.0 and persists `upstream` in `backend-channel.json`. Automatic startup respects that choice. `npm run backend:install` returns to the optimized channel. Stop a running backend before replacing it, particularly on Windows. Explicit local Argon builds remain trusted through their existing build manifest.
|
package/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { normalizeBaseURL, settings, credentials, listModels, forward, UNREACHAB
|
|
|
2
2
|
import { ensureBackend, localBackend } from './lib/native.mjs';
|
|
3
3
|
import { parseCookieInput, importCookie } from './lib/setup.mjs';
|
|
4
4
|
import { usage, poolChoices, poolAccounts, accountEmail } from './lib/usage.mjs';
|
|
5
|
+
import { taskSupported, taskMethod, taskLoader, taskModels, isTaskAuth } from './lib/task.mjs';
|
|
5
6
|
|
|
6
7
|
const ID = 'gemini-web';
|
|
7
8
|
const GEMINI_URL = 'https://gemini.google.com/app';
|
|
@@ -158,9 +159,10 @@ export const GeminiWebPlugin = async (_input, options = {}) => {
|
|
|
158
159
|
callback: async () => signInFor(local, chosen)
|
|
159
160
|
};
|
|
160
161
|
}
|
|
161
|
-
}],
|
|
162
|
+
}, ...(taskSupported() ? [taskMethod] : [])],
|
|
162
163
|
async loader(getAuth) {
|
|
163
164
|
const auth = await getAuth();
|
|
165
|
+
if (isTaskAuth(auth)) return taskLoader(auth);
|
|
164
166
|
if (auth?.type !== 'api') return {};
|
|
165
167
|
const c = credentials(auth, opts);
|
|
166
168
|
return {
|
|
@@ -176,6 +178,7 @@ export const GeminiWebPlugin = async (_input, options = {}) => {
|
|
|
176
178
|
async usage(getAuth) {
|
|
177
179
|
const auth = await getAuth();
|
|
178
180
|
if (auth?.type !== 'api') return { error: 'Gemini Web 还没有登录', windows: [], signIn: 'kept' };
|
|
181
|
+
if (isTaskAuth(auth)) return { error: '原生 Task 登录读不到额度', windows: [], signIn: 'kept' };
|
|
179
182
|
let c;
|
|
180
183
|
try {
|
|
181
184
|
c = credentials(auth, opts);
|
|
@@ -193,6 +196,7 @@ export const GeminiWebPlugin = async (_input, options = {}) => {
|
|
|
193
196
|
id: ID,
|
|
194
197
|
async models(provider, { auth } = {}) {
|
|
195
198
|
if (auth?.type !== 'api') return provider.models;
|
|
199
|
+
if (isTaskAuth(auth)) return taskModels(auth, ID);
|
|
196
200
|
const c = credentials(auth, opts);
|
|
197
201
|
try {
|
|
198
202
|
return await listModels(c, opts);
|
|
@@ -202,6 +206,16 @@ export const GeminiWebPlugin = async (_input, options = {}) => {
|
|
|
202
206
|
return listModels(c, opts);
|
|
203
207
|
}
|
|
204
208
|
}
|
|
209
|
+
},
|
|
210
|
+
// The Task bridge keeps a conversation's state by its session, so each request carries the
|
|
211
|
+
// host's session id. The Web backend's forward() passes on only the headers it needs, so
|
|
212
|
+
// this doesn't reach it.
|
|
213
|
+
async 'chat.headers'(input, output) {
|
|
214
|
+
if (input?.model?.providerID !== ID && input?.provider?.info?.id !== ID) return;
|
|
215
|
+
if (typeof input.sessionID === 'string' && input.sessionID) {
|
|
216
|
+
output.headers ??= {};
|
|
217
|
+
output.headers['X-Gemini-Session'] = input.sessionID;
|
|
218
|
+
}
|
|
205
219
|
}
|
|
206
220
|
};
|
|
207
221
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "4.22.0-magpie.1",
|
|
3
|
+
"repository": "BUKOWSKIREAL/magpie-gemini-web",
|
|
4
|
+
"tag": "backend-v4.22.0-magpie.1",
|
|
5
|
+
"upstream": {
|
|
6
|
+
"repository": "https://github.com/zexadev/gemini-web2api-go.git",
|
|
7
|
+
"commit": "a65b1d1a5ac614c1b455857334b4f1c73ab8fcfb"
|
|
8
|
+
},
|
|
9
|
+
"goVersion": "1.27.1",
|
|
10
|
+
"binaries": {
|
|
11
|
+
"darwin-arm64": {
|
|
12
|
+
"asset": "gemini-web2api-go_v4.22.0-magpie.1_darwin_arm64",
|
|
13
|
+
"sha256": "833dd54e7c0df927afc1d4e0f6443bcc03b7f0a4d04d24cdfde47d5dd27349c5"
|
|
14
|
+
},
|
|
15
|
+
"darwin-x64": {
|
|
16
|
+
"asset": "gemini-web2api-go_v4.22.0-magpie.1_darwin_amd64",
|
|
17
|
+
"sha256": "d65b053a0e5f8e1065ea35835b3a7d12f783d97964c02bef49679df7ead92fd4"
|
|
18
|
+
},
|
|
19
|
+
"linux-x64": {
|
|
20
|
+
"asset": "gemini-web2api-go_v4.22.0-magpie.1_linux_amd64",
|
|
21
|
+
"sha256": "94f082e666b34051f191959de3f2cd601078d117b1d3ededcdcd6b2201c48ef9"
|
|
22
|
+
},
|
|
23
|
+
"linux-arm64": {
|
|
24
|
+
"asset": "gemini-web2api-go_v4.22.0-magpie.1_linux_arm64",
|
|
25
|
+
"sha256": "4a7cbca2771acc9e836a5a986e11b3b247baf77e9900262cb588b515a1161aaa"
|
|
26
|
+
},
|
|
27
|
+
"win32-x64": {
|
|
28
|
+
"asset": "gemini-web2api-go_v4.22.0-magpie.1_windows_amd64.exe",
|
|
29
|
+
"sha256": "61d279d50c6c09ffe81ed059a557077900948d0ae598e4c4b3a5d5c213ad5e5c"
|
|
30
|
+
},
|
|
31
|
+
"win32-arm64": {
|
|
32
|
+
"asset": "gemini-web2api-go_v4.22.0-magpie.1_windows_arm64.exe",
|
|
33
|
+
"sha256": "20bad088b114d3dc989830ff35f7e56c1e0026caf9721b739fe982f5fd3d1880"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/lib/backend.mjs
CHANGED
|
@@ -115,7 +115,9 @@ export function settings(options = {}) {
|
|
|
115
115
|
baseURL = normalizeBaseURL(options.baseURL);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
// On by default: coding agents send tools with every request and don't read toolcall,
|
|
119
|
+
// so turning this off only means every one of their requests gets a 400.
|
|
120
|
+
let allowTools = true;
|
|
119
121
|
if (options.allowTools !== undefined) {
|
|
120
122
|
if (typeof options.allowTools !== 'boolean') {
|
|
121
123
|
throw new Error('Invalid option allowTools: must be a boolean');
|
|
@@ -440,7 +442,7 @@ export async function forward(input, init, getAuth, opts) {
|
|
|
440
442
|
const hasTools = (Array.isArray(body.tools) && body.tools.length > 0) ||
|
|
441
443
|
(Array.isArray(body.functions) && body.functions.length > 0);
|
|
442
444
|
if (!opts.allowTools && hasTools) {
|
|
443
|
-
return makeOpenAIError(400,
|
|
445
|
+
return makeOpenAIError(400, `Tool calls are turned off for magpie-gemini-web. Turn them on with: magpie plugin options magpie-gemini-web '{"allowTools":true}'`);
|
|
444
446
|
}
|
|
445
447
|
|
|
446
448
|
if (body.messages !== undefined) {
|
|
@@ -462,7 +464,7 @@ export async function forward(input, init, getAuth, opts) {
|
|
|
462
464
|
}
|
|
463
465
|
} else if (part.type === 'image_url') {
|
|
464
466
|
if (!opts.allowImages) {
|
|
465
|
-
return makeOpenAIError(400,
|
|
467
|
+
return makeOpenAIError(400, `Image input is turned off for magpie-gemini-web. Turn it on with: magpie plugin options magpie-gemini-web '{"allowImages":true}'`);
|
|
466
468
|
}
|
|
467
469
|
if (!part.image_url || typeof part.image_url.url !== 'string' || !part.image_url.url.trim()) {
|
|
468
470
|
return makeOpenAIError(400, 'Image content must contain an image_url URL');
|
package/lib/native.mjs
CHANGED
|
@@ -3,12 +3,16 @@ import path from 'node:path';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
+
import release from './backend-release.json' with { type: 'json' };
|
|
6
7
|
|
|
7
|
-
export const BACKEND_VERSION =
|
|
8
|
-
const RELEASE_BASE = `https://github.com/
|
|
8
|
+
export const BACKEND_VERSION = `v${release.version}`;
|
|
9
|
+
const RELEASE_BASE = `https://github.com/${release.repository}/releases/download/${release.tag}/`;
|
|
10
|
+
export const PINNED_BINARIES = release.binaries;
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
const UPSTREAM_VERSION = 'v4.22.0';
|
|
13
|
+
const UPSTREAM_BASE = `https://github.com/zexadev/gemini-web2api-go/releases/download/${UPSTREAM_VERSION}/`;
|
|
14
|
+
// Retained for an explicit, checksum-verified rollback to the upstream release.
|
|
15
|
+
export const UPSTREAM_BINARIES = {
|
|
12
16
|
'darwin-arm64': { asset: 'gemini-web2api-go_v4.22.0_darwin_arm64', sha256: 'c69ca75cf43017cf2a62dbf5c12b07f6bde66f403f714c32209b1ad350ed24c9' },
|
|
13
17
|
'darwin-x64': { asset: 'gemini-web2api-go_v4.22.0_darwin_amd64', sha256: '384e617bcc30aacac6b7c4e4d0490e88d282b13b29f7a0a20d645b69514cd405' },
|
|
14
18
|
'linux-x64': { asset: 'gemini-web2api-go_v4.22.0_linux_amd64', sha256: '21fcc81e0ad8ce0921e6b6c2cafce1089852530142b37e0289355c6e72df5299' },
|
|
@@ -17,12 +21,38 @@ export const PINNED_BINARIES = {
|
|
|
17
21
|
'win32-arm64': { asset: 'gemini-web2api-go_v4.22.0_windows_arm64.exe', sha256: 'b42e951bfc08d7fe8bdb436dca87fc6a1b7948bb8d10e110653bcf13a790756c' }
|
|
18
22
|
};
|
|
19
23
|
|
|
20
|
-
export function pinnedBinary(platform = process.platform, arch = process.arch) {
|
|
21
|
-
|
|
24
|
+
export function pinnedBinary(platform = process.platform, arch = process.arch, channel = 'optimized') {
|
|
25
|
+
if (channel !== 'optimized' && channel !== 'upstream') throw new Error(`Invalid backend channel: ${channel}`);
|
|
26
|
+
const pins = channel === 'upstream' ? UPSTREAM_BINARIES : PINNED_BINARIES;
|
|
27
|
+
const base = channel === 'upstream' ? UPSTREAM_BASE : RELEASE_BASE;
|
|
28
|
+
const version = channel === 'upstream' ? UPSTREAM_VERSION : BACKEND_VERSION;
|
|
29
|
+
const pin = pins[`${platform}-${arch}`];
|
|
22
30
|
if (!pin) {
|
|
23
|
-
throw new Error(`No gemini-web2api-go ${
|
|
31
|
+
throw new Error(`No gemini-web2api-go ${version} build for ${platform}/${arch}. See ${base.slice(0, -1).replace('/download/', '/tag/')}`);
|
|
32
|
+
}
|
|
33
|
+
return { url: base + pin.asset, sha256: pin.sha256 };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function backendChannel(dataDir = getDataDir()) {
|
|
37
|
+
try {
|
|
38
|
+
const { channel } = JSON.parse(fs.readFileSync(path.join(dataDir, 'backend-channel.json'), 'utf8'));
|
|
39
|
+
if (channel !== 'optimized' && channel !== 'upstream') throw new Error('Expected optimized or upstream');
|
|
40
|
+
return channel;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code === 'ENOENT') return 'optimized';
|
|
43
|
+
throw new Error(`Invalid backend-channel.json: ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function saveBackendChannel(dataDir, channel) {
|
|
48
|
+
const file = path.join(dataDir, 'backend-channel.json');
|
|
49
|
+
const temp = `${file}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
50
|
+
try {
|
|
51
|
+
fs.writeFileSync(temp, JSON.stringify({ channel }) + '\n', { mode: 0o600 });
|
|
52
|
+
fs.renameSync(temp, file);
|
|
53
|
+
} finally {
|
|
54
|
+
fs.rmSync(temp, { force: true });
|
|
24
55
|
}
|
|
25
|
-
return { url: RELEASE_BASE + pin.asset, sha256: pin.sha256 };
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
export function getDataDir(env = process.env) {
|
|
@@ -41,7 +71,8 @@ export function computeFileHash(filePath) {
|
|
|
41
71
|
export async function installBackend(options = {}) {
|
|
42
72
|
const platform = options.platform || process.platform;
|
|
43
73
|
const arch = options.arch || process.arch;
|
|
44
|
-
const
|
|
74
|
+
const channel = options.channel ?? 'optimized';
|
|
75
|
+
const pin = options.expectedHash && options.url ? null : pinnedBinary(platform, arch, channel);
|
|
45
76
|
const expectedHash = options.expectedHash || pin.sha256;
|
|
46
77
|
const downloadUrl = options.url || pin.url;
|
|
47
78
|
const fetchFn = options.fetchFn || globalThis.fetch;
|
|
@@ -62,8 +93,6 @@ export async function installBackend(options = {}) {
|
|
|
62
93
|
if (existingHash === expectedHash) {
|
|
63
94
|
binaryReady = true;
|
|
64
95
|
console.log(`Backend binary verified: ${binPath}`);
|
|
65
|
-
} else {
|
|
66
|
-
fs.unlinkSync(binPath);
|
|
67
96
|
}
|
|
68
97
|
}
|
|
69
98
|
|
|
@@ -97,7 +126,11 @@ export async function installBackend(options = {}) {
|
|
|
97
126
|
}
|
|
98
127
|
}
|
|
99
128
|
|
|
100
|
-
|
|
129
|
+
const files = ensureDataFiles(dataDir);
|
|
130
|
+
// A local custom build keeps the user's channel. Official installs record their
|
|
131
|
+
// selection only after verification and replacement succeeded.
|
|
132
|
+
if ((!options.expectedHash && !options.url) || options.channel !== undefined) saveBackendChannel(dataDir, channel);
|
|
133
|
+
return { binPath, ...files };
|
|
101
134
|
}
|
|
102
135
|
|
|
103
136
|
// ensureDataFiles creates config.json and secrets.json when missing; existing ones are kept.
|
|
@@ -136,7 +169,7 @@ function ensureDataFiles(dataDir) {
|
|
|
136
169
|
|
|
137
170
|
export function startBackend(options = {}) {
|
|
138
171
|
const dataDir = options.dataDir || getDataDir();
|
|
139
|
-
const expectedHash = options.expectedHash || pinnedBinary().sha256;
|
|
172
|
+
const expectedHash = options.expectedHash || pinnedBinary(process.platform, process.arch, backendChannel(dataDir)).sha256;
|
|
140
173
|
const spawnFn = options.spawnFn || spawn;
|
|
141
174
|
const { binPath, args, env } = prepareLaunch(dataDir, [expectedHash]);
|
|
142
175
|
|
|
@@ -236,12 +269,11 @@ function prepareLaunch(dataDir, trustedHashes, platform = process.platform) {
|
|
|
236
269
|
return { binPath, args, env, port: config.port, secrets };
|
|
237
270
|
}
|
|
238
271
|
|
|
239
|
-
// trustedHashes
|
|
240
|
-
// this platform, plus a local Argon build installed by `backend:install:argon`.
|
|
272
|
+
// trustedHashes accepts the selected release channel and any explicitly installed local build.
|
|
241
273
|
function trustedHashes(dataDir, platform, arch) {
|
|
242
274
|
const hashes = [];
|
|
243
275
|
try {
|
|
244
|
-
hashes.push(pinnedBinary(platform, arch).sha256);
|
|
276
|
+
hashes.push(pinnedBinary(platform, arch, backendChannel(dataDir)).sha256);
|
|
245
277
|
} catch {}
|
|
246
278
|
try {
|
|
247
279
|
const argon = JSON.parse(fs.readFileSync(path.join(dataDir, 'argon-build.json'), 'utf8'));
|
|
@@ -318,7 +350,7 @@ async function withLock(dataDir, fn, timeoutMs) {
|
|
|
318
350
|
|
|
319
351
|
// ensureBackend makes sure the local backend is installed and running, and returns how to reach
|
|
320
352
|
// it. A backend already answering with this setup's key is left alone. Otherwise a missing or
|
|
321
|
-
// untrusted binary is (re)installed from the
|
|
353
|
+
// untrusted binary is (re)installed from the selected release channel, config and secrets are
|
|
322
354
|
// created when missing, and the backend is started in the background (logging to backend.log)
|
|
323
355
|
// so it outlives the caller.
|
|
324
356
|
export async function ensureBackend(options = {}) {
|
|
@@ -344,7 +376,7 @@ export async function ensureBackend(options = {}) {
|
|
|
344
376
|
const trusted = trustedHashes(dataDir, platform, arch);
|
|
345
377
|
const binPath = binaryPath(dataDir, platform);
|
|
346
378
|
if (!fs.existsSync(binPath) || !trusted.includes(computeFileHash(binPath))) {
|
|
347
|
-
await installBackend({ dataDir, platform, arch, fetchFn });
|
|
379
|
+
await installBackend({ dataDir, platform, arch, fetchFn, channel: backendChannel(dataDir) });
|
|
348
380
|
} else {
|
|
349
381
|
ensureDataFiles(dataDir);
|
|
350
382
|
}
|
package/lib/task.mjs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { createForwarder, ensureService, getDefaultDataDir } from '../native-task/lib/runtime.mjs';
|
|
5
|
+
|
|
6
|
+
// The experimental Task route: on macOS, a sign-in can go to the native-task bridge (see
|
|
7
|
+
// native-task/), which runs Gemini.app's own Task mode, instead of the Gemini Web backend.
|
|
8
|
+
// The sign-in says which with metadata.mode.
|
|
9
|
+
|
|
10
|
+
export const TASK_MODE = 'task';
|
|
11
|
+
export const TASK_MODEL = 'gemini-task-argon';
|
|
12
|
+
|
|
13
|
+
const BRIDGE_CLI = fileURLToPath(new URL('../native-task/scripts/bridge.mjs', import.meta.url));
|
|
14
|
+
|
|
15
|
+
// taskSupported is whether this computer can run the bridge: it drives Gemini.app for macOS.
|
|
16
|
+
export function taskSupported(platform = process.platform) {
|
|
17
|
+
return platform === 'darwin';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isTaskAuth(auth) {
|
|
21
|
+
return auth?.type === 'api' && auth.metadata?.mode === TASK_MODE;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// startTask starts the bridge set up in dataDir. The bridge's setup needs files only a capture
|
|
25
|
+
// gives, so the plugin can't run it: without one, the error says how.
|
|
26
|
+
async function startTask(dataDir = getDefaultDataDir()) {
|
|
27
|
+
if (!fs.existsSync(path.join(dataDir, 'config.json'))) {
|
|
28
|
+
throw new Error(`还没有配置本机 Task 服务(${dataDir})。先在终端运行 node "${BRIDGE_CLI}" setup,需要的文件见 native-task/README.md`);
|
|
29
|
+
}
|
|
30
|
+
return ensureService({ dataDir });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// taskMethod is the Magpie sign-in method for the bridge.
|
|
34
|
+
export const taskMethod = {
|
|
35
|
+
type: 'oauth',
|
|
36
|
+
label: 'macOS 原生 Task(实验性,需先完成本机 setup)',
|
|
37
|
+
async authorize() {
|
|
38
|
+
const service = await startTask();
|
|
39
|
+
return {
|
|
40
|
+
url: '',
|
|
41
|
+
method: 'auto',
|
|
42
|
+
instructions: '',
|
|
43
|
+
callback: async () => ({
|
|
44
|
+
type: 'success',
|
|
45
|
+
key: service.apiKey,
|
|
46
|
+
accountId: `native:${service.instance_id}`,
|
|
47
|
+
metadata: { mode: TASK_MODE, baseURL: `${service.baseURL}/v1`, dataDir: service.dataDir }
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// taskLoader is the auth loader for a Task sign-in: requests go to the bridge, starting it when
|
|
54
|
+
// it isn't running.
|
|
55
|
+
export async function taskLoader(auth) {
|
|
56
|
+
const service = await startTask(auth.metadata?.dataDir);
|
|
57
|
+
if (auth.key !== service.apiKey) {
|
|
58
|
+
throw Object.assign(new Error('本机 Task 服务的密钥变了,请重新登录'), { signIn: 'expired' });
|
|
59
|
+
}
|
|
60
|
+
return { apiKey: auth.key, baseURL: `${service.baseURL}/v1`, fetch: createForwarder(service) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// taskModels is the one model the bridge serves, in the shape listModels gives.
|
|
64
|
+
export function taskModels(auth, providerID) {
|
|
65
|
+
return {
|
|
66
|
+
[TASK_MODEL]: {
|
|
67
|
+
id: TASK_MODEL,
|
|
68
|
+
providerID,
|
|
69
|
+
name: 'Argon Task',
|
|
70
|
+
api: { id: TASK_MODEL, url: auth.metadata?.baseURL, npm: '@ai-sdk/openai-compatible' },
|
|
71
|
+
limit: { context: 65536, output: 8192 },
|
|
72
|
+
capabilities: {
|
|
73
|
+
temperature: false,
|
|
74
|
+
reasoning: false,
|
|
75
|
+
attachment: false,
|
|
76
|
+
toolcall: true,
|
|
77
|
+
input: { text: true, image: false },
|
|
78
|
+
output: { text: true }
|
|
79
|
+
},
|
|
80
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
81
|
+
options: {},
|
|
82
|
+
headers: {},
|
|
83
|
+
variants: {},
|
|
84
|
+
status: 'active'
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 magpie-gemini-web contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|