@bacnh85/pi-web 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/README.md +4 -4
- package/extensions/index.ts +8 -5
- package/extensions/lib/imageapi.ts +17 -6
- package/package.json +1 -1
- package/skills/pi-web/SKILL.md +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1 (2026-09-13)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`zai` default model is `glm-image`** (live-verified against
|
|
8
|
+
`api.z.ai/api/paas/v4/images/generations` — the endpoint rejects
|
|
9
|
+
`cogview-4`/`cogview-3-flash` with "Unknown Model"; GLM-Image returns
|
|
10
|
+
URL results).
|
|
11
|
+
- **Failed image downloads no longer waste the generation** — some upstreams
|
|
12
|
+
return URL-only results and the CDN can be unreachable from the local
|
|
13
|
+
network (e.g. `mfile.z.ai` DNS-sinkholed). The tool now reports
|
|
14
|
+
"Not saved (image host unreachable…)" with the URL instead of failing the
|
|
15
|
+
provider; inline image blocks are still attached for saved files.
|
|
16
|
+
|
|
3
17
|
## 0.9.0 (2026-09-13)
|
|
4
18
|
|
|
5
19
|
### Added
|
|
@@ -7,7 +21,7 @@
|
|
|
7
21
|
- **`web_image` tool** — image generation from text via free upstream
|
|
8
22
|
providers, all direct-to-upstream (no self-host services), with automatic
|
|
9
23
|
fallback: `gemini` (gemini.google.com web tier via `gemini-reverse`, guest
|
|
10
|
-
or cookie auth) → `zai` (official `api.z.ai`
|
|
24
|
+
or cookie auth) → `zai` (official `api.z.ai` GLM-Image via `ZAI_API_KEY`)
|
|
11
25
|
→ `custom` (any OpenAI-compatible `/images/generations` endpoint via
|
|
12
26
|
`WEB_IMAGE_API_BASE_URL`). `provider: "auto"` walks the chain and the
|
|
13
27
|
result reports every fallback attempt.
|
package/README.md
CHANGED
|
@@ -32,7 +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
|
-
| `ZAI_API_KEY` | No (5) | — | Z.ai API key — enables the `web_image` `zai` provider (
|
|
35
|
+
| `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
36
|
| `WEB_IMAGE_API_BASE_URL` | No | — | `web_image` `custom` provider: any OpenAI-compatible images endpoint (e.g. `https://api.openai.com/v1`) |
|
|
37
37
|
| `WEB_IMAGE_API_KEY` | No | — | Bearer key for the `custom` endpoint |
|
|
38
38
|
| `WEB_IMAGE_API_LABEL` | No | — | Display label for the `custom` endpoint (default: host name) |
|
|
@@ -273,8 +273,8 @@ self-host services):
|
|
|
273
273
|
|
|
274
274
|
```
|
|
275
275
|
web_image(prompt="isometric cutaway of a container ship, technical illustration")
|
|
276
|
-
web_image(prompt="...", provider="zai") # pin
|
|
277
|
-
web_image(prompt="...", model="
|
|
276
|
+
web_image(prompt="...", provider="zai") # pin GLM-Image via api.z.ai
|
|
277
|
+
web_image(prompt="...", model="glm-image", n=2, out_dir="/tmp/imgs")
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
**Provider chain** (`provider: "auto"` tries in order; pin one to skip):
|
|
@@ -282,7 +282,7 @@ web_image(prompt="...", model="cogview-4", n=2, out_dir="/tmp/imgs")
|
|
|
282
282
|
| Provider | Upstream | Auth | Notes |
|
|
283
283
|
|---|---|---|---|
|
|
284
284
|
| `gemini` (default) | gemini.google.com web tier | none (guest) or `GEMINI_WEB_SECURE_1PSID` | free ≈ 20 images/day; availability varies by region/account |
|
|
285
|
-
| `zai` | `https://api.z.ai/api/paas/v4` (official API) | `ZAI_API_KEY` |
|
|
285
|
+
| `zai` | `https://api.z.ai/api/paas/v4` (official API) | `ZAI_API_KEY` | GLM-Image (`model` default), fully ToS-compliant |
|
|
286
286
|
| `custom` | any OpenAI-compatible `/images/generations` endpoint | `WEB_IMAGE_API_KEY` | e.g. official OpenAI `https://api.openai.com/v1` |
|
|
287
287
|
|
|
288
288
|
Results are saved to `out_dir` (default: fresh temp dir) and returned as file
|
package/extensions/index.ts
CHANGED
|
@@ -108,7 +108,7 @@ const WEB_ROUTING_GUIDANCE = `## Web Tool Routing (pi-web)
|
|
|
108
108
|
- **web_crawl** — multi-page crawl: \`mode: "light"\` (Firecrawl, url) or \`mode: "full"\` (Crawl4AI, urls[]).
|
|
109
109
|
- **web_screenshot** / **web_pdf** — page capture (Crawl4AI).
|
|
110
110
|
- **web_research** — AI-synthesized research via Gemini web (mode "ask" = grounded answer, guest OK; mode "research" = Deep Research report, needs cookie + Gemini Advanced, takes minutes).
|
|
111
|
-
- **web_image** — text→image generation via free upstreams (auto: Gemini web → Z.ai
|
|
111
|
+
- **web_image** — text→image generation via free upstreams (auto: Gemini web → Z.ai GLM-Image → custom OpenAI-images endpoint; \`model\`/\`n\` params).
|
|
112
112
|
- **web_status** — provider config + health.
|
|
113
113
|
|
|
114
114
|
Rules: Firecrawl Search is weak on domain-specific queries — prefer SearXNG/Brave; Firecrawl Scrape fails on bot-protected sites — use Crawl4AI (\`mode: "full"\`) then agy (\`mode: "agy"\`); cite source URLs.`;
|
|
@@ -529,8 +529,8 @@ export default function piWebExtension(pi: ExtensionAPI) {
|
|
|
529
529
|
name: "web_image",
|
|
530
530
|
label: "Web Image Generation",
|
|
531
531
|
description:
|
|
532
|
-
"Generate images from text via free upstream providers, with fallback: Gemini web (gemini.google.com, guest or cookie auth), Z.ai official API (
|
|
533
|
-
promptSnippet: "Generate images via free upstreams (Gemini web, Z.ai
|
|
532
|
+
"Generate images from text via free upstream providers, with fallback: Gemini web (gemini.google.com, guest or cookie auth), Z.ai official API (GLM-Image via ZAI_API_KEY), or any custom OpenAI-compatible images endpoint (WEB_IMAGE_API_BASE_URL). Returns saved file paths plus the images inline.",
|
|
533
|
+
promptSnippet: "Generate images via free upstreams (Gemini web, Z.ai GLM-Image)",
|
|
534
534
|
promptGuidelines: [
|
|
535
535
|
"Use for image GENERATION from a text prompt. provider auto falls back gemini → zai → custom. Capturing an EXISTING page is web_screenshot, not this.",
|
|
536
536
|
],
|
|
@@ -540,7 +540,7 @@ export default function piWebExtension(pi: ExtensionAPI) {
|
|
|
540
540
|
[Type.Literal("auto"), Type.Literal("gemini"), Type.Literal("zai"), Type.Literal("custom")],
|
|
541
541
|
{ default: "auto", description: "auto = gemini → zai (if ZAI_API_KEY) → custom (if WEB_IMAGE_API_BASE_URL); pin one to skip fallback." },
|
|
542
542
|
)),
|
|
543
|
-
model: Type.Optional(Type.String({ description: "Provider-specific model (e.g.
|
|
543
|
+
model: Type.Optional(Type.String({ description: "Provider-specific model (e.g. glm-image, or a Gemini image-capable model id). Omit for the provider default." })),
|
|
544
544
|
n: Type.Optional(Type.Number({ default: 1, description: "Number of images, 1-4." })),
|
|
545
545
|
out_dir: Type.Optional(Type.String({ description: "Directory for saved images (default: fresh temp dir)." })),
|
|
546
546
|
...sharedControlSchema,
|
|
@@ -571,13 +571,16 @@ export default function piWebExtension(pi: ExtensionAPI) {
|
|
|
571
571
|
`Provider: ${result.provider}${result.model ? ` (${result.model})` : ""}`,
|
|
572
572
|
`Saved: ${result.paths.length} image(s)`,
|
|
573
573
|
...result.paths.map((p) => ` ${p}`),
|
|
574
|
+
...(result.urls.length
|
|
575
|
+
? [`Not saved (image host unreachable from this machine — open directly):`, ...result.urls.map((u) => ` ${u}`)]
|
|
576
|
+
: []),
|
|
574
577
|
result.attempts.length ? `Fallback attempts: ${result.attempts.join(" | ")}` : null,
|
|
575
578
|
].filter(Boolean).join("\n");
|
|
576
579
|
const content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [
|
|
577
580
|
{ type: "text" as const, text },
|
|
578
581
|
...blocks,
|
|
579
582
|
];
|
|
580
|
-
return { content, details: { provider: result.provider, model: result.model, paths: result.paths, attempts: result.attempts } };
|
|
583
|
+
return { content, details: { provider: result.provider, model: result.model, paths: result.paths, urls: result.urls, attempts: result.attempts } };
|
|
581
584
|
},
|
|
582
585
|
});
|
|
583
586
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generic OpenAI-compatible images client + fallback chain for web_image.
|
|
2
|
-
// Serves the `zai` preset (official api.z.ai,
|
|
2
|
+
// Serves the `zai` preset (official api.z.ai, GLM-Image) and any `custom`
|
|
3
3
|
// OpenAI-images endpoint — direct-to-upstream plain fetch, no self-host.
|
|
4
4
|
|
|
5
5
|
import fs from "node:fs";
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
// Config
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
|
|
21
|
-
export const ZAI_PRESET = { baseUrl: "https://api.z.ai/api/paas/v4", defaultModel: "
|
|
21
|
+
export const ZAI_PRESET = { baseUrl: "https://api.z.ai/api/paas/v4", defaultModel: "glm-image" } as const;
|
|
22
22
|
|
|
23
23
|
export interface ImageApiConfig {
|
|
24
24
|
zai?: { apiKey: string; source: string };
|
|
@@ -155,6 +155,8 @@ export interface FetchLike {
|
|
|
155
155
|
|
|
156
156
|
export interface ApiImageResult {
|
|
157
157
|
paths: string[];
|
|
158
|
+
/** Image URLs that could not be downloaded (e.g. CDN unreachable) — the generation still happened. */
|
|
159
|
+
urls: string[];
|
|
158
160
|
model?: string;
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -212,17 +214,25 @@ export async function apiGenerateImage(opts: {
|
|
|
212
214
|
if (!items.length) throw new Error(`upstream returned no image data (model ${opts.model ?? "default"})`);
|
|
213
215
|
fs.mkdirSync(opts.outDir, { recursive: true });
|
|
214
216
|
const paths: string[] = [];
|
|
217
|
+
const urls: string[] = [];
|
|
215
218
|
for (let i = 0; i < items.length; i++) {
|
|
216
219
|
const item = items[i];
|
|
217
220
|
if (typeof item?.b64_json === "string" && item.b64_json) {
|
|
218
221
|
paths.push(writeB64(opts.outDir, item.b64_json, i));
|
|
219
222
|
} else if (typeof item?.url === "string" && item.url) {
|
|
220
|
-
|
|
223
|
+
// A failed download must not waste the generation: surface the URL.
|
|
224
|
+
try {
|
|
225
|
+
paths.push(await downloadImage(fetchImpl, item.url, opts.outDir, i, opts.signal, opts.timeoutMs));
|
|
226
|
+
} catch (err) {
|
|
227
|
+
urls.push(item.url);
|
|
228
|
+
void err;
|
|
229
|
+
}
|
|
221
230
|
} else {
|
|
222
231
|
throw new Error(`image item ${i} had neither b64_json nor url`);
|
|
223
232
|
}
|
|
224
233
|
}
|
|
225
|
-
|
|
234
|
+
if (!paths.length && !urls.length) throw new Error(`upstream returned no image data (model ${opts.model ?? "default"})`);
|
|
235
|
+
return { paths, urls, model: typeof payload?.model === "string" ? payload.model : opts.model };
|
|
226
236
|
}
|
|
227
237
|
|
|
228
238
|
function writeB64(outDir: string, b64: string, i: number): string {
|
|
@@ -267,6 +277,7 @@ export interface ImageChainResult {
|
|
|
267
277
|
provider: ImageProvider;
|
|
268
278
|
model?: string;
|
|
269
279
|
paths: string[];
|
|
280
|
+
urls: string[];
|
|
270
281
|
attempts: string[];
|
|
271
282
|
}
|
|
272
283
|
|
|
@@ -309,7 +320,7 @@ export async function generateImageWithFallback(params: ImageChainParams): Promi
|
|
|
309
320
|
continue;
|
|
310
321
|
}
|
|
311
322
|
try {
|
|
312
|
-
let result: { paths: string[]; model?: string };
|
|
323
|
+
let result: { paths: string[]; urls?: string[]; model?: string };
|
|
313
324
|
if (provider === "gemini") {
|
|
314
325
|
result = await geminiGenerateImage(params.prompt, {
|
|
315
326
|
config: params.geminiConfig,
|
|
@@ -345,7 +356,7 @@ export async function generateImageWithFallback(params: ImageChainParams): Promi
|
|
|
345
356
|
});
|
|
346
357
|
}
|
|
347
358
|
imageRateRecord(provider);
|
|
348
|
-
return { provider, model: result.model, paths: result.paths, attempts };
|
|
359
|
+
return { provider, model: result.model, paths: result.paths, urls: result.urls ?? [], attempts };
|
|
349
360
|
} catch (err) {
|
|
350
361
|
attempts.push(`${provider}: ${provider === "gemini" ? describeGeminiError(err) : describeImageApiError(err)}`);
|
|
351
362
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-web",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Pi extension for web search, page extraction, Firecrawl scraping/crawling, Crawl4AI headless browser crawling, Gemini web-tier research, and free upstream image generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/skills/pi-web/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pi-web
|
|
3
|
-
description: Web search, content extraction, site crawling, page capture, Gemini web-tier research, and free upstream image generation via the pi-web extension. Use when the user needs current web search results, documentation lookup, factual research, AI-synthesized research with sources (Gemini Deep Research), image generation from text (Gemini web, Z.ai
|
|
3
|
+
description: Web search, content extraction, site crawling, page capture, Gemini web-tier research, and free upstream image generation via the pi-web extension. Use when the user needs current web search results, documentation lookup, factual research, AI-synthesized research with sources (Gemini Deep Research), image generation from text (Gemini web, Z.ai GLM-Image), source discovery, URL-to-markdown extraction, JSON extraction from websites, site URL discovery, site crawling, or page screenshots/PDFs. Use when the user mentions searching the web, finding docs, looking something up, researching deeply, generating/creating an image, scraping/extracting content from a URL, or capturing a page.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# pi-web — Unified Web Tools
|
|
@@ -18,7 +18,7 @@ Use the **9 unified tools** from the `pi-web` extension for all web-related task
|
|
|
18
18
|
| `web_screenshot` | Capture page screenshot as PNG | Crawl4AI daemon (public URLs) or local headless Chrome (localhost/LAN/file URLs — auto-detected) |
|
|
19
19
|
| `web_pdf` | Generate page PDF | Crawl4AI daemon (public URLs) or local headless Chrome (localhost/LAN/file URLs — auto-detected) |
|
|
20
20
|
| `web_research` | AI-synthesized research with sources | Gemini web tier: ask = grounded answer (guest OK); research = Deep Research report (cookie + Gemini Advanced) |
|
|
21
|
-
| `web_image` | Generate images from a text prompt | Gemini web (guest/cookie) → Z.ai
|
|
21
|
+
| `web_image` | Generate images from a text prompt | Gemini web (guest/cookie) → Z.ai GLM-Image (`ZAI_API_KEY`) → custom OpenAI-images endpoint |
|
|
22
22
|
| `web_status` | Check provider configuration and health | — |
|
|
23
23
|
|
|
24
24
|
## Decision Tree
|
|
@@ -62,8 +62,8 @@ What do you need?
|
|
|
62
62
|
│
|
|
63
63
|
├── Generate an image from a text prompt (NOT capturing an existing page)
|
|
64
64
|
│ → web_image
|
|
65
|
-
│ ├─ default: provider=auto (Gemini web → Z.ai
|
|
66
|
-
│ └─ pin/model: provider=zai model=
|
|
65
|
+
│ ├─ default: provider=auto (Gemini web → Z.ai GLM-Image → custom endpoint)
|
|
66
|
+
│ └─ pin/model: provider=zai model=glm-image, or any custom OpenAI-images endpoint
|
|
67
67
|
│
|
|
68
68
|
└── Check what web tools are configured
|
|
69
69
|
→ web_status
|