magpie-gemini-web 0.4.0 → 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 +23 -17
- package/README.md +23 -17
- package/docs/backend-release.md +31 -0
- package/lib/backend-release.json +36 -0
- package/lib/native.mjs +50 -18
- package/native-task/README.md +3 -1
- package/package.json +5 -1
- 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.
|
|
@@ -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
|
|
|
@@ -79,32 +81,36 @@ So with a proxy pool, an account just added by one-click setup has no email yet.
|
|
|
79
81
|
|
|
80
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.
|
|
81
83
|
|
|
82
|
-
This is an alpha: it supports only the verified Gemini.app build, needs a local setup first (Gemini.app signed in, plus session templates),
|
|
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.
|
|
83
91
|
|
|
84
|
-
|
|
92
|
+
To roll back, stop the backend and run this from the plugin directory:
|
|
85
93
|
|
|
86
|
-
|
|
94
|
+
```bash
|
|
95
|
+
npm run backend:install:upstream
|
|
96
|
+
```
|
|
97
|
+
|
|
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:
|
|
87
99
|
|
|
88
100
|
```bash
|
|
89
|
-
|
|
90
|
-
git clone https://github.com/zexadev/gemini-web2api-go.git backend-source
|
|
91
|
-
git -C backend-source checkout a65b1d1a5ac614c1b455857334b4f1c73ab8fcfb
|
|
92
|
-
git -C backend-source apply ../patches/argon.patch
|
|
93
|
-
npm run backend:build:argon
|
|
94
|
-
npm run backend:install:argon
|
|
101
|
+
npm run backend:install
|
|
95
102
|
```
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
A failed download or checksum verification keeps the previous binary. On Windows, stop the old process before replacing its executable.
|
|
98
105
|
|
|
99
|
-
|
|
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:
|
|
100
107
|
|
|
101
108
|
```bash
|
|
102
|
-
|
|
103
|
-
git -C backend-source apply ../patches/argon.patch
|
|
104
|
-
npm run backend:build:argon && npm run backend:install:argon
|
|
105
|
-
kill $(cat ~/.local/share/magpie-gemini-web/backend.pid)
|
|
109
|
+
npm run backend:release:build
|
|
106
110
|
```
|
|
107
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
|
+
|
|
108
114
|
## License
|
|
109
115
|
|
|
110
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(网络)**,刷新页面;
|
|
@@ -48,7 +50,7 @@ magpie plugin options magpie-gemini-web '{"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
|
|
|
@@ -79,32 +81,36 @@ Magpie 的额度卡片会显示 Google 账号的 **5 小时** 和 **每周** 用
|
|
|
79
81
|
|
|
80
82
|
在 macOS 上登录时会多出一个「macOS 原生 Task(实验性)」选项:请求不走 Gemini Web 后端,而是交给本机常驻服务,由 Gemini.app 自己的 Task 模式执行,工具调用走原生的 Function Calling 通道,模型为 `gemini-task-argon`。Windows 和 Linux 上不会出现这个选项,请继续使用上面的一键配置。
|
|
81
83
|
|
|
82
|
-
当前为 Alpha:仅支持已验证的 Gemini.app 版本,选这个选项之前需要先在本机完成 setup(需要 Gemini.app
|
|
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`。
|
|
83
91
|
|
|
84
|
-
|
|
92
|
+
需要回退时,在插件目录停止后端后运行:
|
|
85
93
|
|
|
86
|
-
|
|
94
|
+
```bash
|
|
95
|
+
npm run backend:install:upstream
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
这会安装经过 SHA-256 校验的官方 v4.22.0,并记住回退选择,之后自动启动也使用官方版。恢复默认优化版:
|
|
87
99
|
|
|
88
100
|
```bash
|
|
89
|
-
|
|
90
|
-
git clone https://github.com/zexadev/gemini-web2api-go.git backend-source
|
|
91
|
-
git -C backend-source checkout a65b1d1a5ac614c1b455857334b4f1c73ab8fcfb
|
|
92
|
-
git -C backend-source apply ../patches/argon.patch
|
|
93
|
-
npm run backend:build:argon
|
|
94
|
-
npm run backend:install:argon
|
|
101
|
+
npm run backend:install
|
|
95
102
|
```
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
下载失败或校验不通过时保留旧二进制。Windows 上必须先停止旧进程,才能替换正在使用的可执行文件。
|
|
98
105
|
|
|
99
|
-
|
|
106
|
+
源码与构建记录随后端 Release 提供。开发者使用 Git、Node 22+、Go 1.27.1,可从本仓库复现全部六个平台的构建并核对哈希:
|
|
100
107
|
|
|
101
108
|
```bash
|
|
102
|
-
|
|
103
|
-
git -C backend-source apply ../patches/argon.patch
|
|
104
|
-
npm run backend:build:argon && npm run backend:install:argon
|
|
105
|
-
kill $(cat ~/.local/share/magpie-gemini-web/backend.pid)
|
|
109
|
+
npm run backend:release:build
|
|
106
110
|
```
|
|
107
111
|
|
|
112
|
+
构建脚本从固定的上游提交提取干净源码、应用 `patches/argon.patch`,不会修改本地 `backend-source` 的工作内容。发布流程见 [docs/backend-release.md](docs/backend-release.md)。
|
|
113
|
+
|
|
108
114
|
## 友链
|
|
109
115
|
|
|
110
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.
|
|
@@ -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/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/native-task/README.md
CHANGED
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
|
|
30
30
|
2026-10-15:已执行通过 41 项 Python 测试和 12 项 Node 测试;真实 Magpie `0.1.1244` 的隔离环境完成了插件安装、本地连接、模型发现及 8 个 HTTP 请求的工具往返测试。该网关测试使用模拟原生上游,包含两次串行工具、最终答案重试、错误会话/调用 ID 拒绝、旧工具阶段拒绝和下一轮用户消息。
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
2026-10-15:通过 `magpie-gemini-web` 0.4.0 的 Task 登录方式(直接加载插件,未经 Magpie 网关)完成了一次真实 Argon 工具往返:模型返回 `Read` 工具调用(19.2 秒),回传工具结果后得到正确的最终答案(13.7 秒)。此前真实 Task 请求曾被 Google 以 `Remy quota exhausted`(gRPC 8)拒绝,服务将该错误映射为 `native_quota_exhausted`,不会自动重发 Task。
|
|
33
|
+
|
|
34
|
+
已知问题:客户端收到工具调用后若不回传结果(例如 agent 中途被打断),该 Task 会一直占用唯一的名额,直到工具等待和任务超时到期(最长约 4 分钟),期间其他请求返回 409 `concurrency_limit`。另外曾出现过一次首个健康检查超时(2 秒),未能复现。
|
|
33
35
|
|
|
34
36
|
## Magpie 插件安装与登录
|
|
35
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magpie-gemini-web",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Gemini Web provider for Magpie, backed by gemini-web2api-go",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"lib/",
|
|
23
23
|
"scripts/backend.mjs",
|
|
24
24
|
"scripts/backend-argon.mjs",
|
|
25
|
+
"scripts/backend-release.mjs",
|
|
25
26
|
"patches/",
|
|
26
27
|
"native-task/lib/",
|
|
27
28
|
"native-task/bridge/",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"native-task/LICENSE",
|
|
33
34
|
"README.md",
|
|
34
35
|
"README.en.md",
|
|
36
|
+
"docs/backend-release.md",
|
|
35
37
|
"LICENSE",
|
|
36
38
|
"LICENSE-UPSTREAM"
|
|
37
39
|
],
|
|
@@ -50,6 +52,8 @@
|
|
|
50
52
|
"scripts": {
|
|
51
53
|
"test": "node --test test/*.test.mjs native-task/test/*.test.mjs",
|
|
52
54
|
"backend:install": "node scripts/backend.mjs install",
|
|
55
|
+
"backend:install:upstream": "node scripts/backend.mjs install-upstream",
|
|
56
|
+
"backend:release:build": "node scripts/backend-release.mjs --verify",
|
|
53
57
|
"backend:start": "node scripts/backend.mjs start",
|
|
54
58
|
"backend:status": "node scripts/backend.mjs status",
|
|
55
59
|
"backend:build:argon": "node scripts/backend-argon.mjs build",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Build from a clean, pinned upstream tree plus the checked-in patch. The developer's
|
|
3
|
+
// backend-source working tree is never changed or included in release artifacts.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
12
|
+
const pinPath = path.join(root, 'lib/backend-release.json');
|
|
13
|
+
const release = JSON.parse(fs.readFileSync(pinPath, 'utf8'));
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const writePins = args.includes('--write-pins');
|
|
16
|
+
const verify = args.includes('--verify');
|
|
17
|
+
const requested = args.find(arg => arg.startsWith('--target='))?.slice('--target='.length);
|
|
18
|
+
for (const arg of args) {
|
|
19
|
+
if (!['--write-pins', '--verify'].includes(arg) && !arg.startsWith('--target=')) throw new Error(`Unknown argument: ${arg}`);
|
|
20
|
+
}
|
|
21
|
+
const platforms = {
|
|
22
|
+
'darwin-arm64': ['darwin', 'arm64'],
|
|
23
|
+
'darwin-x64': ['darwin', 'amd64'],
|
|
24
|
+
'linux-x64': ['linux', 'amd64'],
|
|
25
|
+
'linux-arm64': ['linux', 'arm64'],
|
|
26
|
+
'win32-x64': ['windows', 'amd64'],
|
|
27
|
+
'win32-arm64': ['windows', 'arm64']
|
|
28
|
+
};
|
|
29
|
+
const target = requested === 'host' ? `${process.platform}-${process.arch}` : requested;
|
|
30
|
+
if (target && !platforms[target]) throw new Error(`Unsupported target: ${target}`);
|
|
31
|
+
if (writePins && (target || verify)) throw new Error('--write-pins requires a full build without --verify');
|
|
32
|
+
const targets = target ? [target] : Object.keys(platforms);
|
|
33
|
+
const output = path.join(root, 'backend-build', 'release');
|
|
34
|
+
fs.mkdirSync(output, { recursive: true });
|
|
35
|
+
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'magpie-backend-build-'));
|
|
36
|
+
const source = path.join(scratch, 'source');
|
|
37
|
+
fs.mkdirSync(source);
|
|
38
|
+
const env = { ...process.env, CGO_ENABLED: '0', GOFLAGS: '', GOEXPERIMENT: '',
|
|
39
|
+
GOTOOLCHAIN: `go${release.goVersion}`, GOAMD64: 'v1', GOARM64: 'v8.0' };
|
|
40
|
+
for (const name of ['ARGON_PROBE_COOKIE', 'GW2A_UPLOAD_PROBE', 'SQL_DSN', 'GOOS', 'GOARCH']) delete env[name];
|
|
41
|
+
const go = (argv, extraEnv = {}) => execFileSync('go', argv, { cwd: source, env: { ...env, ...extraEnv }, stdio: 'inherit' });
|
|
42
|
+
const hash = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
|
|
43
|
+
const patchPath = path.join(root, 'patches/argon.patch');
|
|
44
|
+
const flags = ['-trimpath', '-buildvcs=false', '-ldflags=-s -w'];
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
let upstream = path.join(root, 'backend-source');
|
|
48
|
+
try {
|
|
49
|
+
execFileSync('git', ['-C', upstream, 'cat-file', '-e', `${release.upstream.commit}^{commit}`], { stdio: 'pipe' });
|
|
50
|
+
} catch {
|
|
51
|
+
upstream = path.join(scratch, 'upstream');
|
|
52
|
+
execFileSync('git', ['init', '-q', upstream]);
|
|
53
|
+
execFileSync('git', ['-C', upstream, 'fetch', '--depth=1', release.upstream.repository, release.upstream.commit], { stdio: 'inherit' });
|
|
54
|
+
}
|
|
55
|
+
const archive = path.join(scratch, 'upstream.tar');
|
|
56
|
+
execFileSync('git', ['-C', upstream, 'archive', '--format=tar', `--output=${archive}`, release.upstream.commit]);
|
|
57
|
+
// Relative archive paths also work with Git for Windows' tar, which otherwise
|
|
58
|
+
// treats the drive letter in C:\\... as a remote archive host.
|
|
59
|
+
execFileSync('tar', ['-xf', 'upstream.tar', '-C', 'source'], { cwd: scratch });
|
|
60
|
+
execFileSync('git', ['apply', '--check', patchPath], { cwd: source, stdio: 'inherit' });
|
|
61
|
+
execFileSync('git', ['apply', patchPath], { cwd: source, stdio: 'inherit' });
|
|
62
|
+
const versionPath = path.join(source, 'internal/app/server.go');
|
|
63
|
+
const server = fs.readFileSync(versionPath, 'utf8');
|
|
64
|
+
const versionLine = /^const Version = "[^"]+"$/m;
|
|
65
|
+
if (!versionLine.test(server)) throw new Error('Backend version declaration not found');
|
|
66
|
+
fs.writeFileSync(versionPath, server.replace(versionLine, `const Version = ${JSON.stringify(release.version)}`));
|
|
67
|
+
console.log(`Testing ${release.version} from ${release.upstream.commit} with patches/argon.patch`);
|
|
68
|
+
go(['version']);
|
|
69
|
+
go(['test', './...', '-count=1']);
|
|
70
|
+
|
|
71
|
+
const binaries = {};
|
|
72
|
+
for (const key of targets) {
|
|
73
|
+
const [goos, goarch] = platforms[key];
|
|
74
|
+
const asset = `gemini-web2api-go_v${release.version}_${goos}_${goarch}${goos === 'windows' ? '.exe' : ''}`;
|
|
75
|
+
const file = path.join(output, asset);
|
|
76
|
+
console.log(`Building ${key}: ${asset}`);
|
|
77
|
+
go(['build', ...flags, '-o', file, '.'], { GOOS: goos, GOARCH: goarch });
|
|
78
|
+
const bytes = fs.readFileSync(file);
|
|
79
|
+
const sha256 = hash(bytes);
|
|
80
|
+
if (verify && (release.binaries[key]?.sha256 !== sha256 || release.binaries[key]?.asset !== asset)) {
|
|
81
|
+
throw new Error(`Release pin mismatch for ${key}: built ${sha256}`);
|
|
82
|
+
}
|
|
83
|
+
binaries[key] = { asset, sha256 };
|
|
84
|
+
if (key === `${process.platform}-${process.arch}`) {
|
|
85
|
+
const version = execFileSync(file, ['--version'], { encoding: 'utf8' }).trim();
|
|
86
|
+
if (version !== `gemini-web2api-go ${release.version}`) throw new Error(`Unexpected binary version: ${version}`);
|
|
87
|
+
console.log(version);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!target) {
|
|
92
|
+
const sourceName = `gemini-web2api-go_v${release.version}_source.tar.gz`;
|
|
93
|
+
const sourceArchive = fs.openSync(path.join(output, sourceName), 'w');
|
|
94
|
+
try {
|
|
95
|
+
execFileSync('tar', ['-czf', '-', '.'], { cwd: source, stdio: ['ignore', sourceArchive, 'inherit'] });
|
|
96
|
+
} finally {
|
|
97
|
+
fs.closeSync(sourceArchive);
|
|
98
|
+
}
|
|
99
|
+
fs.copyFileSync(path.join(root, 'LICENSE-UPSTREAM'), path.join(output, 'LICENSE-UPSTREAM'));
|
|
100
|
+
const build = { version: release.version, upstream: release.upstream, goVersion: release.goVersion,
|
|
101
|
+
patch: 'patches/argon.patch', patchSha256: hash(fs.readFileSync(patchPath)),
|
|
102
|
+
versionOverride: release.version, cgoEnabled: false, flags, binaries };
|
|
103
|
+
fs.writeFileSync(path.join(output, 'build.json'), `${JSON.stringify(build, null, 2)}\n`);
|
|
104
|
+
const assets = [...Object.values(binaries).map(b => b.asset), sourceName, 'LICENSE-UPSTREAM', 'build.json'].sort();
|
|
105
|
+
fs.writeFileSync(path.join(output, 'SHA256SUMS.txt'), assets.map(name => `${hash(fs.readFileSync(path.join(output, name)))} ${name}\n`).join(''));
|
|
106
|
+
if (writePins) fs.writeFileSync(pinPath, `${JSON.stringify({ ...release, binaries }, null, 2)}\n`);
|
|
107
|
+
}
|
|
108
|
+
console.log(`Release artifacts ready: ${output}`);
|
|
109
|
+
} finally {
|
|
110
|
+
fs.rmSync(scratch, { recursive: true, force: true });
|
|
111
|
+
}
|
package/scripts/backend.mjs
CHANGED
|
@@ -8,6 +8,9 @@ async function main() {
|
|
|
8
8
|
case 'install':
|
|
9
9
|
await installBackend();
|
|
10
10
|
break;
|
|
11
|
+
case 'install-upstream':
|
|
12
|
+
await installBackend({ channel: 'upstream' });
|
|
13
|
+
break;
|
|
11
14
|
case 'start':
|
|
12
15
|
startBackend();
|
|
13
16
|
break;
|
|
@@ -15,7 +18,7 @@ async function main() {
|
|
|
15
18
|
await statusBackend();
|
|
16
19
|
break;
|
|
17
20
|
default:
|
|
18
|
-
console.error('Usage: node scripts/backend.mjs <install|start|status>');
|
|
21
|
+
console.error('Usage: node scripts/backend.mjs <install|install-upstream|start|status>');
|
|
19
22
|
process.exit(1);
|
|
20
23
|
}
|
|
21
24
|
}
|