@bacnh85/pi-web 0.10.0 → 0.10.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.1 (2026-09-13)
4
+
5
+ ### Added
6
+
7
+ - **Gemini image-refusal session skip** — after 2 consecutive "replied with
8
+ text but no images" refusals (observed on accounts where the chat path
9
+ refuses image generation), `provider: auto` skips gemini for the rest of
10
+ the session and goes straight to `zai`/`custom`; pinned
11
+ `provider=gemini` always retries, and any gemini success resets the
12
+ counter. Session-scoped (resets with pi).
13
+
3
14
  ## 0.10.0 (2026-09-13)
4
15
 
5
16
  ### Added
@@ -82,6 +82,14 @@ interface RateState {
82
82
  const rate = new Map<string, RateState>();
83
83
  let nowMs = () => Date.now();
84
84
 
85
+ // Gemini chat-path image refusals ("replied with text but no images") are
86
+ // sticky on some accounts — the chat intent classifier refuses while the
87
+ // dedicated /images surface would route fine (hypothesis; wire-unverified).
88
+ // After 2 consecutive refusals, skip gemini in AUTO chains for the session.
89
+ // Pinned provider=gemini always attempts; a success resets the counter.
90
+ const GEMINI_REFUSAL_SKIP_THRESHOLD = 2;
91
+ let geminiRefusals = 0;
92
+
85
93
  /** @internal test hooks */
86
94
  export function __setImageRateClock(fn: () => number): void {
87
95
  nowMs = fn;
@@ -90,6 +98,7 @@ export function __setImageRateClock(fn: () => number): void {
90
98
  /** @internal test hooks */
91
99
  export function __resetImageRate(): void {
92
100
  rate.clear();
101
+ geminiRefusals = 0;
93
102
  nowMs = () => Date.now();
94
103
  }
95
104
 
@@ -315,6 +324,10 @@ export async function generateImageWithFallback(params: ImageChainParams): Promi
315
324
  abortErr.name = "AbortError";
316
325
  throw abortErr;
317
326
  }
327
+ if (provider === "gemini" && params.provider === "auto" && geminiRefusals >= GEMINI_REFUSAL_SKIP_THRESHOLD) {
328
+ attempts.push(`gemini: skipped — refused image generation ${geminiRefusals}× consecutively this session (pin provider=gemini to retry)`);
329
+ continue;
330
+ }
318
331
  const configured =
319
332
  provider === "gemini" ? true : provider === "zai" ? Boolean(params.apiConfig.zai) : Boolean(params.apiConfig.custom);
320
333
  if (!configured) {
@@ -363,6 +376,7 @@ export async function generateImageWithFallback(params: ImageChainParams): Promi
363
376
  });
364
377
  }
365
378
  imageRateRecord(provider);
379
+ if (provider === "gemini") geminiRefusals = 0;
366
380
  if (provider === "gemini" && params.n && params.n > 1 && result.paths.length < params.n) {
367
381
  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
382
  }
@@ -379,6 +393,7 @@ export async function generateImageWithFallback(params: ImageChainParams): Promi
379
393
  abortErr.name = "AbortError";
380
394
  throw abortErr;
381
395
  }
396
+ if (provider === "gemini" && err instanceof Error && /no images/.test(err.message)) geminiRefusals++;
382
397
  // A foreign AbortError-named error (not from the caller's signal) is a
383
398
  // provider failure like any other — record it and keep the chain going.
384
399
  attempts.push(`${provider}: ${provider === "gemini" ? describeGeminiError(err) : describeImageApiError(err)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-web",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
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",