magpie-gemini-web 0.5.6 → 0.6.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/lib/setup.mjs CHANGED
@@ -1,5 +1,36 @@
1
1
  // Sign-in helpers for the one-click setup: turn what the user pasted into a Cookie header and
2
2
  // add it to the local backend's account pool through its admin API.
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ // Do not infer support from a version: upstream and local builds can have the same version
8
+ // while silently ignoring proxy_id. Recheck at submission too, in case the service restarted.
9
+ export async function requireLoginBackend(setup, options = {}) {
10
+ const fetchFn = options.fetchFn || globalThis.fetch;
11
+ let res, info;
12
+ try {
13
+ res = await fetchFn(`${setup.origin}/`, {
14
+ method: 'GET', redirect: 'error', signal: AbortSignal.timeout(5000)
15
+ });
16
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
17
+ info = await res.json();
18
+ } catch (err) {
19
+ throw new Error(`无法确认本地 Gemini Web 后端的登录能力(${setup.origin}):${err.message}`);
20
+ }
21
+ if (info?.capabilities?.account_proxy_binding === true) return;
22
+ const dataDir = options.dataDir || process.env.GEMINI_WEB_DATA_DIR || path.join(os.homedir(), '.local', 'share', 'magpie-gemini-web');
23
+ const platform = options.platform || process.platform;
24
+ const script = fileURLToPath(new URL('../scripts/backend.mjs', import.meta.url));
25
+ // PowerShell and POSIX shells both accept single-quoted literal arguments, but escape
26
+ // apostrophes differently. Never interpolate credentials or proxy URLs into the hint.
27
+ const quote = value => `'${value.replaceAll("'", platform === 'win32' ? "''" : "'\\''")}'`;
28
+ const command = `node ${quote(script)} install --data-dir=${quote(path.resolve(dataDir))}`;
29
+ const stop = platform === 'win32'
30
+ ? '在任务管理器的「详细信息」中结束本数据目录对应的 gemini-web2api-go.exe'
31
+ : '在活动监视器或进程管理器中停止本数据目录对应的 gemini-web2api-go';
32
+ throw new Error(`正在运行的 Gemini Web 后端不支持登录时绑定代理,请先升级。先退出 Magpie,${stop},再执行:\n${command}\n数据目录:${path.resolve(dataDir)}。如下载需要代理,请先设置 GEMINI_WEB_PROXY 环境变量。升级后重新打开 Magpie 并登录;账号和配置会保留。`);
33
+ }
3
34
 
4
35
  // The sign-in cookies a gemini.google.com request carries; without these the backend serves
5
36
  // the request anonymously (or the account check fails).
@@ -79,6 +110,31 @@ export function parseCookieInput(input) {
79
110
  }
80
111
 
81
112
  const ADMIN_TIMEOUT_MS = 60000;
113
+ const PROXY_PROTOCOLS = new Set(['http:', 'https:', 'socks5:', 'socks5h:']);
114
+
115
+ // validateProxyURL checks a proxy URL meant for the backend's proxy pool (http, https, socks5,
116
+ // or socks5h). An empty or omitted value returns '', meaning no proxy is set here.
117
+ export function validateProxyURL(proxy) {
118
+ if (proxy === undefined || proxy === null || proxy === '') return '';
119
+ if (typeof proxy !== 'string') {
120
+ throw new Error('Invalid option proxy: must be a string');
121
+ }
122
+ const trimmed = proxy.trim();
123
+ if (!trimmed) return '';
124
+ if (/[\r\n]/.test(trimmed)) {
125
+ throw new Error('Invalid proxy URL: must not contain newlines');
126
+ }
127
+ let parsed;
128
+ try {
129
+ parsed = new URL(trimmed);
130
+ } catch {
131
+ throw new Error('代理地址格式无效:请填写完整的 URL,例如 http://127.0.0.1:7890 或 socks5://127.0.0.1:1080');
132
+ }
133
+ if (!PROXY_PROTOCOLS.has(parsed.protocol)) {
134
+ throw new Error('代理地址必须以 http://、https://、socks5:// 或 socks5h:// 开头');
135
+ }
136
+ return trimmed;
137
+ }
82
138
 
83
139
  // adminCall is one request to the backend's admin API. A request that gets no answer within
84
140
  // ADMIN_TIMEOUT_MS fails with timeoutMessage when one is given: the backend was reached, but what
@@ -108,10 +164,41 @@ async function adminCall(setup, fetchFn, method, path, body, timeoutMessage) {
108
164
  return data;
109
165
  }
110
166
 
167
+ // ensureProxy makes sure the given proxy URL is present, enabled, and not circuit-broken in the
168
+ // backend's proxy pool before the backend reaches gemini.google.com to check a cookie.
169
+ export async function ensureProxy(setup, proxy, options = {}) {
170
+ const url = validateProxyURL(proxy);
171
+ if (!url) return null;
172
+ const fetchFn = options.fetchFn || globalThis.fetch;
173
+ const name = options.name || 'Magpie';
174
+ const listed = await adminCall(setup, fetchFn, 'GET', 'proxies');
175
+ const items = Array.isArray(listed?.items) ? listed.items : [];
176
+ const exact = items.find(p => p && p.url === url && Number.isInteger(p.id));
177
+ if (exact) {
178
+ if (!exact.enabled) {
179
+ await adminCall(setup, fetchFn, 'PATCH', `proxies/${exact.id}`, { enabled: true });
180
+ }
181
+ if ((exact.fail_count ?? 0) > 0) {
182
+ await adminCall(setup, fetchFn, 'POST', `proxies/${exact.id}/reset`);
183
+ }
184
+ return { id: exact.id, url };
185
+ }
186
+ const managed = items.find(p => p && p.name === name && Number.isInteger(p.id));
187
+ if (managed) {
188
+ await adminCall(setup, fetchFn, 'PATCH', `proxies/${managed.id}`, { name, url, enabled: true });
189
+ if ((managed.fail_count ?? 0) > 0) {
190
+ await adminCall(setup, fetchFn, 'POST', `proxies/${managed.id}/reset`);
191
+ }
192
+ return { id: managed.id, url };
193
+ }
194
+ const created = await adminCall(setup, fetchFn, 'POST', 'proxies', { name, url, weight: 1 });
195
+ return { id: created.id, url };
196
+ }
197
+
111
198
  // The check is the backend fetching gemini.google.com with the cookie, directly or through the
112
199
  // backend's own proxy pool: neither Magpie's proxy nor HTTP_PROXY reach it.
113
200
  function proxyHint(setup) {
114
- return `后端不走 Magpie 的代理,也不读系统代理(HTTP_PROXY / HTTPS_PROXY):需要代理才能访问 Google 的话,请在管理面板 ${setup.origin}/admin 的代理池里添加(登录口令是数据目录 secrets.json 里的 adminToken),然后重试。`;
201
+ return `后端不走 Magpie 的代理,也不读系统代理(HTTP_PROXY / HTTPS_PROXY):需要代理才能访问 Google 的话,请在登录时填写代理地址(或设置插件选项 proxy / 环境变量 GEMINI_WEB_PROXY,也可以在管理面板 ${setup.origin}/admin 的代理池里添加,登录口令是数据目录 secrets.json 里的 adminToken),然后重试。`;
115
202
  }
116
203
 
117
204
  // checkFailure is the error for a check that came back not ok. The backend words the cookie's
@@ -128,11 +215,18 @@ function checkFailure(setup, detail) {
128
215
  // account that fails the check is removed again, and the error says why.
129
216
  export async function importCookie(setup, cookie, options = {}) {
130
217
  const fetchFn = options.fetchFn || globalThis.fetch;
218
+ await requireLoginBackend(setup, options);
219
+ let proxyId = Number.isInteger(options.proxyId) ? options.proxyId : undefined;
220
+ if (options.proxy) {
221
+ const ensured = await ensureProxy(setup, options.proxy, { fetchFn });
222
+ if (ensured && Number.isInteger(ensured.id)) proxyId = ensured.id;
223
+ }
131
224
  const label = options.label || `Magpie ${new Date().toISOString().slice(0, 10)}`;
132
225
  const added = await adminCall(setup, fetchFn, 'POST', 'cookies', {
133
226
  label,
134
227
  cookie,
135
- note: 'Added by Magpie one-click setup'
228
+ note: 'Added by Magpie one-click setup',
229
+ ...(proxyId !== undefined ? { proxy_id: proxyId } : {})
136
230
  });
137
231
  if (!Number.isInteger(added.id)) throw new Error('本地后端没有返回新账号的 id');
138
232
 
@@ -3,6 +3,8 @@ import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import crypto from 'node:crypto';
5
5
  import net from 'node:net';
6
+ import http from 'node:http';
7
+ import { Readable } from 'node:stream';
6
8
  import { fileURLToPath } from 'node:url';
7
9
  import { spawn, execFile, execFileSync } from 'node:child_process';
8
10
  import { promisify } from 'node:util';
@@ -16,6 +18,7 @@ export const STUDIO_VERSION = `v${release.version}`;
16
18
  export const PINNED_ARCHIVES = release.binaries;
17
19
  // The port a new setup tries first: AIStudio2API's own default.
18
20
  export const DEFAULT_STUDIO_PORT = 2048;
21
+ const DEFAULT_PREVIEW_PORT = 2050;
19
22
  // backend.log is set aside as backend.log.1 when a start finds it past this size.
20
23
  const LOG_ROTATE_BYTES = 10 * 1024 * 1024;
21
24
 
@@ -34,7 +37,7 @@ export function setupStep(dataDir) {
34
37
  }
35
38
 
36
39
  export function studioDataDir(env = process.env) {
37
- return env.AI_STUDIO_DATA_DIR || path.join(os.homedir(), '.local', 'share', 'magpie-ai-studio');
40
+ return path.resolve(env.AI_STUDIO_DATA_DIR || path.join(os.homedir(), '.local', 'share', 'magpie-ai-studio'));
38
41
  }
39
42
 
40
43
  export function computeFileHash(filePath) {
@@ -59,7 +62,7 @@ export function studioCommand(action, { dataDir } = {}) {
59
62
  // has free. A user who already runs AIStudio2API keeps it on its default port, which a new setup
60
63
  // would otherwise find taken on every start.
61
64
  export async function pickStudioPort(preferred = DEFAULT_STUDIO_PORT) {
62
- if (!(await portAnswers(preferred)) && await canListen(preferred)) return preferred;
65
+ if (preferred > 0 && !(await portAnswers(preferred)) && await canListen(preferred)) return preferred;
63
66
  return new Promise((resolve, reject) => {
64
67
  const server = net.createServer();
65
68
  server.once('error', reject);
@@ -189,11 +192,12 @@ export function validateConfig(config) {
189
192
 
190
193
  // ENV_KEYS are the settings upstream reads from .env, in the order it writes them (config.configKeys).
191
194
  const ENV_KEYS = [
192
- 'AISTUDIO_AUTH_STATES', 'LISTEN_ADDR', 'PROXY_API_KEY', 'PROXY', 'INIT_TIMEOUT', 'REQUEST_TIMEOUT',
195
+ 'AISTUDIO_AUTH_STATES', 'LISTEN_ADDR', 'PREVIEW_LISTEN_ADDR', 'PROXY_API_KEY', 'PROXY', 'INIT_TIMEOUT', 'REQUEST_TIMEOUT',
193
196
  'WARM_WORKER_LIMIT', 'MAX_ACTIVE_WORKERS', 'WARM_STARTUP_CONCURRENCY', 'PER_ACCOUNT_CONCURRENCY',
194
- 'ROUTING_STRATEGY', 'UPSTREAM_CHANNELS', 'MODEL_MAPPING', 'TEMPORARY_CHAT', 'WAA_BACKEND', 'AUTO_START',
195
- 'REQUEST_BODY_LOG', 'LOG_FILE', 'LOG_MAX_TOTAL_MB', 'ADMIN_AUTH_ENABLED', 'ADMIN_USERNAME', 'ADMIN_PASSWORD',
196
- 'BUILD_NATIVE_NONSTREAM', 'MODEL_CHANNELS', 'MEDIA_OUTPUT', 'MEDIA_BASE_URL'
197
+ 'ROUTING_STRATEGY', 'UPSTREAM_CHANNELS', 'MODEL_MAPPING', 'MODEL_CHANNELS', 'TEMPORARY_CHAT', 'WAA_BACKEND',
198
+ 'MEDIA_OUTPUT', 'MEDIA_BASE_URL', 'AUTO_START', 'REQUEST_BODY_LOG', 'LOG_FILE', 'LOG_MAX_TOTAL_MB',
199
+ 'ADMIN_AUTH_ENABLED', 'ADMIN_USERNAME', 'ADMIN_PASSWORD', 'BUILD_NATIVE_NONSTREAM',
200
+ 'NONSTREAM_KEEPALIVE', 'FAKE_STREAM', 'YOUTUBE_LINKS'
197
201
  ];
198
202
 
199
203
  // PROXY_ENV are the proxy variables the service would fall back to with no PROXY of its own.
@@ -211,7 +215,8 @@ const SETTING_DEFAULTS = {
211
215
  WARM_STARTUP_CONCURRENCY: '1',
212
216
  ROUTING_STRATEGY: 'fill-first',
213
217
  REQUEST_TIMEOUT: '30m',
214
- INIT_TIMEOUT: '2m'
218
+ INIT_TIMEOUT: '2m',
219
+ NONSTREAM_KEEPALIVE: 'false'
215
220
  };
216
221
 
217
222
  // pluginEnv is the settings this plugin depends on to reach, trust and drive the service. The
@@ -305,6 +310,17 @@ function writeStudioEnv(dataDir, values) {
305
310
  fs.chmodSync(envPath, 0o600);
306
311
  }
307
312
 
313
+ function previewPort(addr) {
314
+ const match = /^.+:(\d+)$/.exec(String(addr ?? '').trim());
315
+ if (!match) return 0;
316
+ const port = Number(match[1]);
317
+ return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : 0;
318
+ }
319
+
320
+ function defaultPreviewPort(studioPort) {
321
+ return studioPort === DEFAULT_PREVIEW_PORT ? DEFAULT_PREVIEW_PORT + 1 : DEFAULT_PREVIEW_PORT;
322
+ }
323
+
308
324
  // syncStudioEnv brings .env up to date with config.json, keeping what the admin page saved: this
309
325
  // plugin's settings are written over, the others only when missing. A missing PROXY is the one
310
326
  // given, else the one an older config.json kept (which nothing reads after that).
@@ -312,10 +328,26 @@ function syncStudioEnv(dataDir, config, seedProxy) {
312
328
  const values = readStudioEnv(dataDir);
313
329
  Object.assign(values, pluginEnv(dataDir, config));
314
330
  for (const [key, value] of Object.entries(SETTING_DEFAULTS)) values[key] ??= value;
331
+ const preview = previewPort(values.PREVIEW_LISTEN_ADDR);
332
+ if (!preview || preview === config.port) {
333
+ values.PREVIEW_LISTEN_ADDR = `127.0.0.1:${defaultPreviewPort(config.port)}`;
334
+ }
315
335
  values.PROXY ??= validateProxy(seedProxy) || config.proxy;
316
336
  writeStudioEnv(dataDir, values);
317
337
  }
318
338
 
339
+ // ensurePreviewPort makes sure PREVIEW_LISTEN_ADDR in .env is on a free port other than
340
+ // studioPort before the service starts: upstream listens on both and exits when either is taken.
341
+ async function ensurePreviewPort(dataDir, studioPort) {
342
+ const values = readStudioEnv(dataDir);
343
+ const current = previewPort(values.PREVIEW_LISTEN_ADDR);
344
+ if (current && current !== studioPort && !(await portAnswers(current)) && await canListen(current)) return;
345
+ let port = await pickStudioPort(defaultPreviewPort(studioPort));
346
+ while (port === studioPort) port = await pickStudioPort(0);
347
+ values.PREVIEW_LISTEN_ADDR = `127.0.0.1:${port}`;
348
+ writeStudioEnv(dataDir, values);
349
+ }
350
+
319
351
  // saveStudioProxy saves the service's proxy in its .env, as the admin page does. The service
320
352
  // takes it the next time its generation service starts.
321
353
  export function saveStudioProxy(dataDir, proxy) {
@@ -398,6 +430,20 @@ export function localStudio(options = {}) {
398
430
  };
399
431
  }
400
432
 
433
+ // nodeFetch is fetch over node:http, bounded only by its signal.
434
+ export function nodeFetch(url, init = {}) {
435
+ return new Promise((resolve, reject) => {
436
+ const options = { method: init.method, headers: Object.fromEntries(new Headers(init.headers)), signal: init.signal };
437
+ const req = http.request(url, options, res => {
438
+ const headers = new Headers();
439
+ for (let i = 0; i < res.rawHeaders.length; i += 2) headers.append(res.rawHeaders[i], res.rawHeaders[i + 1]);
440
+ resolve(new Response(res.statusCode === 204 ? null : Readable.toWeb(res), { status: res.statusCode, headers }));
441
+ });
442
+ req.on('error', reject);
443
+ req.end(init.body ?? undefined);
444
+ });
445
+ }
446
+
401
447
  export async function studioAdmin(local, method, pathname, body = null, options = {}) {
402
448
  if (!local || typeof local !== 'object') {
403
449
  throw new Error('localStudio configuration required for studioAdmin');
@@ -484,11 +530,13 @@ export async function studioAdmin(local, method, pathname, body = null, options
484
530
  payload = String(body);
485
531
  }
486
532
  }
533
+ // Bun's fetch drops a connection idle for 300 s; the signal sets the deadline instead.
487
534
  return fetchFn(url, {
488
535
  method,
489
536
  headers,
490
537
  body: payload,
491
538
  redirect: 'error',
539
+ timeout: false,
492
540
  signal: AbortSignal.timeout(timeoutMs)
493
541
  });
494
542
  }
@@ -893,7 +941,7 @@ export function verifyProcessIdentity(pid, expectedBinaryPath, platform = proces
893
941
  '-NoProfile',
894
942
  '-NonInteractive',
895
943
  '-Command',
896
- `Get-CimInstance Win32_Process -Filter "ProcessId = $env:TARGET_PID" | Select-Object -ExpandProperty ExecutablePath`
944
+ `[Console]::OutputEncoding = [Text.UTF8Encoding]::new(); Get-CimInstance Win32_Process -Filter "ProcessId = $env:TARGET_PID" | Select-Object -ExpandProperty ExecutablePath`
897
945
  ], {
898
946
  env: { ...process.env, TARGET_PID: String(pid) },
899
947
  encoding: 'utf8',
@@ -1188,7 +1236,7 @@ export async function installStudio(options = {}) {
1188
1236
  fs.writeFileSync(archivePath, buffer, { mode: 0o600 });
1189
1237
  } else {
1190
1238
  // Without a progress meter curl's stderr holds just the reason a download failed.
1191
- const curlArgs = ['--fail', '--location', '--silent', '--show-error', '--connect-timeout', '15', '--max-time', '300'];
1239
+ const curlArgs = ['--fail', '--location', '--silent', '--show-error', '--connect-timeout', '15', '--speed-limit', '1', '--speed-time', '60'];
1192
1240
  if (proxy) {
1193
1241
  curlArgs.push('--proxy', proxy);
1194
1242
  }
@@ -1513,6 +1561,7 @@ async function launchStudio(options) {
1513
1561
  if (health.state === 'foreign') throw portTaken(local);
1514
1562
  if (health.state !== 'down') throw notResponding(local, health);
1515
1563
  assertStudioExited(dataDir, platform);
1564
+ await ensurePreviewPort(dataDir, config.port);
1516
1565
  protectPackage(dataDir, platform);
1517
1566
 
1518
1567
  // Clean environment: no host AISTUDIO_* or WAA_* vars, nor any of upstream's settings, which
@@ -1,39 +1,39 @@
1
1
  {
2
- "version": "0.2.14-magpie.1",
2
+ "version": "0.2.16-magpie.1",
3
3
  "repository": "BUKOWSKIREAL/magpie-gemini-web",
4
- "tag": "studio-v0.2.14-magpie.1",
4
+ "tag": "studio-v0.2.16-magpie.1",
5
5
  "goVersion": "1.27.1",
6
6
  "upstream": {
7
- "version": "0.2.14",
7
+ "version": "0.2.16",
8
8
  "repository": "https://github.com/Mag1cFall/AIStudio2API.git",
9
- "commit": "370a313814099b508881807effa2a9d72f2d7e66",
9
+ "commit": "fd6aab611b52fbfe179fbfc1bf830c4343066e32",
10
10
  "camoufox": "152.0.4-beta.29"
11
11
  },
12
12
  "binaries": {
13
13
  "darwin-arm64": {
14
- "asset": "aistudio2api-v0.2.14-magpie.1-darwin-arm64.tar.gz",
15
- "sha256": "f649c21837741bdb448407f79a3bc998429b446408ef3117a897103b9a47ee72",
16
- "binarySha256": "999e49572dede3a63f538e3f62ceed5f03349459bdf391fd49fd57784985e13b"
14
+ "asset": "aistudio2api-v0.2.16-magpie.1-darwin-arm64.tar.gz",
15
+ "sha256": "3dd83c7c7fef6bbf96dc7d8eb6dd7257955af6c274c0fc67ad2ecb24a7df9992",
16
+ "binarySha256": "6f07122038587514bebe9f15ba0b00f7d9916d4db130028669380729b2e97c97"
17
17
  },
18
18
  "darwin-x64": {
19
- "asset": "aistudio2api-v0.2.14-magpie.1-darwin-amd64.tar.gz",
20
- "sha256": "ece680b5dfef57b0b0e96d639b618b74570e6e194f58808d762da85fd41d9b4a",
21
- "binarySha256": "970df3ac51719721d908162d0233d80711f6c3ee007e193d0a73b6e4e596ca9a"
19
+ "asset": "aistudio2api-v0.2.16-magpie.1-darwin-amd64.tar.gz",
20
+ "sha256": "4152cbb37d83680c903d1bb3e72e7eaebd80e5e0df617c20e3ba7fbffd160868",
21
+ "binarySha256": "a0ad46091dd4ec20f0d82ffa3f14673ac3b5c70dc4a8a0e4cc8aac658525d050"
22
22
  },
23
23
  "linux-x64": {
24
- "asset": "aistudio2api-v0.2.14-magpie.1-linux-amd64.tar.gz",
25
- "sha256": "f6c7b52a739e4534236f055280fcddd48e64b56fdb954a7844eb453226d5b67f",
26
- "binarySha256": "316306225bcc343188d6d81635d4bd79e7e82bd5c51b1009cd25361deb3b5e61"
24
+ "asset": "aistudio2api-v0.2.16-magpie.1-linux-amd64.tar.gz",
25
+ "sha256": "7f97398ea29c2413d3c3e12aca9525213bc80b97c6667a68429ccd3e8dc27ba1",
26
+ "binarySha256": "deef4cc68cafff147ad24fcaed2d42389d2dbd54d081696c3d6ea95b7d0ea8fa"
27
27
  },
28
28
  "linux-arm64": {
29
- "asset": "aistudio2api-v0.2.14-magpie.1-linux-arm64.tar.gz",
30
- "sha256": "a5b838b9b64e78d402372cae6b88e1f3faba406747e598e976728c71da3a75ef",
31
- "binarySha256": "a5e9573b9edad7fb246178fb4c3d21a0d4905789fd59abf96656d57f26b7526e"
29
+ "asset": "aistudio2api-v0.2.16-magpie.1-linux-arm64.tar.gz",
30
+ "sha256": "2fccc892f00e17b8171ab2ed5d22b4a413b6e7d8230bb911bc5e846c5dc8f717",
31
+ "binarySha256": "567c61bec4f7cf199eabd05ecb3420f409039064fe02a2e38ed22e8b35db3da9"
32
32
  },
33
33
  "win32-x64": {
34
- "asset": "aistudio2api-v0.2.14-magpie.1-windows-amd64.zip",
35
- "sha256": "ccd5b96dabebbc0f1d510959c698beb9d9af09bb65d8b0e021327970b3173384",
36
- "binarySha256": "ce784c98f1558395a670421ef6cc7d36dde5313f74788f7df5772851c241ae06"
34
+ "asset": "aistudio2api-v0.2.16-magpie.1-windows-amd64.zip",
35
+ "sha256": "e7d115aacd957ffda80cf2ecadc38af8404d780bc45af01b6b945accc50d8bc7",
36
+ "binarySha256": "bf96178445dbb653b6876b3abe1768b880465974531d6fb12892857fbc666818"
37
37
  }
38
38
  }
39
39
  }
package/lib/studio.mjs CHANGED
@@ -660,15 +660,13 @@ export const AIStudioPlugin = async (_input, options = {}) => {
660
660
  }
661
661
  }
662
662
 
663
- // setupNote is a line for a sign-in that must set the service up before its login window can
664
- // open: the download (from GitHub, and on Windows not through the system proxy) shows nothing
665
- // until it ends, which can take minutes.
663
+ // setupNote says a sign-in must set the service up first, which can take minutes.
666
664
  function setupNote() {
667
665
  let proxy = '';
668
666
  try {
669
667
  proxy = downloadProxy(studioDataDir(), opts.proxy);
670
668
  } catch {}
671
- const via = proxy ? `走代理 ${redactProxy(proxy)}` : '直连 GitHub,不走系统代理';
669
+ const via = proxy ? `走代理 ${redactProxy(proxy)}` : '按 Magpie 的代理设置';
672
670
  return `本地 AI Studio 服务还没启动:要先启动它,第一次使用或插件更新后还要先从 GitHub 下载(约 25 MB,${via}),可能要等几分钟。${SETUP_PROXY_HINT}\n`;
673
671
  }
674
672
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magpie-gemini-web",
3
- "version": "0.5.6",
3
+ "version": "0.6.0",
4
4
  "description": "Gemini Web and Google AI Studio provider for Magpie, backed by gemini-web2api-go and AIStudio2API",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
@@ -27,6 +27,7 @@
27
27
  "scripts/studio.mjs",
28
28
  "scripts/studio-release.mjs",
29
29
  "scripts/studio-archive.go",
30
+ "scripts/studio-regzip.go",
30
31
  "scripts/studio-managed-smoke.mjs",
31
32
  "patches/",
32
33
  "native-task/lib/",