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/README.en.md +10 -7
- package/README.md +10 -7
- package/docs/backend-release.md +3 -1
- package/docs/studio-release.md +5 -5
- package/index.mjs +37 -9
- package/lib/backend-release.json +15 -15
- package/lib/backend.mjs +34 -8
- package/lib/native.mjs +77 -26
- package/lib/setup.mjs +96 -2
- package/lib/studio-native.mjs +58 -9
- package/lib/studio-release.json +19 -19
- package/lib/studio.mjs +2 -4
- package/package.json +2 -1
- package/patches/argon.patch +781 -168
- package/scripts/backend-argon.mjs +2 -2
- package/scripts/backend.mjs +17 -5
- package/scripts/studio-managed-smoke.mjs +10 -6
- package/scripts/studio-regzip.go +75 -0
- package/scripts/studio-release.mjs +2 -0
- package/scripts/studio.mjs +8 -6
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
|
|
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
|
|
package/lib/studio-native.mjs
CHANGED
|
@@ -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', '
|
|
195
|
-
'
|
|
196
|
-
'
|
|
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', '--
|
|
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
|
package/lib/studio-release.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.2.
|
|
2
|
+
"version": "0.2.16-magpie.1",
|
|
3
3
|
"repository": "BUKOWSKIREAL/magpie-gemini-web",
|
|
4
|
-
"tag": "studio-v0.2.
|
|
4
|
+
"tag": "studio-v0.2.16-magpie.1",
|
|
5
5
|
"goVersion": "1.27.1",
|
|
6
6
|
"upstream": {
|
|
7
|
-
"version": "0.2.
|
|
7
|
+
"version": "0.2.16",
|
|
8
8
|
"repository": "https://github.com/Mag1cFall/AIStudio2API.git",
|
|
9
|
-
"commit": "
|
|
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.
|
|
15
|
-
"sha256": "
|
|
16
|
-
"binarySha256": "
|
|
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.
|
|
20
|
-
"sha256": "
|
|
21
|
-
"binarySha256": "
|
|
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.
|
|
25
|
-
"sha256": "
|
|
26
|
-
"binarySha256": "
|
|
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.
|
|
30
|
-
"sha256": "
|
|
31
|
-
"binarySha256": "
|
|
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.
|
|
35
|
-
"sha256": "
|
|
36
|
-
"binarySha256": "
|
|
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
|
|
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)}` : '
|
|
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.
|
|
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/",
|