@bacnh85/pi-web 0.10.3 → 0.10.5
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/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/extensions/lib/gemini.ts +17 -5
- package/extensions/lib/imageapi.ts +15 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.5 (2026-09-13)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`GEMINI_WEB_SECURE_1PSIDTS` env** — the rotating `__Secure-1PSIDTS`
|
|
8
|
+
session cookie, injected into the client jar pre-init. Google stopped
|
|
9
|
+
serving SNlM0e/cookie rotations to plain clients; without it the session
|
|
10
|
+
is partially authed (chat works, `accessToken` unresolved, sensitive
|
|
11
|
+
surfaces refuse). With it: full auth (`accessToken` resolves).
|
|
12
|
+
|
|
13
|
+
## 0.10.4 (2026-09-13)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Truthful image extensions**: saved files are typed by magic bytes, not
|
|
18
|
+
URL/file extension — Z.ai GLM-Image serves JPEG behind a `.png` URL, which
|
|
19
|
+
previously produced mislabeled files and inline blocks.
|
|
20
|
+
|
|
3
21
|
## 0.10.3 (2026-09-13)
|
|
4
22
|
|
|
5
23
|
### Fixed
|
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ Variables:
|
|
|
32
32
|
| `CRAWL4AI_API_TOKEN` | No (3) | — | Required if Crawl4AI auth enabled |
|
|
33
33
|
| `GEMINI_WEB_SECURE_1PSID` | No (4) | — | `__Secure-1PSID` cookie from gemini.google.com — enables authed `web_research` (Deep Research) |
|
|
34
34
|
| `GEMINI_WEB_PROXY` | No | — | Proxy URL for Gemini web calls (escape hatch if Google blocks the IP) |
|
|
35
|
+
| `GEMINI_WEB_SECURE_1PSIDTS` | No (6) | — | Rotating `__Secure-1PSIDTS` cookie — restores full auth when Google withholds SNlM0e from plain clients (recommended for image surfaces) |
|
|
35
36
|
| `ZAI_API_KEY` | No (5) | — | Z.ai API key — enables the `web_image` `zai` provider (GLM-Image via the official `api.z.ai`); `Z_AI_API_KEY` also accepted |
|
|
36
37
|
| `WEB_IMAGE_API_BASE_URL` | No | — | `web_image` `custom` provider: any OpenAI-compatible images endpoint (e.g. `https://api.openai.com/v1`) |
|
|
37
38
|
| `WEB_IMAGE_API_KEY` | No | — | Bearer key for the `custom` endpoint |
|
|
@@ -45,6 +46,7 @@ Variables:
|
|
|
45
46
|
> (2) Required for hosted Firecrawl; optional for self-hosted instances without auth.
|
|
46
47
|
> (3) Required for Crawl4AI v0.9+ default config.
|
|
47
48
|
> (4) Without it `web_research mode=ask` still works in guest mode (Flash-only); `mode=research` errors with setup steps.
|
|
49
|
+
> (6) Copy the current value from DevTools (Application → Cookies) alongside `__Secure-1PSID`; it rotates, so refresh it when auth degrades.
|
|
48
50
|
> (5) `web_image` works with zero config via Gemini guest mode (availability varies by region/account); `zai` activates when `ZAI_API_KEY` is present, `custom` when `WEB_IMAGE_API_BASE_URL` is set.
|
|
49
51
|
|
|
50
52
|
Secrets are never printed; `web_status` reports only presence/source.
|
package/extensions/lib/gemini.ts
CHANGED
|
@@ -14,13 +14,15 @@ import { findEnvValue } from "./config";
|
|
|
14
14
|
export interface GeminiWebConfig {
|
|
15
15
|
psid?: string;
|
|
16
16
|
psidSource: string;
|
|
17
|
+
psidts?: string;
|
|
17
18
|
proxy?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function loadGeminiWebConfig(cwd = process.cwd(), includeCwdEnv = false): GeminiWebConfig {
|
|
21
22
|
const psid = findEnvValue("GEMINI_WEB_SECURE_1PSID", cwd, includeCwdEnv);
|
|
23
|
+
const psidts = findEnvValue("GEMINI_WEB_SECURE_1PSIDTS", cwd, includeCwdEnv);
|
|
22
24
|
const proxy = findEnvValue("GEMINI_WEB_PROXY", cwd, includeCwdEnv);
|
|
23
|
-
return { psid: psid.value, psidSource: psid.value ? psid.source : "not set", proxy: proxy.value };
|
|
25
|
+
return { psid: psid.value, psidSource: psid.value ? psid.source : "not set", psidts: psidts.value, proxy: proxy.value };
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// ---------------------------------------------------------------------------
|
|
@@ -66,7 +68,7 @@ export interface GeminiClientLike {
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
export type GeminiClientFactory = (
|
|
69
|
-
opts: { secure_1psid?: string; proxy?: string },
|
|
71
|
+
opts: { secure_1psid?: string; secure_1psidts?: string; proxy?: string },
|
|
70
72
|
) => GeminiClientLike | Promise<GeminiClientLike>;
|
|
71
73
|
|
|
72
74
|
// Cached per config (psid|proxy) so a config change re-creates the client.
|
|
@@ -137,14 +139,24 @@ export async function loadDefaultFactory(): Promise<GeminiClientFactory> {
|
|
|
137
139
|
}
|
|
138
140
|
// ponytail: generous per-request cap (covers research); per-mode ask/research
|
|
139
141
|
// timeouts are enforced by raceGuard below.
|
|
140
|
-
|
|
142
|
+
// ponytail: Google's rotating __Secure-1PSIDTS is required for sensitive
|
|
143
|
+
// surfaces (image generation refuses with "You might be signed out" when it
|
|
144
|
+
// is missing); the page HTML no longer carries SNlM0e/rotations for plain
|
|
145
|
+
// clients, so the user passes it explicitly and we inject it pre-init.
|
|
146
|
+
return (opts) => {
|
|
147
|
+
const client = new Gemini({ secure_1psid: opts.secure_1psid, proxy: opts.proxy ?? null, timeout: 1_800_000 });
|
|
148
|
+
if (opts.secure_1psidts) {
|
|
149
|
+
(client as unknown as { cookies: Record<string, string> }).cookies["__Secure-1PSIDTS"] = opts.secure_1psidts;
|
|
150
|
+
}
|
|
151
|
+
return client;
|
|
152
|
+
};
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
async function getClient(config: GeminiWebConfig, factory?: GeminiClientFactory): Promise<GeminiClientLike> {
|
|
144
|
-
const key = `${config.psid ?? ""}|${config.proxy ?? ""}`;
|
|
156
|
+
const key = `${config.psid ?? ""}|${config.psidts ?? ""}|${config.proxy ?? ""}`;
|
|
145
157
|
if (cached?.key === key) return cached.client;
|
|
146
158
|
const make = factory ?? (await loadDefaultFactory());
|
|
147
|
-
const client = await make({ secure_1psid: config.psid, proxy: config.proxy });
|
|
159
|
+
const client = await make({ secure_1psid: config.psid, secure_1psidts: config.psidts, proxy: config.proxy });
|
|
148
160
|
cached = { key, client };
|
|
149
161
|
return client;
|
|
150
162
|
}
|
|
@@ -238,7 +238,7 @@ export async function apiGenerateImage(opts: {
|
|
|
238
238
|
for (let i = 0; i < items.length; i++) {
|
|
239
239
|
const item = items[i];
|
|
240
240
|
if (typeof item?.b64_json === "string" && item.b64_json) {
|
|
241
|
-
paths.push(writeB64(opts.outDir, item.b64_json, i));
|
|
241
|
+
paths.push(writeB64(opts.outDir, Buffer.from(item.b64_json, "base64"), i));
|
|
242
242
|
} else if (typeof item?.url === "string" && item.url) {
|
|
243
243
|
// A failed download must not waste the generation: surface the URL.
|
|
244
244
|
try {
|
|
@@ -256,9 +256,19 @@ export async function apiGenerateImage(opts: {
|
|
|
256
256
|
return { paths, urls, model: typeof payload?.model === "string" ? payload.model : opts.model };
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
// Some gateways serve JPEG/WebP bytes behind a .png URL (Z.ai GLM-Image does) —
|
|
260
|
+
// trust the magic bytes, not the URL/file extension.
|
|
261
|
+
function extFromBytes(buf: Buffer, fallback: string): string {
|
|
262
|
+
if (buf.length >= 4 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return ".png";
|
|
263
|
+
if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return ".jpg";
|
|
264
|
+
if (buf.length >= 6 && buf.toString("ascii", 0, 3) === "GIF") return ".gif";
|
|
265
|
+
if (buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP") return ".webp";
|
|
266
|
+
return fallback;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function writeB64(outDir: string, buf: Buffer, i: number): string {
|
|
270
|
+
const file = path.join(outDir, `pi-web-image-${randomUUID().slice(0, 8)}-${i}${extFromBytes(buf, ".png")}`);
|
|
271
|
+
fs.writeFileSync(file, buf);
|
|
262
272
|
return file;
|
|
263
273
|
}
|
|
264
274
|
|
|
@@ -290,7 +300,7 @@ async function downloadImage(
|
|
|
290
300
|
if (!res.ok) throw new ImageApiError(res.status, `image download failed (HTTP ${res.status})`);
|
|
291
301
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
292
302
|
if (buf.length > MAX_DOWNLOAD_BYTES) throw new Error(`image exceeds the ${MAX_DOWNLOAD_BYTES}-byte download cap`);
|
|
293
|
-
const file = path.join(outDir, `pi-web-image-${randomUUID().slice(0, 8)}-${i}${extFor(url)}`);
|
|
303
|
+
const file = path.join(outDir, `pi-web-image-${randomUUID().slice(0, 8)}-${i}${extFromBytes(buf, extFor(url))}`);
|
|
294
304
|
fs.writeFileSync(file, buf);
|
|
295
305
|
return file;
|
|
296
306
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-web",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Pi extension for web search, page extraction, Firecrawl scraping/crawling, Crawl4AI headless browser crawling, Gemini web-tier research, free upstream image generation, and one-off gateway chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|