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 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 the backend, verifies it and starts it in the background.
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 and image input are off by default. Turn them on if a coding agent needs tools:
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 '{"allowTools":true,"allowImages":true}'
34
+ magpie plugin options magpie-gemini-web '{"allowImages":true}'
33
35
  ```
34
36
 
35
37
  | Option | Default | Description |
36
38
  | :--- | :--- | :--- |
37
- | `allowTools` | `false` | Allows tool calls (experimental). When off, requests with tools get a 400 |
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 plugin pins the official v4.22.0 backend binary and checks its SHA-256 on download and on every start. Builds are available for macOS, Linux and Windows on x64 and arm64.
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
- ## Argon patched build (optional)
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
- The official backend already supports `gemini-4-argon`, which needs a paid or Ultra account. This repo's patched build also verifies that responses really come from Argon, so a silent downgrade is caught, and it lowers latency. It also makes the backend's requests look more like a browser's: on a direct connection every request carries the full set of Chrome headers (the official build's direct chat requests say `Go-http-client/2.0`), and refreshing the cookies to keep a session alive no longer fetches the `/app` page right away. Building it requires Go 1.26+:
92
+ To roll back, stop the backend and run this from the plugin directory:
81
93
 
82
94
  ```bash
83
- git clone https://github.com/BUKOWSKIREAL/magpie-gemini-web.git && cd magpie-gemini-web
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
- Restart Magpie when the install finishes, and the plugin starts the Argon build in the background. If an older backend is still running, stop it first with `kill $(cat ~/.local/share/magpie-gemini-web/backend.pid)`. Run `npm run backend:install` to switch back to the official build.
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
- When `patches/argon.patch` changes (after you pull a new version of this repo, say), apply it again to clean sources and rebuild. Once the old backend is stopped, the plugin starts the new one on the next request:
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
- git -C backend-source checkout -- . && git -C backend-source clean -fdq
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
- 工具调用和图片输入默认关闭,Coding Agent 需要工具时请开启:
31
+ 工具调用默认开启(Coding Agent 每个请求都带工具),图片输入默认关闭。需要图片输入时:
30
32
 
31
33
  ```bash
32
- magpie plugin options magpie-gemini-web '{"allowTools":true,"allowImages":true}'
34
+ magpie plugin options magpie-gemini-web '{"allowImages":true}'
33
35
  ```
34
36
 
35
37
  | 选项 | 默认值 | 说明 |
36
38
  | :--- | :--- | :--- |
37
- | `allowTools` | `false` | 允许工具调用(实验性)。关闭时带工具的请求会返回 400 |
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
- - 后端二进制锁定官方 v4.22.0,下载和每次启动都校验 SHA-256。支持 macOS、Linux、Windows(x64 / arm64)。
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
- ## Argon 增强版(可选)
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
- 官方后端已支持 `gemini-4-argon`(需付费 / Ultra 账号)。本仓库的补丁版额外校验返回的确实是 Argon(防止被静默降级),并降低延迟。它也让后端的请求更像浏览器:直连时每个请求都带齐 Chrome 请求头(官方版直连时,对话请求的 UA 是 `Go-http-client/2.0`),保活刷新 Cookie 后也不再立即重新抓取 `/app` 页面。需要 Go 1.26+:
92
+ 需要回退时,在插件目录停止后端后运行:
81
93
 
82
94
  ```bash
83
- git clone https://github.com/BUKOWSKIREAL/magpie-gemini-web.git && cd magpie-gemini-web
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
- 安装后重启 Magpie 即可,插件会在后台启动 Argon 版(如有旧后端在运行,先 `kill $(cat ~/.local/share/magpie-gemini-web/backend.pid)`)。运行 `npm run backend:install` 可切回官方版。
98
+ 这会安装经过 SHA-256 校验的官方 v4.22.0,并记住回退选择,之后自动启动也使用官方版。恢复默认优化版:
92
99
 
93
- `patches/argon.patch` 更新后(比如拉取了本仓库的新版本),需要在干净的源码上重新应用补丁并重新构建。停掉旧后端后,插件会在下一个请求时启动新版:
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
- git -C backend-source checkout -- . && git -C backend-source clean -fdq
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
- let allowTools = false;
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, 'Experimental tools are disabled; set allowTools: true');
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, 'Image input is disabled; set allowImages: true');
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 = 'v4.22.0';
8
- const RELEASE_BASE = `https://github.com/zexadev/gemini-web2api-go/releases/download/${BACKEND_VERSION}/`;
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
- // Upstream release assets by `${process.platform}-${process.arch}`, SHA-256 from the release's SHA256SUMS.txt.
11
- export const PINNED_BINARIES = {
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
- const pin = PINNED_BINARIES[`${platform}-${arch}`];
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 ${BACKEND_VERSION} build for ${platform}/${arch}. Download one manually from https://github.com/zexadev/gemini-web2api-go/releases/tag/${BACKEND_VERSION}`);
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 pin = options.expectedHash && options.url ? null : pinnedBinary(platform, arch);
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
- return { binPath, ...ensureDataFiles(dataDir) };
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 is what a backend binary in dataDir may hash to: the pinned upstream build for
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 pinned upstream release, config and secrets are
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.