@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 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` CogView-4 via `ZAI_API_KEY`)
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 (CogView-4 via the official `api.z.ai`); `Z_AI_API_KEY` also accepted |
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 CogView-4 via api.z.ai
277
- web_image(prompt="...", model="cogview-4", n=2, out_dir="/tmp/imgs")
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` | CogView-4 (`model` default), fully ToS-compliant |
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
@@ -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 CogView-4 → custom OpenAI-images endpoint; \`model\`/\`n\` params).
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 (CogView-4 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 CogView)",
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. cogview-4, or a Gemini image-capable model id). Omit for the provider default." })),
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, CogView-4) and any `custom`
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: "cogview-4" } as const;
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
- paths.push(await downloadImage(fetchImpl, item.url, opts.outDir, i, opts.signal, opts.timeoutMs));
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
- return { paths, model: typeof payload?.model === "string" ? payload.model : opts.model };
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.0",
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",
@@ -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 CogView-4), 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.
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 CogView-4 (`ZAI_API_KEY`) → custom OpenAI-images endpoint |
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 CogView-4 → custom endpoint)
66
- │ └─ pin/model: provider=zai model=cogview-4, or any custom OpenAI-images endpoint
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