@pi-unipi/image 2.12.0 → 2.14.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 +8 -1
- package/package.json +2 -2
- package/src/index.ts +37 -5
- package/src/models.ts +24 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Image generation and image recognition tools for the agent.
|
|
|
7
7
|
| Tool | Description |
|
|
8
8
|
|------|-------------|
|
|
9
9
|
| `image_generate` | Generate an image from a text prompt. Returned inline and saved to disk. |
|
|
10
|
-
| `image_recognize` | Analyze an image with a vision model. Accepts a file path, `data:` URL, or base64. |
|
|
10
|
+
| `image_recognize` | Analyze an image with a vision model. Accepts a file path, `data:` URL, or base64. Automatically hidden while the session model itself has vision. |
|
|
11
11
|
|
|
12
12
|
## Commands
|
|
13
13
|
|
|
@@ -63,6 +63,13 @@ works. Supported: PNG, JPEG, GIF, WebP. Remote URLs are not fetched.
|
|
|
63
63
|
Prefer file paths — inlining base64 into the conversation is far more
|
|
64
64
|
expensive in tokens.
|
|
65
65
|
|
|
66
|
+
**Vision models don't get this tool.** When the session's current model already
|
|
67
|
+
accepts image input, `image_recognize` is dropped from the active tool set —
|
|
68
|
+
the model reads images natively through pi's own tools, so a separate
|
|
69
|
+
recognition round-trip through another model would only duplicate that ability
|
|
70
|
+
and burn context. Switch to a text-only model (via `/model`) and the tool comes
|
|
71
|
+
back automatically.
|
|
72
|
+
|
|
66
73
|
## Configuration
|
|
67
74
|
|
|
68
75
|
`~/.unipi/config/image/config.json`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/image",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "Image generation and image recognition tools for the Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "2.
|
|
37
|
+
"@pi-unipi/core": "2.14.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@earendil-works/pi-ai": "^0.84.0",
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,13 @@ import {
|
|
|
20
20
|
|
|
21
21
|
import { registerImageCommands } from "./commands.js";
|
|
22
22
|
import { registerImageTools } from "./tools.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
applyRecognizeGating,
|
|
25
|
+
isVisionModel,
|
|
26
|
+
listImageGenModels,
|
|
27
|
+
listVisionModels,
|
|
28
|
+
type ChatModelRegistry,
|
|
29
|
+
} from "./models.js";
|
|
24
30
|
import { registerRegistryImageProviders } from "./register-providers.js";
|
|
25
31
|
import { loadConfig } from "./settings.js";
|
|
26
32
|
|
|
@@ -37,10 +43,27 @@ function getInfoRegistry() {
|
|
|
37
43
|
).__unipi_info_registry;
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Hide image_recognize when the session model can natively see images, and
|
|
48
|
+
* restore it when a text-only model takes over. Returns whether the tool is
|
|
49
|
+
* provided after gating.
|
|
50
|
+
*/
|
|
51
|
+
function applyVisionGating(pi: ExtensionAPI, model: unknown): boolean {
|
|
52
|
+
const active = pi.getActiveTools();
|
|
53
|
+
const next = applyRecognizeGating(active, model, IMAGE_TOOLS.RECOGNIZE);
|
|
54
|
+
if (next !== active) pi.setActiveTools(next);
|
|
55
|
+
return next.includes(IMAGE_TOOLS.RECOGNIZE);
|
|
56
|
+
}
|
|
57
|
+
|
|
40
58
|
export default function (pi: ExtensionAPI) {
|
|
41
59
|
registerImageTools(pi);
|
|
42
60
|
registerImageCommands(pi);
|
|
43
61
|
|
|
62
|
+
pi.on("model_select", (event) => {
|
|
63
|
+
if (!loadConfig().recognize.enabled) return;
|
|
64
|
+
applyVisionGating(pi, event.model);
|
|
65
|
+
});
|
|
66
|
+
|
|
44
67
|
pi.on("session_start", async (_event, ctx) => {
|
|
45
68
|
const config = loadConfig();
|
|
46
69
|
|
|
@@ -51,9 +74,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
51
74
|
(ctx as unknown as { modelRegistry?: ChatModelRegistry }).modelRegistry,
|
|
52
75
|
).catch(() => undefined);
|
|
53
76
|
|
|
77
|
+
// A vision-capable session model reads images itself, so image_recognize
|
|
78
|
+
// would only duplicate that ability. Drop it from the active tool set;
|
|
79
|
+
// the model_select handler above restores it when a text-only model is
|
|
80
|
+
// chosen later in the same session.
|
|
81
|
+
const recognizeProvided =
|
|
82
|
+
config.recognize.enabled && applyVisionGating(pi, ctx.model);
|
|
83
|
+
|
|
54
84
|
const tools: string[] = [];
|
|
55
85
|
if (config.generate.enabled) tools.push(IMAGE_TOOLS.GENERATE);
|
|
56
|
-
if (
|
|
86
|
+
if (recognizeProvided) tools.push(IMAGE_TOOLS.RECOGNIZE);
|
|
57
87
|
|
|
58
88
|
emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
|
|
59
89
|
name: MODULES.IMAGE,
|
|
@@ -92,9 +122,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
92
122
|
.modelRegistry;
|
|
93
123
|
const vision = chatRegistry ? listVisionModels(chatRegistry) : [];
|
|
94
124
|
|
|
95
|
-
const recognize = current.recognize.enabled
|
|
96
|
-
?
|
|
97
|
-
:
|
|
125
|
+
const recognize = !current.recognize.enabled
|
|
126
|
+
? "Disabled"
|
|
127
|
+
: isVisionModel(ctx.model)
|
|
128
|
+
? "Hidden (model has vision)"
|
|
129
|
+
: current.recognize.model || "Session model";
|
|
98
130
|
|
|
99
131
|
return {
|
|
100
132
|
generate: { value: generate },
|
package/src/models.ts
CHANGED
|
@@ -383,7 +383,7 @@ export function listVisionModels(registry: ChatModelRegistry): VisionModel[] {
|
|
|
383
383
|
return models.filter(isVisionModel);
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
function isVisionModel(model: unknown): model is VisionModel {
|
|
386
|
+
export function isVisionModel(model: unknown): model is VisionModel {
|
|
387
387
|
if (model === null || typeof model !== "object") return false;
|
|
388
388
|
const candidate = model as Partial<VisionModel>;
|
|
389
389
|
if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
|
|
@@ -392,6 +392,29 @@ function isVisionModel(model: unknown): model is VisionModel {
|
|
|
392
392
|
return Array.isArray(candidate.input) && candidate.input.includes("image");
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Active tool names after hiding `recognizeTool` for a vision-capable model.
|
|
397
|
+
*
|
|
398
|
+
* A model that accepts image input can read images natively (pi's own read
|
|
399
|
+
* tool hands it the pixels), so a separate image_recognize round-trip through
|
|
400
|
+
* another model only duplicates the ability and burns system-prompt context.
|
|
401
|
+
* Text-only models get the tool back. Models that do not declare their input
|
|
402
|
+
* modalities are treated as non-vision, matching `isVisionModel`.
|
|
403
|
+
*/
|
|
404
|
+
export function applyRecognizeGating(
|
|
405
|
+
active: string[],
|
|
406
|
+
model: unknown,
|
|
407
|
+
recognizeTool: string,
|
|
408
|
+
): string[] {
|
|
409
|
+
const vision = isVisionModel(model);
|
|
410
|
+
const present = active.includes(recognizeTool);
|
|
411
|
+
// Already correct: hidden for a vision model, or provided for a text-only one.
|
|
412
|
+
if (vision !== present) return active;
|
|
413
|
+
return vision
|
|
414
|
+
? active.filter((name) => name !== recognizeTool)
|
|
415
|
+
: [...active, recognizeTool];
|
|
416
|
+
}
|
|
417
|
+
|
|
395
418
|
/**
|
|
396
419
|
* Resolve a vision-model reference, restricted to image-capable models.
|
|
397
420
|
*
|