pi-banana 2.1.4 → 2.4.0
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/README.md +1 -1
- package/extensions/index.ts +96 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ The model calls `banana_image` or `banana_vision` automatically.
|
|
|
58
58
|
| `prompt` | string | — | Required. What to draw, or what to change about the reference image. |
|
|
59
59
|
| `aspectRatio` | enum | `1:1` | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` |
|
|
60
60
|
| `imageSize` | enum | `1K` | `1K`, `2K`, `4K`. `4K` requires `quality=high`. |
|
|
61
|
-
| `quality` | enum | `fast` | `fast` = Nano Banana 2 (~3–10 s
|
|
61
|
+
| `quality` | enum | `fast` | `lite` = Nano Banana 2 Lite (ultra-low latency, cheapest). `fast` = Nano Banana 2 (~3–10 s). `high` = Nano Banana Pro (slower, top quality). All GA model ids since 2.4.0. |
|
|
62
62
|
| `referenceImages` | array | — | Optional array of paths (PNG/JPEG/WebP/GIF) to edit or use for composition instead of generating from scratch. |
|
|
63
63
|
| `outputPath` | string | `./generated/<slug>-<ts>.png` | Optional output path. Parent dirs are created. |
|
|
64
64
|
|
package/extensions/index.ts
CHANGED
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
* pi-coding-agent renders them inline (Kitty / iTerm2). Also writes the file
|
|
10
10
|
* to disk so the agent and the user always have a path to refer back to.
|
|
11
11
|
*
|
|
12
|
-
* Auth
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Auth (checked in this order — first match wins):
|
|
13
|
+
* 1. pi modelRegistry → auth.json / env vars / models.json
|
|
14
|
+
* (set via /login → Google, or GEMINI_API_KEY env var)
|
|
15
|
+
* 2. Vertex AI ADC — gcloud auth application-default login,
|
|
16
|
+
* GOOGLE_APPLICATION_CREDENTIALS, or GOOGLE_CLOUD_PROJECT
|
|
17
|
+
* 3. GOOGLE_API_KEY env var (AIza… or AQ.…) — pi-banana backward compat
|
|
18
|
+
* Vertex AI Express keys (AQ.…) automatically switch to Vertex.
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
18
22
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
19
23
|
import {
|
|
20
24
|
Box,
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
} from "@earendil-works/pi-tui";
|
|
26
30
|
import { GoogleGenAI } from "@google/genai";
|
|
27
31
|
import { existsSync } from "node:fs";
|
|
32
|
+
import { homedir } from "node:os";
|
|
28
33
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
29
34
|
import { extname, isAbsolute, resolve } from "node:path";
|
|
30
35
|
import { Type } from "typebox";
|
|
@@ -46,26 +51,26 @@ const ASPECT_RATIOS = [
|
|
|
46
51
|
"21:9",
|
|
47
52
|
] as const;
|
|
48
53
|
const IMAGE_SIZES = ["1K", "2K", "4K"] as const;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// `callWithModelFallback`
|
|
58
|
-
//
|
|
59
|
-
const MODEL_FOR_QUALITY: Record<
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
const IMAGE_QUALITY = ["lite", "fast", "high"] as const;
|
|
55
|
+
const VISION_QUALITY = ["fast", "high"] as const;
|
|
56
|
+
type ImageQuality = (typeof IMAGE_QUALITY)[number];
|
|
57
|
+
|
|
58
|
+
// GA ids (Gemini API changelog 2026-05-28: gemini-3.1-flash-image and
|
|
59
|
+
// gemini-3-pro-image went GA; 2026-06-30: gemini-3.1-flash-lite-image GA).
|
|
60
|
+
// Vertex Express keys historically served the `-preview` variants; the
|
|
61
|
+
// GA and preview ids are published at different times per backend, so
|
|
62
|
+
// `callWithModelFallback` retries the other variant on 404. Both work.
|
|
63
|
+
// Verified against the Gemini API changelog on 2026-09-01.
|
|
64
|
+
const MODEL_FOR_QUALITY: Record<ImageQuality, string> = {
|
|
65
|
+
lite: "gemini-3.1-flash-lite-image", // Nano Banana 2 Lite (ultra-low latency, cheapest)
|
|
66
|
+
fast: "gemini-3.1-flash-image", // Nano Banana 2
|
|
67
|
+
high: "gemini-3-pro-image", // Nano Banana Pro
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
const DEFAULT_OUTPUT_DIR =
|
|
65
71
|
process.env.PI_IMAGE_DIR?.trim() || "generated";
|
|
66
72
|
const DEFAULT_QUALITY = (process.env.PI_IMAGE_QUALITY as
|
|
67
|
-
|
|
|
68
|
-
| "high"
|
|
73
|
+
| ImageQuality
|
|
69
74
|
| undefined) ?? "fast";
|
|
70
75
|
const SUPPORTED_INPUT_MIME = new Set([
|
|
71
76
|
"image/png",
|
|
@@ -76,19 +81,71 @@ const SUPPORTED_INPUT_MIME = new Set([
|
|
|
76
81
|
|
|
77
82
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
// Well-known path for gcloud Application Default Credentials.
|
|
85
|
+
const ADC_CREDENTIALS_PATH = resolve(
|
|
86
|
+
homedir(),
|
|
87
|
+
".config/gcloud/application_default_credentials.json",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check whether Vertex AI Application Default Credentials are available.
|
|
92
|
+
* Returns true when any of these are configured:
|
|
93
|
+
* - GOOGLE_APPLICATION_CREDENTIALS points to an existing file
|
|
94
|
+
* - gcloud ADC file exists (~/.config/gcloud/application_default_credentials.json)
|
|
95
|
+
* - GOOGLE_CLOUD_PROJECT is set (implies GKE / Cloud Run / Compute Engine default SA)
|
|
96
|
+
*/
|
|
97
|
+
function hasVertexADC(): boolean {
|
|
98
|
+
const gac = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
99
|
+
if (gac && existsSync(gac)) return true;
|
|
100
|
+
if (existsSync(ADC_CREDENTIALS_PATH)) return true;
|
|
101
|
+
if (process.env.GOOGLE_CLOUD_PROJECT) return true;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function buildClient(
|
|
106
|
+
modelRegistry: ExtensionContext["modelRegistry"] | undefined,
|
|
107
|
+
): Promise<GoogleGenAI> {
|
|
108
|
+
// 1. Pi's model registry — the pi-native auth chain
|
|
109
|
+
// (auth.json → env vars → models.json fallback)
|
|
110
|
+
if (modelRegistry) {
|
|
111
|
+
try {
|
|
112
|
+
const apiKey = await modelRegistry.getApiKeyForProvider("google");
|
|
113
|
+
if (apiKey) {
|
|
114
|
+
return new GoogleGenAI({
|
|
115
|
+
apiKey,
|
|
116
|
+
...(apiKey.startsWith("AQ.") ? { vertexai: true } : {}),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// ignore — fall through
|
|
121
|
+
}
|
|
86
122
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
123
|
+
|
|
124
|
+
// 2. Vertex AI ADC (no API key needed — SDK picks up ADC automatically)
|
|
125
|
+
if (hasVertexADC()) {
|
|
126
|
+
return new GoogleGenAI({ vertexai: true });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 3. GOOGLE_API_KEY env var — backward compat for pi-banana users
|
|
130
|
+
// (pi's standard GEMINI_API_KEY is already covered by modelRegistry above)
|
|
131
|
+
if (process.env.GOOGLE_API_KEY) {
|
|
132
|
+
const key = process.env.GOOGLE_API_KEY;
|
|
133
|
+
return new GoogleGenAI({
|
|
134
|
+
apiKey: key,
|
|
135
|
+
...(key.startsWith("AQ.") ? { vertexai: true } : {}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
throw new Error(
|
|
140
|
+
"No Google credentials found. pi-banana looked in this order:\n" +
|
|
141
|
+
" 1. pi model registry (auth.json → env vars → models.json)\n" +
|
|
142
|
+
" Run /login → Google, or set GEMINI_API_KEY, or configure in models.json\n" +
|
|
143
|
+
" 2. Vertex AI ADC (gcloud auth application-default login,\n" +
|
|
144
|
+
" GOOGLE_APPLICATION_CREDENTIALS, or GOOGLE_CLOUD_PROJECT)\n" +
|
|
145
|
+
" 3. GOOGLE_API_KEY env var (AIza… or AQ.…)\n\n" +
|
|
146
|
+
"For Gemini API: get a key at https://aistudio.google.com/apikey\n" +
|
|
147
|
+
"For Vertex AI: gcloud auth application-default login",
|
|
148
|
+
);
|
|
92
149
|
}
|
|
93
150
|
|
|
94
151
|
function slugify(text: string): string {
|
|
@@ -193,7 +250,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
193
250
|
"inline in the terminal AND saved to disk so it can be re-used. " +
|
|
194
251
|
"Pass `referenceImages` (array of paths) to iterate on existing pictures.",
|
|
195
252
|
promptSnippet:
|
|
196
|
-
"Generate or edit images with Google Nano Banana via
|
|
253
|
+
"Generate or edit images with Google Nano Banana. Requires Google credentials (via /login, GOOGLE_API_KEY / GEMINI_API_KEY, or Vertex AI ADC).",
|
|
197
254
|
promptGuidelines: [
|
|
198
255
|
"Call banana_image when the user asks to create, draw, illustrate, or edit a picture.",
|
|
199
256
|
"For tweaks like 'make the sky purple', pass the previous file path (or paths) as referenceImages to banana_image.",
|
|
@@ -220,9 +277,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
220
277
|
}),
|
|
221
278
|
),
|
|
222
279
|
quality: Type.Optional(
|
|
223
|
-
StringEnum(
|
|
280
|
+
StringEnum(IMAGE_QUALITY, {
|
|
224
281
|
description:
|
|
225
|
-
"'fast' = Nano Banana 2 (default, ~3s, cheap). 'high' = Nano Banana Pro (slower, top quality).",
|
|
282
|
+
"'lite' = Nano Banana 2 Lite (ultra-low latency, cheapest). 'fast' = Nano Banana 2 (default, ~3s, cheap). 'high' = Nano Banana Pro (slower, top quality).",
|
|
226
283
|
default: DEFAULT_QUALITY,
|
|
227
284
|
}),
|
|
228
285
|
),
|
|
@@ -269,7 +326,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
269
326
|
);
|
|
270
327
|
}
|
|
271
328
|
|
|
272
|
-
const client = buildClient();
|
|
329
|
+
const client = await buildClient(ctx.modelRegistry);
|
|
273
330
|
|
|
274
331
|
// Build content parts — text + (optional) reference image.
|
|
275
332
|
const parts: Array<{
|
|
@@ -291,7 +348,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
291
348
|
content: [
|
|
292
349
|
{
|
|
293
350
|
type: "text",
|
|
294
|
-
text: `🎨 ${verbLabel} ${aspectRatio} ${imageSize} image with ${quality === "fast" ? "Nano Banana 2" : "Nano Banana Pro"}…`,
|
|
351
|
+
text: `🎨 ${verbLabel} ${aspectRatio} ${imageSize} image with ${quality === "lite" ? "Nano Banana 2 Lite" : quality === "fast" ? "Nano Banana 2" : "Nano Banana Pro"}…`,
|
|
295
352
|
},
|
|
296
353
|
],
|
|
297
354
|
details: { model, aspectRatio, imageSize, quality, editing },
|
|
@@ -422,7 +479,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
422
479
|
container.addChild(new Spacer(1));
|
|
423
480
|
container.addChild(
|
|
424
481
|
new Image((details as any).imageBase64, (details as any).mimeType ?? "image/png", {
|
|
425
|
-
...theme,
|
|
426
482
|
fallbackColor: (s: string) => theme.fg("muted", s),
|
|
427
483
|
}, {
|
|
428
484
|
maxWidthCells: 80,
|
|
@@ -503,10 +559,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
503
559
|
"to analyze (e.g., ['image1.png']). Relative paths resolve to current working directory.",
|
|
504
560
|
}),
|
|
505
561
|
quality: Type.Optional(
|
|
506
|
-
StringEnum(
|
|
562
|
+
StringEnum(VISION_QUALITY, {
|
|
507
563
|
description:
|
|
508
564
|
"'fast' = gemini-3.1-flash-lite (default, fast/cheap). 'high' = gemini-3.1-pro-preview (slower, deep reasoning).",
|
|
509
|
-
default:
|
|
565
|
+
default: "fast",
|
|
510
566
|
}),
|
|
511
567
|
),
|
|
512
568
|
}),
|
|
@@ -528,7 +584,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
528
584
|
return { content: [{ type: "text", text: "Cancelled." }], details: {} };
|
|
529
585
|
}
|
|
530
586
|
|
|
531
|
-
const client = buildClient();
|
|
587
|
+
const client = await buildClient(ctx.modelRegistry);
|
|
532
588
|
const parts: Array<{
|
|
533
589
|
text?: string;
|
|
534
590
|
inlineData?: { mimeType: string; data: string };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-banana",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Generate, edit, and analyze images in pi using Google Nano Banana (image gen) and Gemini Vision (analysis). Inline terminal preview, reference-image editing, auto-save.",
|
|
5
5
|
"author": "Francesco Frapporti <effedue@gmail.com>",
|
|
6
6
|
"license": "MIT",
|