@bacnh85/pi-web 0.9.1 → 0.9.2

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.2 (2026-09-13)
4
+
5
+ ### Fixed
6
+
7
+ Review hardening of the `web_image` fallback chain (3 review rounds):
8
+
9
+ - **Cancellation semantics**: an aborted call now surfaces `AbortError`
10
+ immediately — before any provider client construction or fetch — instead of
11
+ walking the chain and reporting "all providers failed". An abort landing
12
+ mid-generation normalizes to `AbortError` with the in-flight provider error
13
+ preserved as `cause`; foreign abort-named errors from upstreams are recorded
14
+ as provider notes and the chain continues.
15
+ - **`n` transparency**: when the gemini web tier returns fewer images than the
16
+ requested `n`, the result states it explicitly (`n` applies to the
17
+ `zai`/`custom` API providers; the gemini web tier returns its own count).
18
+ `out_dir` now resolves against the session cwd, not the process cwd.
19
+
3
20
  ## 0.9.1 (2026-09-13)
4
21
 
5
22
  ### Fixed
package/README.md CHANGED
@@ -290,6 +290,9 @@ paths **plus inline image blocks** (multimodal models see the render
290
290
  immediately). `details` reports the winning provider, model, and fallback
291
291
  attempts.
292
292
 
293
+ `n` (1–4) applies to the API providers (`zai`/`custom`); the Gemini web tier
294
+ returns its own image count (surfaced as a provider note when fewer than `n`).
295
+
293
296
  **Guardrails** (soft, in-memory): per-provider `WEB_IMAGE_MIN_INTERVAL_MS`
294
297
  (default 5 s) and a `WEB_IMAGE_DAILY_CAP` (default 20/day, applied to the
295
298
  Gemini web tier only — keyed APIs are billed upstream and stay uncapped).
@@ -541,7 +541,7 @@ export default function piWebExtension(pi: ExtensionAPI) {
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
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
- n: Type.Optional(Type.Number({ default: 1, description: "Number of images, 1-4." })),
544
+ n: Type.Optional(Type.Number({ default: 1, description: "Number of images, 1-4 (applies to zai/custom; the gemini web tier returns its own count)." })),
545
545
  out_dir: Type.Optional(Type.String({ description: "Directory for saved images (default: fresh temp dir)." })),
546
546
  ...sharedControlSchema,
547
547
  }),
@@ -552,7 +552,7 @@ export default function piWebExtension(pi: ExtensionAPI) {
552
552
  const n = Math.min(Math.max(Math.trunc((params.n as number) ?? 1) || 1, 1), 4);
553
553
  const timeoutMs = Math.min(Math.max((params.timeout_ms as number) ?? 180_000, 10_000), 600_000);
554
554
  const outDir = params.out_dir
555
- ? path.resolve(String(params.out_dir))
555
+ ? path.resolve(cwd, String(params.out_dir))
556
556
  : await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-web-image-"));
557
557
  const result = await generateImageWithFallback({
558
558
  prompt,
@@ -574,7 +574,7 @@ export default function piWebExtension(pi: ExtensionAPI) {
574
574
  ...(result.urls.length
575
575
  ? [`Not saved (image host unreachable from this machine — open directly):`, ...result.urls.map((u) => ` ${u}`)]
576
576
  : []),
577
- result.attempts.length ? `Fallback attempts: ${result.attempts.join(" | ")}` : null,
577
+ result.attempts.length ? `Provider notes: ${result.attempts.join(" | ")}` : null,
578
578
  ].filter(Boolean).join("\n");
579
579
  const content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [
580
580
  { type: "text" as const, text },
@@ -308,6 +308,13 @@ export async function generateImageWithFallback(params: ImageChainParams): Promi
308
308
  const chain = chainFor(params.provider);
309
309
  const attempts: string[] = [];
310
310
  for (const provider of chain) {
311
+ // Cancelled calls skip fallback entirely — before any provider client
312
+ // construction or fetch invocation.
313
+ if (params.signal?.aborted) {
314
+ const abortErr = new Error("web_image aborted");
315
+ abortErr.name = "AbortError";
316
+ throw abortErr;
317
+ }
311
318
  const configured =
312
319
  provider === "gemini" ? true : provider === "zai" ? Boolean(params.apiConfig.zai) : Boolean(params.apiConfig.custom);
313
320
  if (!configured) {
@@ -356,8 +363,24 @@ export async function generateImageWithFallback(params: ImageChainParams): Promi
356
363
  });
357
364
  }
358
365
  imageRateRecord(provider);
366
+ if (provider === "gemini" && params.n && params.n > 1 && result.paths.length < params.n) {
367
+ attempts.push(`gemini: n=${params.n} requested — the gemini web tier returns its own image count (${result.paths.length}); n applies to zai/custom`);
368
+ }
359
369
  return { provider, model: result.model, paths: result.paths, urls: result.urls ?? [], attempts };
360
370
  } catch (err) {
371
+ // Cancellation is not a provider failure: rethrow so aborted tool calls
372
+ // surface as AbortError instead of an "all providers failed" listing —
373
+ // even when a genuine provider error (AuthError, a failed save, …) was
374
+ // the error in flight when the abort landed.
375
+ if (params.signal?.aborted) {
376
+ if ((err as Error)?.name === "AbortError") throw err;
377
+ // cause keeps the in-flight provider error for diagnostics.
378
+ const abortErr = new Error("web_image aborted", { cause: err });
379
+ abortErr.name = "AbortError";
380
+ throw abortErr;
381
+ }
382
+ // A foreign AbortError-named error (not from the caller's signal) is a
383
+ // provider failure like any other — record it and keep the chain going.
361
384
  attempts.push(`${provider}: ${provider === "gemini" ? describeGeminiError(err) : describeImageApiError(err)}`);
362
385
  }
363
386
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-web",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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",