magpie-gemini-web 0.10.0 → 0.11.0
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 +4 -4
- package/README.md +4 -4
- package/docs/backend-release.md +1 -1
- package/docs/studio-release.md +31 -8
- package/lib/backend-release.json +15 -15
- package/lib/native.mjs +7 -7
- package/lib/studio-native.mjs +1 -1
- package/lib/studio-release.json +45 -19
- package/package.json +1 -1
- package/patches/argon.patch +105 -72
- package/patches/studio-0.2.17-preserve-request-bodies.patch +55 -25
- package/patches/studio-0.2.19-managed-config.patch +15 -7
- package/scripts/backend-argon.mjs +2 -2
- package/scripts/studio-managed-smoke.mjs +3 -2
- package/scripts/studio-release.mjs +17 -7
package/README.en.md
CHANGED
|
@@ -21,7 +21,7 @@ The plugin provides two independent providers that can be used individually or t
|
|
|
21
21
|
| **Image generation** | Nano Banana models, for Magpie's image generation (Magpie v0.1.2075+, see below) | Not supported |
|
|
22
22
|
| **Sign-in method** | Sign in directly in an isolated popup browser window | Copy `cookie` from a private/incognito window |
|
|
23
23
|
| **Usage cards** | Shows models currently on cooldown and recovery times | Syncs **5-hour** & **weekly** usage and reset times from the web |
|
|
24
|
-
| **Local backend** | AIStudio2API (`v0.3.
|
|
24
|
+
| **Local backend** | AIStudio2API (`v0.3.2`) | gemini-web2api-go (`4.27.1-magpie.1` optimized build) |
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -176,7 +176,7 @@ When a plugin error asks you to run a command, it includes the full ready-to-cop
|
|
|
176
176
|
- Gemini Web takes at most 2 requests at once per sign-in (AI Studio 1), adjustable in Magpie's provider settings. Google account rate-limiting or flagging cannot be completely ruled out—use at your own risk.
|
|
177
177
|
|
|
178
178
|
**AI Studio upgrades and model selection:**
|
|
179
|
-
- The plugin pins SHA-256-verified `v0.2
|
|
179
|
+
- The plugin pins SHA-256-verified `v0.3.2-magpie.1`, based on upstream `v0.3.2`. Upgrade the plugin to update the backend. Official releases at or below that upstream version, and managed backends an older plugin installed, are upgraded on the next managed startup, preserving accounts, port and sign-in credentials.
|
|
180
180
|
- Account bindings and hedge model selections from 0.2.19 are preserved. Settings saved in the admin page, including upstream defaults, survive plugin restarts. Hedge requests remain off by default; enable them and select models in the admin page if needed.
|
|
181
181
|
- For users upgrading from 0.2.18 with `auto` enabled, hedging now applies only to `FIRST_EVENT_HEDGE_MODELS`, defaulting to Argon when that list is absent. Saving in the admin page fixes the current defaults in the file; later upstream default changes require manual adjustment. When taking over an official 0.2.19 sparse configuration, missing settings follow the plugin's initialization rules (for example, hedging becomes `off`). See the [upgrade compatibility notes](docs/studio-release.md#0219-升级兼容).
|
|
182
182
|
- **Online updates are disabled in the managed backend on macOS, Linux, and Windows.** The admin page says updates are managed by magpie-gemini-web; the update endpoint returns HTTP 409 and update checks do not contact GitHub. Independently managed external servers are unaffected. See [docs/studio-release.md](docs/studio-release.md) for how the managed build is made and reproduced.
|
|
@@ -189,11 +189,11 @@ When a plugin error asks you to run a command, it includes the full ready-to-cop
|
|
|
189
189
|
- Models with `-build` or `assistant_route` are filtered out: they inject the Build coding assistant's own system prompt and are not offered as ordinary Agent models. Refresh with `magpie provider models ai-studio` after upgrading.
|
|
190
190
|
|
|
191
191
|
**Gemini Web optimized backend upgrades & rollback:**
|
|
192
|
-
- One-click setup uses [4.27.
|
|
192
|
+
- One-click setup uses [4.27.1-magpie.1](https://github.com/BUKOWSKIREAL/magpie-gemini-web/releases/tag/backend-v4.27.1-magpie.1) by default (built from pinned upstream v4.27.1 with XSRF caching, request cancellation propagation, early tool-call completion, and Argon model verification). Chat requests are timed by silence: while Google queues a request it sends a heartbeat every 60 s, and the backend waits through it for the first words. A try is given up, and retried, only after Google has sent nothing for `request_timeout_sec` (180 s by default), so a long reply that keeps coming is never cut off either.
|
|
193
193
|
- **Upgrading:** Running backends are not interrupted automatically. Stop the existing process (PID in `backend.pid` inside the data directory); the next Magpie launch or request will upgrade the binary while preserving config and cookies.
|
|
194
194
|
- **Rollback to upstream / restore optimized build** (run in the plugin directory after stopping the backend):
|
|
195
195
|
```bash
|
|
196
|
-
npm run backend:install:upstream # Roll back and pin to official v4.27.
|
|
196
|
+
npm run backend:install:upstream # Roll back and pin to official v4.27.1
|
|
197
197
|
npm run backend:install # Restore default optimized build
|
|
198
198
|
```
|
|
199
199
|
- For reproducible builds and release procedures, see [docs/backend-release.md](docs/backend-release.md).
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
| **图片生成** | Nano Banana 系列模型,供 Magpie 的图片生成使用(需要 Magpie v0.1.2075+,见下文) | 不支持 |
|
|
22
22
|
| **登录方式** | 弹出独立浏览器窗口直接登录 Google | 用无痕窗口登录并复制 `cookie` |
|
|
23
23
|
| **额度卡片** | 显示冷却中的模型与恢复时间 | 同步网页版 **5 小时** 与 **每周** 用量及重置时间 |
|
|
24
|
-
| **本地后端** | AIStudio2API (`v0.3.
|
|
24
|
+
| **本地后端** | AIStudio2API (`v0.3.2`) | gemini-web2api-go (`4.27.1-magpie.1` 优化版) |
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -176,7 +176,7 @@ magpie plugin options magpie-gemini-web '{"studio":{"proxy":"http://127.0.0.1:78
|
|
|
176
176
|
- Gemini Web 每个登录同时最多 2 个请求(AI Studio 为 1),可在 Magpie 的 provider 设置里调整;仍无法完全排除 Google 账号风控风险,请自行评估使用。
|
|
177
177
|
|
|
178
178
|
**AI Studio 升级与模型范围:**
|
|
179
|
-
- 插件固定使用经过 SHA-256 校验的 `v0.2
|
|
179
|
+
- 插件固定使用经过 SHA-256 校验的 `v0.3.2-magpie.1`(基于上游 `v0.3.2`),通过升级插件更新后端。同基线或更旧的官方版、以及旧版插件装的托管后端,在下次管理启动时自动升级,保留账号、端口与登录凭据。
|
|
180
180
|
- 0.2.19 的账号绑定与备用请求模型设置会保留;管理页保存的默认值不会在重启后被改回插件默认值。备用请求默认关闭,需要时可在管理页开启并选择模型。
|
|
181
181
|
- 从 0.2.18 升级且已开启 `auto` 时,备用请求从所有模型缩为 `FIRST_EVENT_HEDGE_MODELS` 中的模型,未配置该列表时仅有 Argon。管理页完整保存会固定当时的默认值,后续上游默认值变化需要手动调整。接管官方 0.2.19 的稀疏配置时,缺失项会按插件规则补齐(例如备用请求变为 `off`);详见[升级兼容说明](docs/studio-release.md#0219-升级兼容)。
|
|
182
182
|
- **托管后端已在 macOS、Linux、Windows 统一禁用在线更新**:管理页提示“更新由 magpie-gemini-web 管理”,更新接口返回 HTTP 409,检查更新也不会访问 GitHub。用户自行连接的外部服务不受影响。托管构建方式和可复现构建流程见 [docs/studio-release.md](docs/studio-release.md)。
|
|
@@ -189,11 +189,11 @@ magpie plugin options magpie-gemini-web '{"studio":{"proxy":"http://127.0.0.1:78
|
|
|
189
189
|
- 模型列表过滤 `-build` / `assistant_route` 模型:这类模型附带 Build 写代码助手的系统提示词,不作为普通 Agent 模型提供。升级后可执行 `magpie provider models ai-studio` 刷新列表。
|
|
190
190
|
|
|
191
191
|
**Gemini Web 优化后端升级与回退:**
|
|
192
|
-
- 插件默认使用本仓库发布的 [4.27.
|
|
192
|
+
- 插件默认使用本仓库发布的 [4.27.1-magpie.1](https://github.com/BUKOWSKIREAL/magpie-gemini-web/releases/tag/backend-v4.27.1-magpie.1)(基于上游 v4.27.1 集成 XSRF 缓存、请求取消传播、工具调用提前收尾与 Argon 校验补丁)。对话请求按上游沉默计时:Google 让请求排队时每 60 秒发一次心跳,后端会一直等到出字;连续 `request_timeout_sec`(默认 180 秒)没有任何数据才放弃这一次并重试,还在出字的长回复也不会被截断。
|
|
193
193
|
- **旧版升级**:插件不会打断运行中的后端。先停止旧进程(PID 见数据目录 `backend.pid`),下次启动 Magpie 或发请求时会自动更新二进制并保留原有配置与账号。
|
|
194
194
|
- **回退官方版 / 恢复优化版**(停止后端后在插件目录执行):
|
|
195
195
|
```bash
|
|
196
|
-
npm run backend:install:upstream # 回退并固定使用官方 v4.27.
|
|
196
|
+
npm run backend:install:upstream # 回退并固定使用官方 v4.27.1
|
|
197
197
|
npm run backend:install # 恢复默认优化版
|
|
198
198
|
```
|
|
199
199
|
- 源码复现构建与发布流程见 [docs/backend-release.md](docs/backend-release.md)。
|
package/docs/backend-release.md
CHANGED
|
@@ -30,4 +30,4 @@ Existing running processes are left alone to avoid interrupting requests. Once a
|
|
|
30
30
|
|
|
31
31
|
New cookie imports require the running backend to advertise `capabilities.account_proxy_binding: true` at `/`. The plugin checks this before changing the proxy pool and again before submitting a cookie, so a reused older or upstream backend cannot silently ignore `proxy_id`. Unsupported builds receive instructions to stop the old process and run the installed plugin's CLI with `install --data-dir=<path>`. This explicitly selects the optimized channel, including for previous upstream or custom-build installations. The check does not interrupt existing conversations. Release smoke verifies the capability and actual proxy selection; version strings alone do not establish support.
|
|
32
32
|
|
|
33
|
-
An explicit `npm run backend:install:upstream` installs the pinned upstream v4.27.
|
|
33
|
+
An explicit `npm run backend:install:upstream` installs the pinned upstream v4.27.1 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/docs/studio-release.md
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
# AI Studio 托管后端构建
|
|
2
2
|
|
|
3
|
-
插件使用 `0.3.
|
|
3
|
+
插件使用 `0.3.2-magpie.1`,基于 AIStudio2API `v0.3.2`(`4c44cb9765aa9fd497b91db1f5c1573504b9b85f`)。上游版本、commit、Go 版本、C/C++ 工具链、SpiderMonkey 与 libwebp 静态库(`toolchains`)、补丁列表、安装包及二进制 SHA-256 均记录在 `lib/studio-release.json`。`upstream.camoufox` 是这个 commit 要求的 Camoufox 版本(上游 `internal/camoufoxnative/download.go` 里的 `camoufoxRelease`),更新上游 commit 时要一起核对,构建脚本发现不一致会报错。MIT 许可见 `LICENSE-AISTUDIO`。
|
|
4
4
|
|
|
5
|
-
## 0.3.
|
|
5
|
+
## 0.3.2 升级兼容
|
|
6
|
+
|
|
7
|
+
- 基线为上游 `v0.3.2`(`4c44cb9765aa9fd497b91db1f5c1573504b9b85f`)。包含安全/拒答原因透传与重试、Build 模型工具调用续读、每日额度冷却回退、Omni 内联图片与视频、附件预览和视频抽帧设置,以及长文本、Worker 冷启动和 Camoufox 界面运行性能修复。Camoufox、SpiderMonkey、libwebp 和固定编译器工具链沿用上一版。
|
|
8
|
+
- 新增 `HIDE_DOCK_ICON`:插件保留管理页保存的值,并隔离宿主环境同名变量;该设置只影响上游桌面版,托管构建仍不带 `desktop` 标签。
|
|
9
|
+
- 请求正文迁移补丁与托管配置补丁均原样适用于 `v0.3.2`;真实安装冒烟增加 Dock 图标设置保存与重启持久化检查。
|
|
10
|
+
|
|
11
|
+
## 0.3.1 升级兼容(历史记录)
|
|
12
|
+
|
|
13
|
+
- 基线为上游 `v0.3.1`(`80847a7665c442a238918c957963217773f36291`)。包含 AVX2/NEON 汇编图片与音频编码加速、libwebp 1.6.0 有损 WebP 编码、多 choice/图片生成/Gemini 生成结果复制指向已释放栈的崩溃修复、预热登录续签与模型目录同步互等修复、10 秒流式心跳与 `X-Accel-Buffering: no`、YouTube 链接误识别与代码文本附件修复、账户时区不可见字符清理,以及到期资源与媒体目录上限回收。Camoufox、SpiderMonkey 静态库和固定编译器工具链沿用上一版。
|
|
14
|
+
- 新增 `toolchains.libwebp`(`Mag1cFall/libwebp` 的 `v1.6.0-aistudio2api.1` release),与 SpiderMonkey 一样按目标下载到 `studio-build/toolchains/` 并校验 SHA-256;头文件统一经 `CGO_CPPFLAGS` 传入以保留 `-O2`,与上游 `.github/actions/static-libraries` 一致。
|
|
15
|
+
- `patches/studio-0.2.17-preserve-request-bodies.patch` 适配上游 `sqlitedb.Open(path, schema)` 的表结构同步:在建表后、清理未定义旧表前执行 `migrateLegacyBodies`,确保从 0.2.16 及更早版本升级时先完成 `bodies` 事务迁移再删除旧表,迁移失败仍原子回滚。
|
|
16
|
+
- `patches/studio-0.2.19-managed-config.patch` 适配上游 `v0.3.1` 的 `WAA_BACKEND` 回写与回退分支:托管构建(`internal/config.managedBy=magpie-gemini-web`)在 `Load` 时不根据旧账户目录自动切换为 `camoufox` 或向 `.env` 追加键,在 `Save` 时继续完整写出全部非环境变量控制项。
|
|
17
|
+
|
|
18
|
+
## 0.3.0 升级兼容(历史记录)
|
|
6
19
|
|
|
7
20
|
- 基线为上游 `v0.3.0` 加 PR #109(`48f75fa722b25d22a493e75f6d4e5f9d5035d7e3`)。包含账号异步任务流、本机 Chrome / Cookie / CDP 浏览器账号导入、超过 63 字符的工具函数名自动压缩还原、`STRIP_THOUGHT_SIGNATURES`、Responses / Interactions / 音频状态持久化(`runtime/states.db`)、`gemini-live-2.5-flash` 与 `gemini-2.5-pro-tts` 等模型更新,以及上游默认 `WAA_BACKEND=spidermonkey`。Camoufox、SpiderMonkey 静态库和固定工具链沿用上一版。
|
|
8
21
|
- 后台账户任务适配:上游 `POST /api/accounts` 与 `POST /api/accounts/{id}/login` 改为启动不随 HTTP 请求取消的后台任务(返回 `202 Accepted` 与 `{ task }`)。插件与 CLI 通过 `studioLogin` 轮询 `GET /api/accounts/tasks` 等待完成,再从 `GET /api/accounts` 读取账号,并在结束或超时退出时调用 `DELETE /api/accounts/tasks/{id}` 清理或取消后台任务(超时会自动关闭弹出的 Camoufox 登录窗口)。
|
|
@@ -36,7 +49,7 @@
|
|
|
36
49
|
|
|
37
50
|
上游从 v0.2.14 起原生支持分发方管理更新。构建时通过 `-ldflags` 写入以下值:
|
|
38
51
|
|
|
39
|
-
- `internal/app.version=v0.3.
|
|
52
|
+
- `internal/app.version=v0.3.2-magpie.1`:明确的版本标识。同基线或更旧的官方版、以及更旧的托管版需要升级;更新的官方版、更新的托管版或不认识的定制版仍提示冲突。
|
|
40
53
|
- `internal/app.managedBy=magpie-gemini-web`:`GET /api/update`(包括 `?refresh=1`)只返回本地版本、`mode: managed` 和 `managed_by`,不访问 GitHub;`POST /api/update` 返回 HTTP 409,不下载、不替换文件、不重启。管理页显示“更新由 magpie-gemini-web 管理”。
|
|
41
54
|
|
|
42
55
|
`internal/config.managedBy=magpie-gemini-web` 同时启用上面的配置兼容补丁。
|
|
@@ -113,7 +126,17 @@ AI_STUDIO_TEST_ARCHIVE=/absolute/path/to/archive.tar.gz npm test
|
|
|
113
126
|
|
|
114
127
|
真实安装验证检查旧版请求正文迁移及重启后可读、管理页保存的备用请求设置、启动、版本、GET 更新状态、POST 更新返回 409、二进制及 PID 不变、管理页面显示托管提示、停止与状态。`.github/workflows/studio.yml` 在五种系统/架构的原生 runner 上重新构建、对比 SHA-256 并运行该验证;本机交叉编译不能代替 Linux/Windows 真机验证。
|
|
115
128
|
|
|
116
|
-
## 0.3.
|
|
129
|
+
## 0.3.2 升级验证(2026-11-08)
|
|
130
|
+
|
|
131
|
+
- 五个平台的候选构建([run 42443570134](https://github.com/BUKOWSKIREAL/magpie-gemini-web/actions/runs/42443570134))通过上游测试、真实安装冒烟验证(含旧版 `bodies` 表迁移、Dock 图标设置保存与重启持久化、SpiderMonkey 与 libwebp 静态链接、管理页配置持久化)及插件测试;安装包和二进制 pin 来自该次构建。
|
|
132
|
+
- 随后的严格验证构建([run 42444479121](https://github.com/BUKOWSKIREAL/magpie-gemini-web/actions/runs/42444479121))在五个平台的全新 runner 上重新构建,安装包与可执行文件哈希均和 pin 完全一致,真实安装冒烟验证与插件测试全部通过。
|
|
133
|
+
|
|
134
|
+
## 0.3.1 升级验证(2026-11-07,历史记录)
|
|
135
|
+
|
|
136
|
+
- 五个平台的候选构建([run 42255835006](https://github.com/BUKOWSKIREAL/magpie-gemini-web/actions/runs/42255835006))通过上游测试、真实安装冒烟验证(含旧版 `bodies` 表迁移、SpiderMonkey 与 libwebp 静态链接、管理页配置持久化)及插件测试;安装包和二进制 pin(含在固定 Xcode 26.3 `17C529` 下生成的 macOS 产物)来自该次构建。
|
|
137
|
+
- 随后的严格验证构建([run 42258671953](https://github.com/BUKOWSKIREAL/magpie-gemini-web/actions/runs/42258671953))在五个平台的全新 runner 上重新构建,哈希与 pin 完全一致,真实安装冒烟验证与插件测试全部通过。
|
|
138
|
+
|
|
139
|
+
## 0.3.0 升级验证(2026-11-05,历史记录)
|
|
117
140
|
|
|
118
141
|
- 五个平台的候选构建([run 41976798432](https://github.com/BUKOWSKIREAL/magpie-gemini-web/actions/runs/41976798432))一次性通过上游测试、真实安装冒烟验证(含 `strip_thought_signatures=false`、`/api/accounts/tasks` 接口与管理页配置持久化)及插件测试;安装包和二进制 pin 来自该次构建。
|
|
119
142
|
- 随后的严格验证构建([run 41977975873](https://github.com/BUKOWSKIREAL/magpie-gemini-web/actions/runs/41977975873))在五个平台的全新 runner 上重新构建,哈希与 pin 完全一致,真实安装冒烟验证与插件测试全部通过。
|
|
@@ -149,14 +172,14 @@ AI_STUDIO_TEST_ARCHIVE=/absolute/path/to/archive.tar.gz npm test
|
|
|
149
172
|
- 新版 Google 登录/生成端到端验证尚未完成。
|
|
150
173
|
- 此分支未发布后端 release 或 npm 包。本机日常后端仍使用 `0.2.16-magpie.1`,新版仅在临时数据目录运行。
|
|
151
174
|
|
|
152
|
-
发布前必须审查各平台 CI 与构建记录,再将安装包及许可证上传到本仓库 `studio-v0.3.
|
|
175
|
+
发布前必须审查各平台 CI 与构建记录,再将安装包及许可证上传到本仓库 `studio-v0.3.2-magpie.1` release。该 release 的安装包可公开下载后,才能发布对应 npm 版本,否则新安装会遇到 404。合并 PR 不代表已经满足发布条件;构建脚本及验证 CI 不自动发布 release 或 npm 包。
|
|
153
176
|
|
|
154
177
|
本地试用建议使用独立数据目录:
|
|
155
178
|
|
|
156
179
|
```bash
|
|
157
|
-
node scripts/studio.mjs install --data-dir /path/to/studio-
|
|
158
|
-
node scripts/studio.mjs start --data-dir /path/to/studio-
|
|
159
|
-
node scripts/studio.mjs stop --data-dir /path/to/studio-
|
|
180
|
+
node scripts/studio.mjs install --data-dir /path/to/studio-031-trial --archive /absolute/path/to/archive
|
|
181
|
+
node scripts/studio.mjs start --data-dir /path/to/studio-031-trial
|
|
182
|
+
node scripts/studio.mjs stop --data-dir /path/to/studio-031-trial
|
|
160
183
|
```
|
|
161
184
|
|
|
162
185
|
使用现有账号数据测试前,先停止旧服务并备份;不要让两个进程同时写同一个数据目录。
|
package/lib/backend-release.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.27.
|
|
2
|
+
"version": "4.27.1-magpie.1",
|
|
3
3
|
"repository": "BUKOWSKIREAL/magpie-gemini-web",
|
|
4
|
-
"tag": "backend-v4.27.
|
|
4
|
+
"tag": "backend-v4.27.1-magpie.1",
|
|
5
5
|
"upstream": {
|
|
6
6
|
"repository": "https://github.com/zexadev/gemini-web2api-go.git",
|
|
7
|
-
"commit": "
|
|
7
|
+
"commit": "c8a4ac25768b668fa94d293fcb97059c4419b602"
|
|
8
8
|
},
|
|
9
9
|
"goVersion": "1.27.1",
|
|
10
10
|
"binaries": {
|
|
11
11
|
"darwin-arm64": {
|
|
12
|
-
"asset": "gemini-web2api-go_v4.27.
|
|
13
|
-
"sha256": "
|
|
12
|
+
"asset": "gemini-web2api-go_v4.27.1-magpie.1_darwin_arm64",
|
|
13
|
+
"sha256": "a0828624c3301e79a36461bf1197d87ad0836d26262bea69fd9255d018eccac2"
|
|
14
14
|
},
|
|
15
15
|
"darwin-x64": {
|
|
16
|
-
"asset": "gemini-web2api-go_v4.27.
|
|
17
|
-
"sha256": "
|
|
16
|
+
"asset": "gemini-web2api-go_v4.27.1-magpie.1_darwin_amd64",
|
|
17
|
+
"sha256": "4b2a37dceefe7ed7f055f4c92f1038ccd3671c28bd612a4251a339a1c12dd975"
|
|
18
18
|
},
|
|
19
19
|
"linux-x64": {
|
|
20
|
-
"asset": "gemini-web2api-go_v4.27.
|
|
21
|
-
"sha256": "
|
|
20
|
+
"asset": "gemini-web2api-go_v4.27.1-magpie.1_linux_amd64",
|
|
21
|
+
"sha256": "bbded68869c0221868dcd81b96bdd05dce4afea0f4073e9ef4ed2b77a95f66d0"
|
|
22
22
|
},
|
|
23
23
|
"linux-arm64": {
|
|
24
|
-
"asset": "gemini-web2api-go_v4.27.
|
|
25
|
-
"sha256": "
|
|
24
|
+
"asset": "gemini-web2api-go_v4.27.1-magpie.1_linux_arm64",
|
|
25
|
+
"sha256": "95e9ca87c5bc64c59a54d86be31425afa775b4e6ba367fc2eec652e096151bfa"
|
|
26
26
|
},
|
|
27
27
|
"win32-x64": {
|
|
28
|
-
"asset": "gemini-web2api-go_v4.27.
|
|
29
|
-
"sha256": "
|
|
28
|
+
"asset": "gemini-web2api-go_v4.27.1-magpie.1_windows_amd64.exe",
|
|
29
|
+
"sha256": "4da25f05694e7d70d001b1d9665024ec2c98dd8714e310823a5d09d513fe8e02"
|
|
30
30
|
},
|
|
31
31
|
"win32-arm64": {
|
|
32
|
-
"asset": "gemini-web2api-go_v4.27.
|
|
33
|
-
"sha256": "
|
|
32
|
+
"asset": "gemini-web2api-go_v4.27.1-magpie.1_windows_arm64.exe",
|
|
33
|
+
"sha256": "782891140d65f7f364472ba2263e8498cf0b86872881246b3e5612ee74d06447"
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
package/lib/native.mjs
CHANGED
|
@@ -11,16 +11,16 @@ export const BACKEND_VERSION = `v${release.version}`;
|
|
|
11
11
|
const RELEASE_BASE = `https://github.com/${release.repository}/releases/download/${release.tag}/`;
|
|
12
12
|
export const PINNED_BINARIES = release.binaries;
|
|
13
13
|
|
|
14
|
-
const UPSTREAM_VERSION = 'v4.27.
|
|
14
|
+
const UPSTREAM_VERSION = 'v4.27.1';
|
|
15
15
|
const UPSTREAM_BASE = `https://github.com/zexadev/gemini-web2api-go/releases/download/${UPSTREAM_VERSION}/`;
|
|
16
16
|
// Retained for an explicit, checksum-verified rollback to the upstream release.
|
|
17
17
|
export const UPSTREAM_BINARIES = {
|
|
18
|
-
'darwin-arm64': { asset: 'gemini-web2api-go_v4.27.
|
|
19
|
-
'darwin-x64': { asset: 'gemini-web2api-go_v4.27.
|
|
20
|
-
'linux-x64': { asset: 'gemini-web2api-go_v4.27.
|
|
21
|
-
'linux-arm64': { asset: 'gemini-web2api-go_v4.27.
|
|
22
|
-
'win32-x64': { asset: 'gemini-web2api-go_v4.27.
|
|
23
|
-
'win32-arm64': { asset: 'gemini-web2api-go_v4.27.
|
|
18
|
+
'darwin-arm64': { asset: 'gemini-web2api-go_v4.27.1_darwin_arm64', sha256: 'bbb438995debb6e63d1838972477ba478b8a86c038fa8093836b3a492b9178c0' },
|
|
19
|
+
'darwin-x64': { asset: 'gemini-web2api-go_v4.27.1_darwin_amd64', sha256: '6aeb9683c379ed95af1611c48531e4d6464d6061889b97e763e977aa30a05ee3' },
|
|
20
|
+
'linux-x64': { asset: 'gemini-web2api-go_v4.27.1_linux_amd64', sha256: '8e0e3df2de1881e35d8ce2395be95e94e5d0653a1013be5bcab5821fa236b5ac' },
|
|
21
|
+
'linux-arm64': { asset: 'gemini-web2api-go_v4.27.1_linux_arm64', sha256: 'e0ba98effeae4054c3571d9f0df6f0815eac03727ca0bffbc3a1a69c9a896b7a' },
|
|
22
|
+
'win32-x64': { asset: 'gemini-web2api-go_v4.27.1_windows_amd64.exe', sha256: '3d89067af3f68e812be4026d0a9625aea55a3734da154ff0a29276714128025b' },
|
|
23
|
+
'win32-arm64': { asset: 'gemini-web2api-go_v4.27.1_windows_arm64.exe', sha256: 'eeabd5e8de76773140400f5e8dbd234baeb3bc6f5938464ecbdf7cbe09ffaf56' }
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export function pinnedBinary(platform = process.platform, arch = process.arch, channel = 'optimized') {
|
package/lib/studio-native.mjs
CHANGED
|
@@ -195,7 +195,7 @@ const ENV_KEYS = [
|
|
|
195
195
|
'AISTUDIO_AUTH_STATES', 'LISTEN_ADDR', 'PREVIEW_LISTEN_ADDR', 'PROXY_API_KEY', 'API_KEY_ACCOUNTS', 'PROXY', 'INIT_TIMEOUT', 'REQUEST_TIMEOUT',
|
|
196
196
|
'WARM_WORKER_LIMIT', 'MAX_ACTIVE_WORKERS', 'WARM_STARTUP_CONCURRENCY', 'PER_ACCOUNT_CONCURRENCY',
|
|
197
197
|
'ROUTING_STRATEGY', 'UPSTREAM_CHANNELS', 'MODEL_MAPPING', 'MODEL_CHANNELS', 'WAA_BACKEND',
|
|
198
|
-
'MEDIA_OUTPUT', 'MEDIA_BASE_URL', 'AUTO_START', 'REQUEST_BODY_LOG', 'LOG_FILE', 'LOG_MAX_TOTAL_MB',
|
|
198
|
+
'MEDIA_OUTPUT', 'MEDIA_BASE_URL', 'AUTO_START', 'HIDE_DOCK_ICON', 'REQUEST_BODY_LOG', 'LOG_FILE', 'LOG_MAX_TOTAL_MB',
|
|
199
199
|
'ADMIN_AUTH_ENABLED', 'ADMIN_USERNAME', 'ADMIN_PASSWORD', 'BUILD_NATIVE_NONSTREAM',
|
|
200
200
|
'NONSTREAM_KEEPALIVE', 'FAKE_STREAM', 'YOUTUBE_LINKS', 'STRIP_THOUGHT_SIGNATURES', 'SEARCH_MODEL_ALIASES', 'GOOGLE_SEARCH',
|
|
201
201
|
'FIRST_EVENT_HEDGE', 'FIRST_EVENT_HEDGE_MODELS', 'CONTINUATION_REWRITE', 'CONTINUATION_PROMPT'
|
package/lib/studio-release.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.3.
|
|
2
|
+
"version": "0.3.2-magpie.1",
|
|
3
3
|
"repository": "BUKOWSKIREAL/magpie-gemini-web",
|
|
4
|
-
"tag": "studio-v0.3.
|
|
4
|
+
"tag": "studio-v0.3.2-magpie.1",
|
|
5
5
|
"goVersion": "1.27.1",
|
|
6
6
|
"toolchains": {
|
|
7
7
|
"zig": "0.16.0",
|
|
@@ -52,12 +52,38 @@
|
|
|
52
52
|
"sha256": "942a3b57b202f899d799cac678b7471d01d0b34714070f0810c18fca37ffd721"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
},
|
|
56
|
+
"libwebp": {
|
|
57
|
+
"release": "v1.6.0-aistudio2api.1",
|
|
58
|
+
"repository": "Mag1cFall/libwebp",
|
|
59
|
+
"targets": {
|
|
60
|
+
"darwin-arm64": {
|
|
61
|
+
"asset": "libwebp-v1.6.0-aistudio2api.1-darwin-arm64.tar.gz",
|
|
62
|
+
"sha256": "66394cee457fe48408718c2dafd34808077f446a25678d54b4c504f711e579a6"
|
|
63
|
+
},
|
|
64
|
+
"darwin-x64": {
|
|
65
|
+
"asset": "libwebp-v1.6.0-aistudio2api.1-darwin-amd64.tar.gz",
|
|
66
|
+
"sha256": "d526ba604c0d2937122587eee4d13a01edd7c6c3e7e6bfdc72ab453ab18ae89f"
|
|
67
|
+
},
|
|
68
|
+
"linux-x64": {
|
|
69
|
+
"asset": "libwebp-v1.6.0-aistudio2api.1-linux-amd64.tar.gz",
|
|
70
|
+
"sha256": "d5047d17eace8b9639a08786b750e46ee83a1f251b7b961cd0f049371974ab78"
|
|
71
|
+
},
|
|
72
|
+
"linux-arm64": {
|
|
73
|
+
"asset": "libwebp-v1.6.0-aistudio2api.1-linux-arm64.tar.gz",
|
|
74
|
+
"sha256": "299028ab369a7f8b34feefd41f68cebdc6118c1306d37a57731da9503cd64084"
|
|
75
|
+
},
|
|
76
|
+
"win32-x64": {
|
|
77
|
+
"asset": "libwebp-v1.6.0-aistudio2api.1-windows-amd64.tar.gz",
|
|
78
|
+
"sha256": "ca38ca4cfae9bfe5808dbcfa11707d81efd534d723d2b7622cd32ef1114dea1d"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
55
81
|
}
|
|
56
82
|
},
|
|
57
83
|
"upstream": {
|
|
58
|
-
"version": "0.3.
|
|
84
|
+
"version": "0.3.2",
|
|
59
85
|
"repository": "https://github.com/Mag1cFall/AIStudio2API.git",
|
|
60
|
-
"commit": "
|
|
86
|
+
"commit": "4c44cb9765aa9fd497b91db1f5c1573504b9b85f",
|
|
61
87
|
"camoufox": "152.0.4-beta.29"
|
|
62
88
|
},
|
|
63
89
|
"patches": [
|
|
@@ -66,29 +92,29 @@
|
|
|
66
92
|
],
|
|
67
93
|
"binaries": {
|
|
68
94
|
"darwin-arm64": {
|
|
69
|
-
"asset": "aistudio2api-v0.3.
|
|
70
|
-
"sha256": "
|
|
71
|
-
"binarySha256": "
|
|
95
|
+
"asset": "aistudio2api-v0.3.2-magpie.1-darwin-arm64.tar.gz",
|
|
96
|
+
"sha256": "a3b10971d3b30d6c2621fc0ca179a041efd0b9992ed2fdb4117a689012dc86f3",
|
|
97
|
+
"binarySha256": "f0f2234862b4955a2adc4f24940db631aed3b62148e3732f4a9cb614e4d0c507"
|
|
72
98
|
},
|
|
73
99
|
"darwin-x64": {
|
|
74
|
-
"asset": "aistudio2api-v0.3.
|
|
75
|
-
"sha256": "
|
|
76
|
-
"binarySha256": "
|
|
100
|
+
"asset": "aistudio2api-v0.3.2-magpie.1-darwin-amd64.tar.gz",
|
|
101
|
+
"sha256": "1b613b228054417904a8f8cebcb50446bbdd438ce19e104ae293053673b0816a",
|
|
102
|
+
"binarySha256": "a8b56b4f411929742482ef18538ce5b5cc4a0e690d50f049b8c8aa95d3513b3a"
|
|
77
103
|
},
|
|
78
104
|
"linux-x64": {
|
|
79
|
-
"asset": "aistudio2api-v0.3.
|
|
80
|
-
"sha256": "
|
|
81
|
-
"binarySha256": "
|
|
105
|
+
"asset": "aistudio2api-v0.3.2-magpie.1-linux-amd64.tar.gz",
|
|
106
|
+
"sha256": "fe1c2c65a3dd4a2971b40432070c438b372cd1680724f96c07638e1855a4cbfd",
|
|
107
|
+
"binarySha256": "01e78fd13fd5ff12c30f71e24501b9aa01faa6a5c67ecae526f8e52d34d8c7f3"
|
|
82
108
|
},
|
|
83
109
|
"linux-arm64": {
|
|
84
|
-
"asset": "aistudio2api-v0.3.
|
|
85
|
-
"sha256": "
|
|
86
|
-
"binarySha256": "
|
|
110
|
+
"asset": "aistudio2api-v0.3.2-magpie.1-linux-arm64.tar.gz",
|
|
111
|
+
"sha256": "155fbae81c8e188d5770d4ad31e9feb74dc8e87aec92367d8d8069000441f99c",
|
|
112
|
+
"binarySha256": "3dbc76102beffe9a97dda612b9684f08b114176fae127763d5d1379cb3c08cdd"
|
|
87
113
|
},
|
|
88
114
|
"win32-x64": {
|
|
89
|
-
"asset": "aistudio2api-v0.3.
|
|
90
|
-
"sha256": "
|
|
91
|
-
"binarySha256": "
|
|
115
|
+
"asset": "aistudio2api-v0.3.2-magpie.1-windows-amd64.zip",
|
|
116
|
+
"sha256": "054e9aac804a7116b626750a5a2adbcb8ad8d465e428b292530fdebc4f2b2cd5",
|
|
117
|
+
"binarySha256": "e7720ced68f82a2aa5afa16c17614c067a57ef2afa59399b720e9d215e7eb42a"
|
|
92
118
|
}
|
|
93
119
|
}
|
|
94
120
|
}
|
package/package.json
CHANGED
package/patches/argon.patch
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
diff --git a/go.mod b/go.mod
|
|
2
|
-
index
|
|
2
|
+
index c6a75e272d59110a5f83e71bfa89b4588622905d..db2c8f5f60618bad14c38e013b469132d2c0c546 100644
|
|
3
3
|
--- a/go.mod
|
|
4
4
|
+++ b/go.mod
|
|
5
5
|
@@ -9,6 +9,7 @@ require (
|
|
@@ -19,7 +19,7 @@ index c6a75e2..db2c8f5 100644
|
|
|
19
19
|
golang.org/x/text v0.42.0 // indirect
|
|
20
20
|
modernc.org/libc v1.72.3 // indirect
|
|
21
21
|
diff --git a/internal/app/admin_cookies.go b/internal/app/admin_cookies.go
|
|
22
|
-
index
|
|
22
|
+
index 78236c6760f6509fcdce801ba853d9ecfafe16ca..a5109b0785d71c0ed1081b7316c2b3319223b160 100644
|
|
23
23
|
--- a/internal/app/admin_cookies.go
|
|
24
24
|
+++ b/internal/app/admin_cookies.go
|
|
25
25
|
@@ -87,9 +87,10 @@ func handleAdminCookies(w http.ResponseWriter, r *http.Request) {
|
|
@@ -65,7 +65,7 @@ index 78236c6..a5109b0 100644
|
|
|
65
65
|
// 只改传了的字段:没传的沿用当前值。
|
|
66
66
|
var curLabel, curNote string
|
|
67
67
|
diff --git a/internal/app/admin_ui/index.html b/internal/app/admin_ui/index.html
|
|
68
|
-
index
|
|
68
|
+
index 4aedd2788f396f98cb4267baa0b3674e7a8ba7af..2364e2baec4c0c3c7678b78d3bb5f7e118e4beed 100644
|
|
69
69
|
--- a/internal/app/admin_ui/index.html
|
|
70
70
|
+++ b/internal/app/admin_ui/index.html
|
|
71
71
|
@@ -1020,7 +1020,8 @@ const RT_GROUPS = [
|
|
@@ -89,7 +89,7 @@ index 4aedd27..2364e2b 100644
|
|
|
89
89
|
{
|
|
90
90
|
diff --git a/internal/app/argon.go b/internal/app/argon.go
|
|
91
91
|
new file mode 100644
|
|
92
|
-
index
|
|
92
|
+
index 0000000000000000000000000000000000000000..a2a5e774e8547afb125de6161c624bbfe572fe7e
|
|
93
93
|
--- /dev/null
|
|
94
94
|
+++ b/internal/app/argon.go
|
|
95
95
|
@@ -0,0 +1,172 @@
|
|
@@ -267,7 +267,7 @@ index 0000000..a2a5e77
|
|
|
267
267
|
+}
|
|
268
268
|
diff --git a/internal/app/argon_probe_test.go b/internal/app/argon_probe_test.go
|
|
269
269
|
new file mode 100644
|
|
270
|
-
index
|
|
270
|
+
index 0000000000000000000000000000000000000000..bbc0041bb2d8769657d195f9b2b997f4c49da181
|
|
271
271
|
--- /dev/null
|
|
272
272
|
+++ b/internal/app/argon_probe_test.go
|
|
273
273
|
@@ -0,0 +1,53 @@
|
|
@@ -326,7 +326,7 @@ index 0000000..bbc0041
|
|
|
326
326
|
+}
|
|
327
327
|
diff --git a/internal/app/argon_test.go b/internal/app/argon_test.go
|
|
328
328
|
new file mode 100644
|
|
329
|
-
index
|
|
329
|
+
index 0000000000000000000000000000000000000000..2f0b8cdc8f97bd303aa35bac1dda9d60611428a9
|
|
330
330
|
--- /dev/null
|
|
331
331
|
+++ b/internal/app/argon_test.go
|
|
332
332
|
@@ -0,0 +1,453 @@
|
|
@@ -785,7 +785,7 @@ index 0000000..2f0b8cd
|
|
|
785
785
|
+}
|
|
786
786
|
diff --git a/internal/app/argon_timing_probe_test.go b/internal/app/argon_timing_probe_test.go
|
|
787
787
|
new file mode 100644
|
|
788
|
-
index
|
|
788
|
+
index 0000000000000000000000000000000000000000..fb1ccec78a19896b0bf4ec1ffc3f97e957beb4f4
|
|
789
789
|
--- /dev/null
|
|
790
790
|
+++ b/internal/app/argon_timing_probe_test.go
|
|
791
791
|
@@ -0,0 +1,74 @@
|
|
@@ -865,7 +865,7 @@ index 0000000..fb1ccec
|
|
|
865
865
|
+}
|
|
866
866
|
diff --git a/internal/app/cancel_test.go b/internal/app/cancel_test.go
|
|
867
867
|
new file mode 100644
|
|
868
|
-
index
|
|
868
|
+
index 0000000000000000000000000000000000000000..f36c70005e6da66df26dd934271c0d9646faa9f2
|
|
869
869
|
--- /dev/null
|
|
870
870
|
+++ b/internal/app/cancel_test.go
|
|
871
871
|
@@ -0,0 +1,106 @@
|
|
@@ -976,7 +976,7 @@ index 0000000..f36c700
|
|
|
976
976
|
+ t.Fatal("没有记录这次请求")
|
|
977
977
|
+}
|
|
978
978
|
diff --git a/internal/app/client.go b/internal/app/client.go
|
|
979
|
-
index
|
|
979
|
+
index c63b3a9fabbf1fe2f141a05def3f9947dff32ca3..da8ccb8ff683903907e7159bea42268c299e2793 100644
|
|
980
980
|
--- a/internal/app/client.go
|
|
981
981
|
+++ b/internal/app/client.go
|
|
982
982
|
@@ -1,9 +1,11 @@
|
|
@@ -1196,7 +1196,7 @@ index c63b3a9..da8ccb8 100644
|
|
|
1196
1196
|
+ setChromeHeaders(req.Header)
|
|
1197
1197
|
}
|
|
1198
1198
|
diff --git a/internal/app/client_timeout_test.go b/internal/app/client_timeout_test.go
|
|
1199
|
-
index
|
|
1199
|
+
index 94c0b7097137d057c85cc98df7fbebbf16207b46..2a4b697e943f58c55c33e2af48033853cbcc516a 100644
|
|
1200
1200
|
--- a/internal/app/client_timeout_test.go
|
|
1201
1201
|
+++ b/internal/app/client_timeout_test.go
|
|
1202
1202
|
@@ -1,6 +1,12 @@
|
|
@@ -1309,7 +1309,7 @@ index 94c0b70..2a4b697 100644
|
|
|
1309
1309
|
+ }
|
|
1310
1310
|
+}
|
|
1311
1311
|
diff --git a/internal/app/context_file.go b/internal/app/context_file.go
|
|
1312
|
-
index
|
|
1312
|
+
index 97995a2f0ebb7626512590a23da1bc3f6fc97e3b..ba031401e77e479ebb33cc3a7868c904c7cb42ea 100644
|
|
1313
1313
|
--- a/internal/app/context_file.go
|
|
1314
1314
|
+++ b/internal/app/context_file.go
|
|
1315
1315
|
@@ -1,6 +1,7 @@
|
|
@@ -1388,7 +1388,7 @@ index 97995a2..ba03140 100644
|
|
|
1388
1388
|
+ return promptParts{body: parts}.join()
|
|
1389
1389
|
}
|
|
1390
1390
|
diff --git a/internal/app/conversation.go b/internal/app/conversation.go
|
|
1391
|
-
index
|
|
1391
|
+
index e961a1b6af302999fe46d017feb3e4ed2c3301c1..39adf8893fab0abf1e999e73f3a9d604c72cf40e 100644
|
|
1392
1392
|
--- a/internal/app/conversation.go
|
|
1393
1393
|
+++ b/internal/app/conversation.go
|
|
1394
1394
|
@@ -1,6 +1,7 @@
|
|
@@ -1428,7 +1428,7 @@ index 529cfad..c48b692 100644
|
|
|
1428
1428
|
p, slotOK, slotErr := acquireSlot(conv.proxyID)
|
|
1429
1429
|
if !slotOK {
|
|
1430
1430
|
return &StreamResult{}, slotErr
|
|
1431
|
-
@@ -
|
|
1431
|
+
@@ -375,12 +375,19 @@ func streamGenerateConv(prompt string, mc ModelConfig, conv *convState,
|
|
1432
1432
|
t0 := time.Now()
|
|
1433
1433
|
var lastErr error
|
|
1434
1434
|
for attempt := 0; attempt < rtCfg().RetryAttempts; attempt++ {
|
|
@@ -1449,7 +1449,7 @@ index 529cfad..c48b692 100644
|
|
|
1449
1449
|
// 降级:同一个号重试还是降级,直接交给上层(见 gemini.go 同一处)。
|
|
1450
1450
|
var mde *ModelDowngradedError
|
|
1451
1451
|
if errors.As(err, &mde) {
|
|
1452
|
-
@@ -
|
|
1452
|
+
@@ -389,6 +396,9 @@ func streamGenerateConv(prompt string, mc ModelConfig, conv *convState,
|
|
1453
1453
|
Emitted: tracker.emitted, EmittedReasoning: rtracker.emitted}, err
|
|
1454
1454
|
}
|
|
1455
1455
|
if err != nil || statusCode != 200 || !hasContentFrame(string(raw)) {
|
|
@@ -1459,10 +1459,21 @@ index 529cfad..c48b692 100644
|
|
|
1459
1459
|
if err == nil && statusCode == 200 {
|
|
1460
1460
|
lastErr = fmt.Errorf("续接无内容帧(会话可能已失效,raw %d 字节)", len(raw))
|
|
1461
1461
|
} else if err != nil {
|
|
1462
|
-
@@ -
|
|
1463
|
-
|
|
1462
|
+
@@ -407,7 +417,9 @@ func streamGenerateConv(prompt string, mc ModelConfig, conv *convState,
|
|
1463
|
+
if errors.Is(err, errUpstreamFailureReply) {
|
|
1464
|
+
logf("retry %d/%d: %v", attempt+1, rtCfg().RetryAttempts, err)
|
|
1465
|
+
}
|
|
1466
|
+
- time.Sleep(time.Duration(rtCfg().RetryDelaySec) * time.Second)
|
|
1467
|
+
+ if werr := sleepCtx(ctx, time.Duration(rtCfg().RetryDelaySec)*time.Second); werr != nil {
|
|
1468
|
+
+ return &StreamResult{ProxyID: p.ID, ProxyName: p.Name, AccountID: conv.accountID}, werr
|
|
1469
|
+
+ }
|
|
1470
|
+
}
|
|
1471
|
+
continue
|
|
1472
|
+
}
|
|
1473
|
+
@@ -419,7 +431,9 @@ func streamGenerateConv(prompt string, mc ModelConfig, conv *convState,
|
|
1464
1474
|
}
|
|
1465
1475
|
if attempt < rtCfg().RetryAttempts-1 {
|
|
1476
|
+
logf("retry %d/%d: %v", attempt+1, rtCfg().RetryAttempts, lastErr)
|
|
1466
1477
|
- time.Sleep(time.Duration(rtCfg().RetryDelaySec) * time.Second)
|
|
1467
1478
|
+ if werr := sleepCtx(ctx, time.Duration(rtCfg().RetryDelaySec)*time.Second); werr != nil {
|
|
1468
1479
|
+ return &StreamResult{ProxyID: p.ID, ProxyName: p.Name, AccountID: conv.accountID}, werr
|
|
@@ -1470,7 +1481,7 @@ index 529cfad..c48b692 100644
|
|
|
1470
1481
|
}
|
|
1471
1482
|
continue
|
|
1472
1483
|
}
|
|
1473
|
-
@@ -
|
|
1484
|
+
@@ -460,7 +474,7 @@ func streamGenerateConv(prompt string, mc ModelConfig, conv *convState,
|
|
1474
1485
|
// - 没命中 → 新建会话,首轮发全量拼接的历史(含 tools 指令,把当前上下文交给服务端)。
|
|
1475
1486
|
//
|
|
1476
1487
|
// 支持 tools(续接里也解析 ```tool_call``` 围栏);图片走原单轮路径(callGemini)。
|
|
@@ -1479,7 +1490,7 @@ index 529cfad..c48b692 100644
|
|
|
1479
1490
|
tools []map[string]interface{}, toolChoice interface{},
|
|
1480
1491
|
onDelta, onReasoning func(string)) (string, []ToolCall, *StreamResult, error) {
|
|
1481
1492
|
parentKey := convParentKey(messages)
|
|
1482
|
-
@@ -
|
|
1493
|
+
@@ -511,7 +525,7 @@ func callGeminiConv(messages []map[string]interface{}, mc ModelConfig,
|
|
1483
1494
|
}
|
|
1484
1495
|
}
|
|
1485
1496
|
}
|
|
@@ -1489,7 +1500,7 @@ index 529cfad..c48b692 100644
|
|
|
1489
1500
|
break
|
|
1490
1501
|
}
|
|
1491
1502
|
diff --git a/internal/app/cookie_pool.go b/internal/app/cookie_pool.go
|
|
1492
|
-
index
|
|
1503
|
+
index 60fe35fe0664c2feeb9d9d811be01c6d3323799c..14e5a44734cdd50be933af1cf8387a65500ab3aa 100644
|
|
1493
1504
|
--- a/internal/app/cookie_pool.go
|
|
1494
1505
|
+++ b/internal/app/cookie_pool.go
|
|
1495
1506
|
@@ -565,6 +565,9 @@ func checkAccountCookie(a CookieAccount) CookieCheck {
|
|
@@ -1503,7 +1514,7 @@ index 60fe35f..14e5a44 100644
|
|
|
1503
1514
|
if extractSAPISID(a.Cookie) == "" {
|
|
1504
1515
|
detail += ",但缺 SAPISID(算不出授权头,部分接口会被当匿名)"
|
|
1505
1516
|
diff --git a/internal/app/gemini.go b/internal/app/gemini.go
|
|
1506
|
-
index
|
|
1517
|
+
index e07759d3b1fa17d6e81afab0e42e355925d6cbc9..8b9ad100a2fcd5e7b53e1a9855f28eee98d110d0 100644
|
|
1507
1518
|
--- a/internal/app/gemini.go
|
|
1508
1519
|
+++ b/internal/app/gemini.go
|
|
1509
1520
|
@@ -3,6 +3,7 @@ package app
|
|
@@ -1613,7 +1624,7 @@ index 7572da2..d52d582 100644
|
|
|
1613
1624
|
return attrib(ferr)
|
|
1614
1625
|
} else if used {
|
|
1615
1626
|
prompt = p
|
|
1616
|
-
@@ -726,
|
|
1627
|
+
@@ -726,49 +748,27 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1617
1628
|
xsrfRetried := false // XSRF 自愈只做一次,避免死循环
|
|
1618
1629
|
t0 := time.Now()
|
|
1619
1630
|
|
|
@@ -1628,6 +1639,10 @@ index 7572da2..d52d582 100644
|
|
|
1628
1639
|
- return err
|
|
1629
1640
|
- }
|
|
1630
1641
|
- }
|
|
1642
|
+
- // 出错话术在吐给客户端之前拦下,中断读取走下面的重试。
|
|
1643
|
+
- if failureReplyInLine(line) {
|
|
1644
|
+
- return errUpstreamFailureReply
|
|
1645
|
+
- }
|
|
1631
1646
|
- // 思考链先推完才轮到正文,所以先处理它,客户端拿到的顺序才对。
|
|
1632
1647
|
- if onReasoning != nil {
|
|
1633
1648
|
- if r := reasoningInLine(line); r != "" {
|
|
@@ -1639,7 +1654,7 @@ index 7572da2..d52d582 100644
|
|
|
1639
1654
|
- if onDelta == nil {
|
|
1640
1655
|
- return nil
|
|
1641
1656
|
- }
|
|
1642
|
-
- for _, t := range
|
|
1657
|
+
- for _, t := range renderedTextsInLine(line, false) {
|
|
1643
1658
|
- if d := tracker.Push(t); d != "" {
|
|
1644
1659
|
- onDelta(d)
|
|
1645
1660
|
- }
|
|
@@ -1671,7 +1686,7 @@ index 7572da2..d52d582 100644
|
|
|
1671
1686
|
var statusCode int
|
|
1672
1687
|
var raw []byte
|
|
1673
1688
|
var ttfb int64
|
|
1674
|
-
@@ -
|
|
1689
|
+
@@ -776,9 +776,9 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1675
1690
|
var err error
|
|
1676
1691
|
if useWC {
|
|
1677
1692
|
wcReq.Cookie = cookieStr
|
|
@@ -1683,7 +1698,7 @@ index 7572da2..d52d582 100644
|
|
|
1683
1698
|
}
|
|
1684
1699
|
if len(setCookie) > 0 && cookieID > 0 {
|
|
1685
1700
|
if merged := mergeSetCookie(cookieStr, setCookie); merged != cookieStr {
|
|
1686
|
-
@@ -
|
|
1701
|
+
@@ -786,6 +786,10 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1687
1702
|
updateAccountCookie(cookieID, merged)
|
|
1688
1703
|
}
|
|
1689
1704
|
}
|
|
@@ -1694,7 +1709,7 @@ index 7572da2..d52d582 100644
|
|
|
1694
1709
|
// 降级不是出口或 cookie 的错,也不该重试:同一个号再发一次还是降级。
|
|
1695
1710
|
var mde *ModelDowngradedError
|
|
1696
1711
|
if errors.As(err, &mde) {
|
|
1697
|
-
@@ -
|
|
1712
|
+
@@ -797,6 +801,9 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1698
1713
|
return res, err
|
|
1699
1714
|
}
|
|
1700
1715
|
if err != nil {
|
|
@@ -1702,9 +1717,9 @@ index 7572da2..d52d582 100644
|
|
|
1702
1717
|
+ return attrib(cerr) // 客户端断开,不是出口的错
|
|
1703
1718
|
+ }
|
|
1704
1719
|
lastErr = err
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
@@ -
|
|
1720
|
+
// 出错话术不记进代理健康度:匿名直连也一样出,跟出口无关,算进去会误熔断。
|
|
1721
|
+
if pickedOK && !errors.Is(err, errUpstreamFailureReply) {
|
|
1722
|
+
@@ -808,7 +815,9 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1708
1723
|
}
|
|
1709
1724
|
if attempt < rtCfg().RetryAttempts-1 {
|
|
1710
1725
|
logf("retry %d/%d: %v", attempt+1, rtCfg().RetryAttempts, err)
|
|
@@ -1715,7 +1730,7 @@ index 7572da2..d52d582 100644
|
|
|
1715
1730
|
}
|
|
1716
1731
|
continue
|
|
1717
1732
|
}
|
|
1718
|
-
@@ -
|
|
1733
|
+
@@ -837,7 +846,9 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1719
1734
|
break
|
|
1720
1735
|
}
|
|
1721
1736
|
if attempt < rtCfg().RetryAttempts-1 {
|
|
@@ -1726,7 +1741,7 @@ index 7572da2..d52d582 100644
|
|
|
1726
1741
|
}
|
|
1727
1742
|
continue
|
|
1728
1743
|
}
|
|
1729
|
-
@@ -
|
|
1744
|
+
@@ -865,7 +876,9 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1730
1745
|
}
|
|
1731
1746
|
if attempt < rtCfg().RetryAttempts-1 {
|
|
1732
1747
|
logf("retry %d/%d: 空响应(无内容帧,%d 字节)", attempt+1, rtCfg().RetryAttempts, len(raw))
|
|
@@ -1737,7 +1752,18 @@ index 7572da2..d52d582 100644
|
|
|
1737
1752
|
}
|
|
1738
1753
|
continue
|
|
1739
1754
|
}
|
|
1740
|
-
@@ -
|
|
1755
|
+
@@ -877,7 +890,9 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1756
|
+
}
|
|
1757
|
+
if attempt < rtCfg().RetryAttempts-1 {
|
|
1758
|
+
logf("retry %d/%d: %v", attempt+1, rtCfg().RetryAttempts, lastErr)
|
|
1759
|
+
- time.Sleep(time.Duration(rtCfg().RetryDelaySec) * time.Second)
|
|
1760
|
+
+ if werr := retryWait(); werr != nil {
|
|
1761
|
+
+ return attrib(werr)
|
|
1762
|
+
+ }
|
|
1763
|
+
}
|
|
1764
|
+
continue
|
|
1765
|
+
}
|
|
1766
|
+
@@ -943,6 +958,82 @@ func streamGenerateOnce(prompt, latest string, mc ModelConfig, pending []pending
|
|
1741
1767
|
return attrib(lastErr)
|
|
1742
1768
|
}
|
|
1743
1769
|
|
|
@@ -1767,6 +1793,10 @@ index 7572da2..d52d582 100644
|
|
|
1767
1793
|
+ return err
|
|
1768
1794
|
+ }
|
|
1769
1795
|
+ }
|
|
1796
|
+
+ // 出错话术在吐给客户端之前拦下,中断读取走重试。
|
|
1797
|
+
+ if failureReplyInLine(line) {
|
|
1798
|
+
+ return errUpstreamFailureReply
|
|
1799
|
+
+ }
|
|
1770
1800
|
+ // 思考链先推完才轮到正文,所以先处理它,客户端拿到的顺序才对。
|
|
1771
1801
|
+ if onReasoning != nil {
|
|
1772
1802
|
+ if r := reasoningInLine(line); r != "" {
|
|
@@ -1779,7 +1809,7 @@ index 7572da2..d52d582 100644
|
|
|
1779
1809
|
+ return nil
|
|
1780
1810
|
+ }
|
|
1781
1811
|
+ grew := false
|
|
1782
|
-
+ for _, t := range
|
|
1812
|
+
+ for _, t := range renderedTextsInLine(line, false) {
|
|
1783
1813
|
+ if d := watch.Push(t); d != "" {
|
|
1784
1814
|
+ grew = true
|
|
1785
1815
|
+ if onDelta != nil {
|
|
@@ -1816,7 +1846,7 @@ index 7572da2..d52d582 100644
|
|
|
1816
1846
|
// upstreamModelRe 匹配响应帧里服务端自报的模型显示名(帧的 [42] 位)。
|
|
1817
1847
|
// 形如 ...,"fbb127bbb056c959",null,null,"3.6 Flash",true,...
|
|
1818
1848
|
var upstreamModelRe = regexp.MustCompile(`\\"[0-9a-f]{16}\\",null,null,\\"([^"\\]{1,40})\\"`)
|
|
1819
|
-
@@ -
|
|
1849
|
+
@@ -1023,10 +1114,122 @@ func setAuthUserHeaders(h map[string]string, authuser int) {
|
|
1820
1850
|
// 实测号活一两小时就失效 —— 所以这些必须收下来并写回账号。
|
|
1821
1851
|
func doGeminiRequest(endpoint, body string, headers map[string]string, proxyURL string,
|
|
1822
1852
|
onLine func(string) error) (int, []byte, int64, []string, error) {
|
|
@@ -1940,7 +1970,7 @@ index 7572da2..d52d582 100644
|
|
|
1940
1970
|
if err != nil {
|
|
1941
1971
|
return 0, nil, 0, nil, err
|
|
1942
1972
|
}
|
|
1943
|
-
@@ -
|
|
1973
|
+
@@ -1034,36 +1237,37 @@ func doGeminiRequest(endpoint, body string, headers map[string]string, proxyURL
|
|
1944
1974
|
for k, v := range headers {
|
|
1945
1975
|
req.Header.Set(k, v)
|
|
1946
1976
|
}
|
|
@@ -1987,7 +2017,7 @@ index 7572da2..d52d582 100644
|
|
|
1987
2017
|
}
|
|
1988
2018
|
return resp.StatusCode, raw, ttfb, resp.Header.Values("Set-Cookie"), nil
|
|
1989
2019
|
}
|
|
1990
|
-
@@ -
|
|
2020
|
+
@@ -1421,6 +1625,7 @@ func probeGemini(prompt, proxyURL string) ProbeResult {
|
|
1991
2021
|
res.Diagnostic = "构建请求失败: " + e.Error()
|
|
1992
2022
|
return res
|
|
1993
2023
|
}
|
|
@@ -1996,7 +2026,7 @@ index 7572da2..d52d582 100644
|
|
|
1996
2026
|
req.Header.Set(k, v)
|
|
1997
2027
|
}
|
|
1998
2028
|
diff --git a/internal/app/gemini_api.go b/internal/app/gemini_api.go
|
|
1999
|
-
index
|
|
2029
|
+
index 3721e4492d70b1e8647242a4ffb3df15a7f8fba1..d5950ab09511f419dfd39aa7328a397b9b2783de 100644
|
|
2000
2030
|
--- a/internal/app/gemini_api.go
|
|
2001
2031
|
+++ b/internal/app/gemini_api.go
|
|
2002
2032
|
@@ -1,6 +1,7 @@
|
|
@@ -2072,10 +2102,10 @@ index 3721e44..d5950ab 100644
|
|
|
2072
2102
|
if _, ok := err.(*ModelDowngradedError); ok {
|
|
2073
2103
|
status = 429
|
|
2074
2104
|
diff --git a/internal/app/gemini_test.go b/internal/app/gemini_test.go
|
|
2075
|
-
index
|
|
2105
|
+
index b6830a418c78b045c21d39cb690e2b32542c7262..eb4e02114acdecb78531ad91e72b66d274a6467e 100644
|
|
2076
2106
|
--- a/internal/app/gemini_test.go
|
|
2077
2107
|
+++ b/internal/app/gemini_test.go
|
|
2078
|
-
@@ -
|
|
2108
|
+
@@ -16,6 +16,7 @@ func TestModelHeader(t *testing.T) {
|
|
2079
2109
|
{"gemini-3.6-flash", hexFlash36},
|
|
2080
2110
|
{"gemini-3.5-flash-lite", hexFlashLite},
|
|
2081
2111
|
{"gemini-3.1-pro", hexPro31},
|
|
@@ -2085,7 +2115,7 @@ index 00eb305..2b4c6ca 100644
|
|
|
2085
2115
|
name, mc, err := resolveModel(c.name)
|
|
2086
2116
|
diff --git a/internal/app/headers_test.go b/internal/app/headers_test.go
|
|
2087
2117
|
new file mode 100644
|
|
2088
|
-
index
|
|
2118
|
+
index 0000000000000000000000000000000000000000..ef9b476da72bae3cc53c9cd85510a2f1db224a7d
|
|
2089
2119
|
--- /dev/null
|
|
2090
2120
|
+++ b/internal/app/headers_test.go
|
|
2091
2121
|
@@ -0,0 +1,74 @@
|
|
@@ -2165,7 +2195,7 @@ index 0000000..ef9b476
|
|
|
2165
2195
|
+}
|
|
2166
2196
|
diff --git a/internal/app/latency_test.go b/internal/app/latency_test.go
|
|
2167
2197
|
new file mode 100644
|
|
2168
|
-
index
|
|
2198
|
+
index 0000000000000000000000000000000000000000..fb778f55c20fe14ce2bac64c1e97ff4182328023
|
|
2169
2199
|
--- /dev/null
|
|
2170
2200
|
+++ b/internal/app/latency_test.go
|
|
2171
2201
|
@@ -0,0 +1,333 @@
|
|
@@ -2503,7 +2533,7 @@ index 0000000..fb778f5
|
|
|
2503
2533
|
+ }
|
|
2504
2534
|
+}
|
|
2505
2535
|
diff --git a/internal/app/main_test.go b/internal/app/main_test.go
|
|
2506
|
-
index
|
|
2536
|
+
index 38e1a44d97d73524eafc2e67470d17b98a957c3b..e3f7636fd47514cf505abcb765125e48d4e7237d 100644
|
|
2507
2537
|
--- a/internal/app/main_test.go
|
|
2508
2538
|
+++ b/internal/app/main_test.go
|
|
2509
2539
|
@@ -1,9 +1,12 @@
|
|
@@ -2562,7 +2592,7 @@ index 38e1a44..e3f7636 100644
|
|
|
2562
2592
|
+ t.Cleanup(func() { rtMu.Lock(); rtVal.RequestTimeout = saved; rtMu.Unlock() })
|
|
2563
2593
|
+}
|
|
2564
2594
|
diff --git a/internal/app/mcp_media.go b/internal/app/mcp_media.go
|
|
2565
|
-
index
|
|
2595
|
+
index acb113196d65f76745f816e2c1a9579884384774..a8bab02191b7fc8ae47afa59fce3744fe11f875b 100644
|
|
2566
2596
|
--- a/internal/app/mcp_media.go
|
|
2567
2597
|
+++ b/internal/app/mcp_media.go
|
|
2568
2598
|
@@ -1,6 +1,7 @@
|
|
@@ -2583,7 +2613,7 @@ index acb1131..a8bab02 100644
|
|
|
2583
2613
|
return nil, err
|
|
2584
2614
|
}
|
|
2585
2615
|
diff --git a/internal/app/media.go b/internal/app/media.go
|
|
2586
|
-
index
|
|
2616
|
+
index ec3b6e499aab0bbe4adc1e3ad517e2c9296e9c46..07f316410de88d6c3bae7435344e31fdcfc35157 100644
|
|
2587
2617
|
--- a/internal/app/media.go
|
|
2588
2618
|
+++ b/internal/app/media.go
|
|
2589
2619
|
@@ -762,6 +762,7 @@ func mediaGetOnce(rawURL string, headers map[string]string, proxyURL string) (in
|
|
@@ -2595,10 +2625,10 @@ index ec3b6e4..07f3164 100644
|
|
|
2595
2625
|
req.Header.Set(k, v)
|
|
2596
2626
|
}
|
|
2597
2627
|
diff --git a/internal/app/messages.go b/internal/app/messages.go
|
|
2598
|
-
index
|
|
2628
|
+
index 1bd5bb4ac780da437deb257d8e9f9f1f33852ede..3d473c4f1a88cd1b40b2c9e75889b66a01740dde 100644
|
|
2599
2629
|
--- a/internal/app/messages.go
|
|
2600
2630
|
+++ b/internal/app/messages.go
|
|
2601
|
-
@@ -
|
|
2631
|
+
@@ -49,8 +49,50 @@ type ToolCallFunction struct {
|
|
2602
2632
|
// 出口才知道能不能做,所以判断放在 streamGenerate 里。最新那条消息单独返回,
|
|
2603
2633
|
// 转附件时用来内联,好让模型不必去文件里找问题。
|
|
2604
2634
|
func messagesToPrompt(messages []map[string]interface{}, tools []map[string]interface{},
|
|
@@ -2651,7 +2681,7 @@ index 81b8900..9d1702d 100644
|
|
|
2651
2681
|
}
|
|
2652
2682
|
|
|
2653
2683
|
// latestUserMessage 取最后一条 user 消息的正文,没有则返回空串。
|
|
2654
|
-
@@ -
|
|
2684
|
+
@@ -68,6 +110,12 @@ func latestUserMessage(messages []map[string]interface{}) string {
|
|
2655
2685
|
|
|
2656
2686
|
func buildPrompt(messages []map[string]interface{}, tools []map[string]interface{},
|
|
2657
2687
|
toolChoice interface{}) string {
|
|
@@ -2664,7 +2694,7 @@ index 81b8900..9d1702d 100644
|
|
|
2664
2694
|
var parts []string
|
|
2665
2695
|
|
|
2666
2696
|
toolsInjected := false
|
|
2667
|
-
@@ -
|
|
2697
|
+
@@ -92,7 +140,7 @@ func buildPrompt(messages []map[string]interface{}, tools []map[string]interface
|
|
2668
2698
|
})
|
|
2669
2699
|
}
|
|
2670
2700
|
if len(defs) > 0 {
|
|
@@ -2673,7 +2703,7 @@ index 81b8900..9d1702d 100644
|
|
|
2673
2703
|
rule := "Only use tool_call blocks when needed."
|
|
2674
2704
|
switch {
|
|
2675
2705
|
case forced != "":
|
|
2676
|
-
@@ -
|
|
2706
|
+
@@ -105,13 +153,13 @@ func buildPrompt(messages []map[string]interface{}, tools []map[string]interface
|
|
2677
2707
|
"and nothing else — do not answer the question yourself, even if you " +
|
|
2678
2708
|
"know the answer."
|
|
2679
2709
|
}
|
|
@@ -2690,7 +2720,7 @@ index 81b8900..9d1702d 100644
|
|
|
2690
2720
|
toolsInjected = true
|
|
2691
2721
|
}
|
|
2692
2722
|
}
|
|
2693
|
-
@@ -
|
|
2723
|
+
@@ -168,33 +216,27 @@ func buildPrompt(messages []map[string]interface{}, tools []map[string]interface
|
|
2694
2724
|
// 工具名(shell_command)0/4 抄假名 —— 示例教的是「动作」不是名字,故不必按客户端
|
|
2695
2725
|
// 动态生成 schema。
|
|
2696
2726
|
if toolsInjected {
|
|
@@ -2743,8 +2773,8 @@ index 81b8900..9d1702d 100644
|
|
|
2743
2773
|
}
|
|
2744
2774
|
|
|
2745
2775
|
func contentToString(content interface{}) string {
|
|
2746
|
-
@@ -
|
|
2747
|
-
return
|
|
2776
|
+
@@ -511,6 +553,45 @@ func argsObject(v interface{}) (string, bool) {
|
|
2777
|
+
return string(b), true
|
|
2748
2778
|
}
|
|
2749
2779
|
|
|
2750
2780
|
+// toolCallComplete 返回一个判定函数:累积正文里出现了**闭合且能解析**、函数名在
|
|
@@ -2778,14 +2808,11 @@ index 81b8900..9d1702d 100644
|
|
|
2778
2808
|
+ if !ok {
|
|
2779
2809
|
+ return false
|
|
2780
2810
|
+ }
|
|
2781
|
-
+ var
|
|
2782
|
-
+
|
|
2783
|
-
+ Arguments map[string]interface{} `json:"arguments"`
|
|
2784
|
-
+ }
|
|
2785
|
-
+ if json.Unmarshal([]byte(strings.TrimSpace(after[:body])), &data) != nil {
|
|
2811
|
+
+ var raw map[string]interface{}
|
|
2812
|
+
+ if json.Unmarshal([]byte(strings.TrimSpace(after[:body])), &raw) != nil || raw == nil {
|
|
2786
2813
|
+ return false
|
|
2787
2814
|
+ }
|
|
2788
|
-
+ return names[
|
|
2815
|
+
+ return names[toolCallName(raw)]
|
|
2789
2816
|
+ }
|
|
2790
2817
|
+}
|
|
2791
2818
|
+
|
|
@@ -2793,7 +2820,7 @@ index 81b8900..9d1702d 100644
|
|
|
2793
2820
|
// 返回 (mode, forcedName):mode ∈ {"auto","none","required"};
|
|
2794
2821
|
// forcedName 非空表示客户端点名了某个函数。
|
|
2795
2822
|
diff --git a/internal/app/server.go b/internal/app/server.go
|
|
2796
|
-
index
|
|
2823
|
+
index e29ce3e3ac4372ba65a617ca8c4fdd0aba4da642..b3235675a255057abaa83467cc87b651e7d4d0ac 100644
|
|
2797
2824
|
--- a/internal/app/server.go
|
|
2798
2825
|
+++ b/internal/app/server.go
|
|
2799
2826
|
@@ -1,6 +1,7 @@
|
|
@@ -2808,15 +2835,21 @@ index 830fa6a..03f9b4b 100644
|
|
|
2808
2835
|
"time"
|
|
2809
2836
|
)
|
|
2810
2837
|
|
|
2811
|
-
-const Version = "4.27.
|
|
2812
|
-
+const Version = "4.27.
|
|
2838
|
+
-const Version = "4.27.1"
|
|
2839
|
+
+const Version = "4.27.1-magpie.1"
|
|
2813
2840
|
|
|
2814
2841
|
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
2815
2842
|
body, _ := json.Marshal(data)
|
|
2816
|
-
@@ -
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2843
|
+
@@ -40,9 +41,10 @@ func handleRoot(w http.ResponseWriter, r *http.Request) {
|
|
2844
|
+
modelNames = append(modelNames, n)
|
|
2845
|
+
}
|
|
2846
|
+
writeJSON(w, 200, map[string]interface{}{
|
|
2847
|
+
- "status": "ok",
|
|
2848
|
+
- "version": Version,
|
|
2849
|
+
- "models": modelNames,
|
|
2850
|
+
+ "status": "ok",
|
|
2851
|
+
+ "version": Version,
|
|
2852
|
+
+ "models": modelNames,
|
|
2820
2853
|
+ "capabilities": map[string]bool{"account_proxy_binding": true},
|
|
2821
2854
|
})
|
|
2822
2855
|
}
|
|
@@ -2909,7 +2942,7 @@ index 830fa6a..03f9b4b 100644
|
|
|
2909
2942
|
if downgraded {
|
|
2910
2943
|
diff --git a/internal/app/stall_test.go b/internal/app/stall_test.go
|
|
2911
2944
|
new file mode 100644
|
|
2912
|
-
index
|
|
2945
|
+
index 0000000000000000000000000000000000000000..cfdd6cf4e739c9fe50992fae42f7892e538916d9
|
|
2913
2946
|
--- /dev/null
|
|
2914
2947
|
+++ b/internal/app/stall_test.go
|
|
2915
2948
|
@@ -0,0 +1,199 @@
|
|
@@ -3113,7 +3146,7 @@ index 0000000..cfdd6cf
|
|
|
3113
3146
|
+ }
|
|
3114
3147
|
+}
|
|
3115
3148
|
diff --git a/internal/app/strict_model.go b/internal/app/strict_model.go
|
|
3116
|
-
index
|
|
3149
|
+
index ecc17aceec6f365649ccb84bc9ca04189b524f03..88267c5b550800765d2b50f474e4ba7670bb6efd 100644
|
|
3117
3150
|
--- a/internal/app/strict_model.go
|
|
3118
3151
|
+++ b/internal/app/strict_model.go
|
|
3119
3152
|
@@ -150,7 +150,8 @@ func strictLineCheck(mc ModelConfig, accountID func() int64) func(line string) e
|
|
@@ -3127,7 +3160,7 @@ index ecc17ac..88267c5 100644
|
|
|
3127
3160
|
|
|
3128
3161
|
// modelDowngraded 判断服务端自报的显示名是不是请求的那个模型。
|
|
3129
3162
|
diff --git a/internal/app/tokenizer.go b/internal/app/tokenizer.go
|
|
3130
|
-
index
|
|
3163
|
+
index 634f3b76c1ceea03f540e4c2fc5e3a9ca53ccd2b..399057e5ab5e62120094454e2e81da6b12c0d8d8 100644
|
|
3131
3164
|
--- a/internal/app/tokenizer.go
|
|
3132
3165
|
+++ b/internal/app/tokenizer.go
|
|
3133
3166
|
@@ -1,9 +1,17 @@
|
|
@@ -3262,7 +3295,7 @@ index 634f3b7..399057e 100644
|
|
|
3262
3295
|
if err != nil {
|
|
3263
3296
|
fmt.Fprintf(os.Stderr, "[tokenizer] init failed (will fall back to chars/4): %v\n", err)
|
|
3264
3297
|
diff --git a/internal/app/upload.go b/internal/app/upload.go
|
|
3265
|
-
index
|
|
3298
|
+
index 4feb5d93c088ed058f1f3b57c2e9ca1dc9c6cab1..a21e8d1e671d12d7523648bc3c0948958fe594a3 100644
|
|
3266
3299
|
--- a/internal/app/upload.go
|
|
3267
3300
|
+++ b/internal/app/upload.go
|
|
3268
3301
|
@@ -2,6 +2,7 @@ package app
|
|
@@ -3333,7 +3366,7 @@ index 4feb5d9..a21e8d1 100644
|
|
|
3333
3366
|
req.Header.Set(k, v)
|
|
3334
3367
|
}
|
|
3335
3368
|
diff --git a/internal/app/video_cooldown_test.go b/internal/app/video_cooldown_test.go
|
|
3336
|
-
index
|
|
3369
|
+
index 8eda49e5f5318f962445c4b08bb33b342f8e2b8f..cf64828c36dcb190fae2e6315573460a9ba4a711 100644
|
|
3337
3370
|
--- a/internal/app/video_cooldown_test.go
|
|
3338
3371
|
+++ b/internal/app/video_cooldown_test.go
|
|
3339
3372
|
@@ -2,6 +2,7 @@ package app
|
|
@@ -3354,7 +3387,7 @@ index 8eda49e..cf64828 100644
|
|
|
3354
3387
|
return "", nil, res, err
|
|
3355
3388
|
}
|
|
3356
3389
|
diff --git a/internal/app/videos.go b/internal/app/videos.go
|
|
3357
|
-
index
|
|
3390
|
+
index 4ea7ac64d3854396b8339b0d2240a96d7a8e9331..861f72e355a62e191c875e7ea72dbe28d51b556f 100644
|
|
3358
3391
|
--- a/internal/app/videos.go
|
|
3359
3392
|
+++ b/internal/app/videos.go
|
|
3360
3393
|
@@ -10,6 +10,7 @@ package app
|
|
@@ -3384,7 +3417,7 @@ index 4ea7ac6..861f72e 100644
|
|
|
3384
3417
|
return res, nil
|
|
3385
3418
|
}
|
|
3386
3419
|
diff --git a/internal/app/vision.go b/internal/app/vision.go
|
|
3387
|
-
index
|
|
3420
|
+
index b0c97245b5e822155b97d5c3b82363dd1cbf27a2..b1ca7cb8d9620d1d295543bd7118f507d32b7980 100644
|
|
3388
3421
|
--- a/internal/app/vision.go
|
|
3389
3422
|
+++ b/internal/app/vision.go
|
|
3390
3423
|
@@ -160,6 +160,7 @@ func fetchImage(src, proxyURL string, idx int) (pendingUpload, error) {
|
|
@@ -3396,7 +3429,7 @@ index b0c9724..b1ca7cb 100644
|
|
|
3396
3429
|
if err != nil {
|
|
3397
3430
|
return pendingUpload{}, fmt.Errorf("image %d: fetch failed: %w", idx, err)
|
|
3398
3431
|
diff --git a/internal/app/xsrf.go b/internal/app/xsrf.go
|
|
3399
|
-
index
|
|
3432
|
+
index 254d686a8f91301427d341f31a08363f4ba6c304..e008abbd2e3e901a8ee7e3b4bde6f1c65833f730 100644
|
|
3400
3433
|
--- a/internal/app/xsrf.go
|
|
3401
3434
|
+++ b/internal/app/xsrf.go
|
|
3402
3435
|
@@ -29,15 +29,23 @@ var (
|
|
@@ -55,10 +55,10 @@ index 0000000..5569c03
|
|
|
55
55
|
+}
|
|
56
56
|
diff --git a/internal/requestdb/legacy_bodies_test.go b/internal/requestdb/legacy_bodies_test.go
|
|
57
57
|
new file mode 100644
|
|
58
|
-
index 0000000..
|
|
58
|
+
index 0000000..f815636
|
|
59
59
|
--- /dev/null
|
|
60
60
|
+++ b/internal/requestdb/legacy_bodies_test.go
|
|
61
|
-
@@ -0,0 +1,
|
|
61
|
+
@@ -0,0 +1,73 @@
|
|
62
62
|
+package requestdb
|
|
63
63
|
+
|
|
64
64
|
+import (
|
|
@@ -71,11 +71,7 @@ index 0000000..2761c06
|
|
|
71
71
|
+
|
|
72
72
|
+func TestLegacyBodiesMigration(t *testing.T) {
|
|
73
73
|
+ file := filepath.Join(t.TempDir(), "requests.db")
|
|
74
|
-
+ db, err := sqlitedb.Open(file)
|
|
75
|
-
+ if err != nil {
|
|
76
|
-
+ t.Fatal(err)
|
|
77
|
-
+ }
|
|
78
|
-
+ _, err = db.Exec(`CREATE TABLE bodies (seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, time INTEGER NOT NULL, request TEXT NOT NULL, request_size INTEGER NOT NULL, response TEXT NOT NULL, response_size INTEGER NOT NULL);
|
|
74
|
+
+ db, err := sqlitedb.Open(file, `CREATE TABLE bodies (seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, time INTEGER NOT NULL, request TEXT NOT NULL, request_size INTEGER NOT NULL, response TEXT NOT NULL, response_size INTEGER NOT NULL);
|
|
79
75
|
+INSERT INTO bodies (id,time,request,request_size,response,response_size) VALUES ('old',1234,'旧请求',100,'reply',200);`)
|
|
80
76
|
+ if err != nil {
|
|
81
77
|
+ t.Fatal(err)
|
|
@@ -113,12 +109,12 @@ index 0000000..2761c06
|
|
|
113
109
|
+}
|
|
114
110
|
+
|
|
115
111
|
+func TestLegacyBodiesMigrationRollback(t *testing.T) {
|
|
116
|
-
+ db, err := sqlitedb.Open(filepath.Join(t.TempDir(), "requests.db"))
|
|
112
|
+
+ db, err := sqlitedb.Open(filepath.Join(t.TempDir(), "requests.db"), schema)
|
|
117
113
|
+ if err != nil {
|
|
118
114
|
+ t.Fatal(err)
|
|
119
115
|
+ }
|
|
120
116
|
+ defer db.Close()
|
|
121
|
-
+ if _, err := db.Exec(
|
|
117
|
+
+ if _, err := db.Exec(`CREATE TABLE bodies (seq INTEGER, id TEXT, time INTEGER, request TEXT, request_size INTEGER, response TEXT, response_size INTEGER);
|
|
122
118
|
+INSERT INTO bodies VALUES (1,'good',1234,'request',1,'reply',1),(2,'bad',1234,NULL,1,'reply',1);`); err != nil {
|
|
123
119
|
+ t.Fatal(err)
|
|
124
120
|
+ }
|
|
@@ -137,24 +133,58 @@ index 0000000..2761c06
|
|
|
137
133
|
+ }
|
|
138
134
|
+}
|
|
139
135
|
diff --git a/internal/requestdb/requestdb.go b/internal/requestdb/requestdb.go
|
|
140
|
-
index
|
|
136
|
+
index 0854684..c83be9a 100644
|
|
141
137
|
--- a/internal/requestdb/requestdb.go
|
|
142
138
|
+++ b/internal/requestdb/requestdb.go
|
|
143
|
-
@@ -
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
139
|
+
@@ -112,7 +112,7 @@ func Open(path string, log Logger) (*Store, error) {
|
|
140
|
+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
141
|
+
return nil, err
|
|
142
|
+
}
|
|
143
|
+
- db, err := sqlitedb.Open(path, schema)
|
|
144
|
+
+ db, err := sqlitedb.Open(path, schema, migrateLegacyBodies)
|
|
145
|
+
if err != nil {
|
|
146
|
+
return nil, fmt.Errorf("初始化请求账本: %w", err)
|
|
147
|
+
}
|
|
148
|
+
diff --git a/internal/sqlitedb/sqlitedb.go b/internal/sqlitedb/sqlitedb.go
|
|
149
|
+
index d65e9fb..ec78bf3 100644
|
|
150
|
+
--- a/internal/sqlitedb/sqlitedb.go
|
|
151
|
+
+++ b/internal/sqlitedb/sqlitedb.go
|
|
152
|
+
@@ -33,7 +33,7 @@ func init() {
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Open 打开或创建数据库,为已有表补上缺少的列、执行 schema 并删除 schema 没有定义的对象,非增量收缩的已有库先整理为增量收缩,最后交还空闲页
|
|
156
|
+
-func Open(path string, schema string) (*sql.DB, error) {
|
|
157
|
+
+func Open(path string, schema string, migrations ...func(*sql.DB) error) (*sql.DB, error) {
|
|
158
|
+
db, err := sql.Open(driverName, "file:"+filepath.ToSlash(path))
|
|
159
|
+
if err != nil {
|
|
160
|
+
return nil, err
|
|
161
|
+
@@ -47,7 +47,7 @@ func Open(path string, schema string) (*sql.DB, error) {
|
|
162
|
+
return nil, errors.Join(fmt.Errorf("整理数据库: %w", err), db.Close())
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
- if err := applySchema(db, schema); err != nil {
|
|
166
|
+
+ if err := applySchema(db, schema, migrations...); err != nil {
|
|
167
|
+
return nil, errors.Join(fmt.Errorf("同步表结构: %w", err), db.Close())
|
|
168
|
+
}
|
|
169
|
+
if err := Reclaim(context.Background(), db); err != nil {
|
|
170
|
+
@@ -57,7 +57,7 @@ func Open(path string, schema string) (*sql.DB, error) {
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// applySchema 为已有表补上 schema 定义而缺少的列,执行 schema,再删除 schema 没有定义的表、索引、视图与触发器
|
|
174
|
+
-func applySchema(db *sql.DB, schema string) error {
|
|
175
|
+
+func applySchema(db *sql.DB, schema string, migrations ...func(*sql.DB) error) error {
|
|
176
|
+
defined, err := sql.Open(driverName, ":memory:")
|
|
177
|
+
if err != nil {
|
|
178
|
+
return err
|
|
179
|
+
@@ -84,6 +84,11 @@ func applySchema(db *sql.DB, schema string) error {
|
|
152
180
|
if _, err := db.Exec(schema); err != nil {
|
|
153
|
-
return
|
|
181
|
+
return err
|
|
154
182
|
}
|
|
155
|
-
+
|
|
156
|
-
+
|
|
183
|
+
+ for _, migrate := range migrations {
|
|
184
|
+
+ if err := migrate(db); err != nil {
|
|
185
|
+
+ return err
|
|
186
|
+
+ }
|
|
157
187
|
+ }
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
188
|
+
for name, kind := range have {
|
|
189
|
+
if _, ok := want[name]; !ok {
|
|
190
|
+
if _, err := db.Exec(`DROP ` + strings.ToUpper(kind) + ` IF EXISTS ` + quoteIdentifier(name)); err != nil {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
diff --git a/internal/config/config.go b/internal/config/config.go
|
|
2
|
-
index
|
|
2
|
+
index d65e9fb..2843043 100644
|
|
3
3
|
--- a/internal/config/config.go
|
|
4
4
|
+++ b/internal/config/config.go
|
|
5
|
-
@@ -
|
|
5
|
+
@@ -68,6 +68,11 @@ var configKeys = [...]string{
|
|
6
6
|
"CONTINUATION_PROMPT",
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ index de280dd..2843043 100644
|
|
|
14
14
|
// LogFileMB 是单个日志文件的轮转大小
|
|
15
15
|
const LogFileMB = 10
|
|
16
16
|
|
|
17
|
-
@@ -
|
|
17
|
+
@@ -424,7 +429,7 @@ func Load(path string) (Config, error) {
|
|
18
18
|
if err != nil {
|
|
19
19
|
return Config{}, err
|
|
20
20
|
}
|
|
@@ -23,10 +23,19 @@ index de280dd..2843043 100644
|
|
|
23
23
|
if err := writeEnv(path, values); err != nil {
|
|
24
24
|
return Config{}, err
|
|
25
25
|
}
|
|
26
|
-
@@ -
|
|
27
|
-
if inFile {
|
|
28
|
-
file[key] = saved[key]
|
|
26
|
+
@@ -443,7 +448,7 @@ func Load(path string) (Config, error) {
|
|
29
27
|
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
- if _, ok := values["WAA_BACKEND"]; !ok {
|
|
31
|
+
+ if _, ok := values["WAA_BACKEND"]; managedBy == "" && !ok {
|
|
32
|
+
if camoufoxOnlyAccounts(cfg.AuthStates) {
|
|
33
|
+
cfg.WAABackend = WAABackendCamoufox
|
|
34
|
+
}
|
|
35
|
+
@@ -612,8 +617,8 @@ func (c Config) Save(path string) error {
|
|
36
|
+
}
|
|
37
|
+
case key == "WAA_BACKEND":
|
|
38
|
+
file[key] = values[key]
|
|
30
39
|
- case slices.Contains(workerDefaultKeys, key) && !inFile && values[key] == previousDefaults[key]:
|
|
31
40
|
- case values[key] != defaults[key]:
|
|
32
41
|
+ case managedBy == "" && slices.Contains(workerDefaultKeys, key) && !inFile && values[key] == previousDefaults[key]:
|
|
@@ -110,4 +119,3 @@ index 0000000..c5d9991
|
|
|
110
119
|
+ t.Fatal("managed settings changed across save/load")
|
|
111
120
|
+ }
|
|
112
121
|
+}
|
|
113
|
-
|
|
@@ -9,7 +9,7 @@ import { installBackend, startBackend, getDataDir } from '../lib/native.mjs';
|
|
|
9
9
|
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
10
10
|
const source = path.join(root, 'backend-source');
|
|
11
11
|
const build = path.join(root, 'backend-build');
|
|
12
|
-
const pinned = '
|
|
12
|
+
const pinned = 'c8a4ac25768b668fa94d293fcb97059c4419b602';
|
|
13
13
|
|
|
14
14
|
const command = process.argv[2];
|
|
15
15
|
|
|
@@ -41,7 +41,7 @@ async function main() {
|
|
|
41
41
|
const sha256 = crypto.createHash('sha256').update(bytes).digest('hex');
|
|
42
42
|
|
|
43
43
|
const manifest = {
|
|
44
|
-
version: '4.27.
|
|
44
|
+
version: '4.27.1-argon.1',
|
|
45
45
|
sourceCommit: pinned,
|
|
46
46
|
sha256,
|
|
47
47
|
platform: 'darwin',
|
|
@@ -47,11 +47,12 @@ try {
|
|
|
47
47
|
assert.equal(config.waa_backend, 'spidermonkey');
|
|
48
48
|
assert.equal(config.search_model_aliases, false);
|
|
49
49
|
assert.equal(config.strip_thought_signatures, false);
|
|
50
|
+
assert.equal(config.hide_dock_icon, false);
|
|
50
51
|
assert.deepEqual(await studioAdmin(local, 'GET', '/api/accounts/tasks'), { tasks: [] });
|
|
51
52
|
// Save upstream defaults that differ from this plugin's defaults. They must stay explicit
|
|
52
53
|
// through the backend's Save and the plugin's next config sync, not get silently re-seeded.
|
|
53
54
|
const choices = {
|
|
54
|
-
search_model_aliases: true, strip_thought_signatures: true, routing_strategy: 'round-robin', per_account_concurrency: 2,
|
|
55
|
+
search_model_aliases: true, strip_thought_signatures: true, hide_dock_icon: true, routing_strategy: 'round-robin', per_account_concurrency: 2,
|
|
55
56
|
first_event_hedge: 'auto', first_event_hedge_models: 'gemini-3.8-flash',
|
|
56
57
|
api_key_accounts: `${local.apiKey}:smoke@example.com`
|
|
57
58
|
};
|
|
@@ -105,7 +106,7 @@ try {
|
|
|
105
106
|
assert.equal(restarted.request_timeout, '5m0s');
|
|
106
107
|
assert.equal(restarted.waa_backend, 'spidermonkey');
|
|
107
108
|
assert.deepEqual(await stopStudio({ dataDir }), { stopped: true });
|
|
108
|
-
console.log(`PASS ${key}: ${STUDIO_VERSION} (${source}), legacy body migration/restart, saved defaults/hedge/models/key bindings across restart, locked plugin settings, SpiderMonkey WAA, managed status, update HTTP 409, unchanged executable/PID, managed UI, clean stop`);
|
|
109
|
+
console.log(`PASS ${key}: ${STUDIO_VERSION} (${source}), legacy body migration/restart, saved defaults/Dock visibility/hedge/models/key bindings across restart, locked plugin settings, SpiderMonkey WAA, managed status, update HTTP 409, unchanged executable/PID, managed UI, clean stop`);
|
|
109
110
|
} finally {
|
|
110
111
|
let stopped = !local;
|
|
111
112
|
if (local) stopped = (await stopStudio({ dataDir }).catch(() => ({ stopped: false }))).stopped;
|
|
@@ -133,6 +133,15 @@ function spidermonkey(key) {
|
|
|
133
133
|
return fetchPinned('SpiderMonkey', `https://github.com/${pin.repository}/releases/download/${pin.release}/${archive.asset}`,
|
|
134
134
|
archive, archive.asset.replace(/\.tar\.gz$/, linux ? '-without-env-interposers' : ''), null, linux ? dropEnvInterposers : null);
|
|
135
135
|
}
|
|
136
|
+
// libwebp returns the folder with a target's pinned libwebp headers (include) and static
|
|
137
|
+
// libraries (lib), the ones upstream builds its own releases with.
|
|
138
|
+
function libwebp(key) {
|
|
139
|
+
const pin = toolchains.libwebp;
|
|
140
|
+
const archive = pin.targets[key];
|
|
141
|
+
if (!archive) throw new Error(`No pinned libwebp for ${key}`);
|
|
142
|
+
return fetchPinned('libwebp', `https://github.com/${pin.repository}/releases/download/${pin.release}/${archive.asset}`,
|
|
143
|
+
archive, archive.asset.replace(/\.tar\.gz$/, ''), null, null);
|
|
144
|
+
}
|
|
136
145
|
function xcode() {
|
|
137
146
|
try {
|
|
138
147
|
const [version, build] = execFileSync('xcodebuild', ['-version'], { encoding: 'utf8' }).trim().split('\n');
|
|
@@ -143,15 +152,16 @@ function xcode() {
|
|
|
143
152
|
}
|
|
144
153
|
// cToolchain returns the C and C++ settings for a target and what they were made with. Only the
|
|
145
154
|
// targets being built and pinned must use the pinned toolchain; the host's is also used for upstream
|
|
146
|
-
// tests. SpiderMonkey's headers and libraries are found through
|
|
147
|
-
// upstream's own build finds them (.github/actions/
|
|
155
|
+
// tests. SpiderMonkey's and libwebp's headers and libraries are found through CGO_CPPFLAGS and
|
|
156
|
+
// CGO_LDFLAGS, as upstream's own build finds them (.github/actions/static-libraries).
|
|
148
157
|
function cToolchain(key, { pin = false } = {}) {
|
|
149
158
|
const check = pin ? pinned : () => {};
|
|
150
159
|
const [goos, goarch] = platforms[key];
|
|
151
160
|
const mozjs = spidermonkey(key);
|
|
161
|
+
const webp = libwebp(key);
|
|
152
162
|
// Quoted whole, as cgo splits these flags, so a folder with spaces stays one argument.
|
|
153
|
-
const include = `"-I${path.join(mozjs, 'include')}"`;
|
|
154
|
-
const lib = `"-L${path.join(mozjs, 'lib')}"`;
|
|
163
|
+
const include = `"-I${path.join(mozjs, 'include')}" "-I${path.join(webp, 'include')}"`;
|
|
164
|
+
const lib = `"-L${path.join(mozjs, 'lib')}" "-L${path.join(webp, 'lib')}"`;
|
|
155
165
|
if (goos === 'darwin') {
|
|
156
166
|
if (process.platform !== 'darwin') throw new Error('Build macOS archives on macOS with Xcode');
|
|
157
167
|
const expected = `${toolchains.xcode.version} (${toolchains.xcode.build})`;
|
|
@@ -162,7 +172,7 @@ function cToolchain(key, { pin = false } = {}) {
|
|
|
162
172
|
clang: execFileSync('clang', ['--version'], { encoding: 'utf8' }).split('\n')[0] };
|
|
163
173
|
const flags = '-O2 -g -mmacosx-version-min=13.0';
|
|
164
174
|
return { compiler, env: { CC: 'clang', CXX: 'clang++', MACOSX_DEPLOYMENT_TARGET: '13.0',
|
|
165
|
-
CGO_CFLAGS: flags, CGO_CXXFLAGS:
|
|
175
|
+
CGO_CPPFLAGS: include, CGO_CFLAGS: flags, CGO_CXXFLAGS: flags, CGO_LDFLAGS: `${flags} ${lib}` } };
|
|
166
176
|
}
|
|
167
177
|
if (goos === 'windows') {
|
|
168
178
|
const { cc, cxx, libs } = llvmMingw();
|
|
@@ -172,7 +182,7 @@ function cToolchain(key, { pin = false } = {}) {
|
|
|
172
182
|
check('Windows C compiler', clang, toolchains.llvmMingw.clang,
|
|
173
183
|
`Unset STUDIO_MINGW_CC to use the pinned llvm-mingw ${toolchains.llvmMingw.version}.`);
|
|
174
184
|
return { compiler: { llvmMingw: toolchains.llvmMingw.version, clang },
|
|
175
|
-
env: { CC: `"${cc}"`, CXX: `"${cxx}"`, CGO_CFLAGS: '-O2 -g', CGO_CXXFLAGS:
|
|
185
|
+
env: { CC: `"${cc}"`, CXX: `"${cxx}"`, CGO_CPPFLAGS: include, CGO_CFLAGS: '-O2 -g', CGO_CXXFLAGS: '-O2 -g',
|
|
176
186
|
CGO_LDFLAGS: `-O2 -g "-L${libs}" ${lib}` } };
|
|
177
187
|
}
|
|
178
188
|
const version = execFileSync(zig, ['version'], { encoding: 'utf8' }).trim();
|
|
@@ -180,7 +190,7 @@ function cToolchain(key, { pin = false } = {}) {
|
|
|
180
190
|
const triple = `${goarch === 'amd64' ? 'x86_64' : 'aarch64'}-linux-musl`;
|
|
181
191
|
return { compiler: { zig: version, target: triple },
|
|
182
192
|
env: { CC: `"${zig}" cc -target ${triple}`, CXX: `"${zig}" c++ -target ${triple}`,
|
|
183
|
-
CGO_CFLAGS: '-O2 -g', CGO_CXXFLAGS:
|
|
193
|
+
CGO_CPPFLAGS: include, CGO_CFLAGS: '-O2 -g', CGO_CXXFLAGS: '-O2 -g', CGO_LDFLAGS: `-O2 -g ${lib}` } };
|
|
184
194
|
}
|
|
185
195
|
const go = (argv, extra = {}) => execFileSync('go', argv, { cwd: source, env: { ...env, ...extra }, stdio: 'inherit' });
|
|
186
196
|
const npm = argv => execFileSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', argv,
|