pi-multimodal-proxy 1.21.0 → 1.22.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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.22.0] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Claude Haiku 5.5 in the implicit-model candidate chain** — `DEFAULT_MODEL_FALLBACKS` now reads `zai/glm-5.3-flash` → `deepseek/deepseek-v4-flash-vision-exp` → `anthropic/claude-haiku-5-5` (Pi ≥ 1.1.0 catalog) → `anthropic/claude-sonnet-5` → `anthropic/claude-sonnet-4-5`. An anthropic-keyed setup whose cheaper candidates are unkeyed lands on Haiku 5.5 ($0.1/M in, $0.5/M out, vision-capable, 1M context; above 100K input tokens $0.5/$2.5 — output still 4× cheaper than Sonnet's flat rate) instead of Sonnet 5 ($2/M in, $10/M out) — 20× cheaper output at typical description-prompt sizes, same API key. Description prompts (a few images + instructions) sit far below the tier threshold. Haiku's catalog `inputLimits.images.resize` (2000 px / q80) feeds the existing model-aware upload downscale. Pre-1.1.0 catalogs simply don't contain Haiku, so the registry check skips it and behavior is unchanged; explicit model choices are never rewritten.
|
|
12
|
+
- **`durationMs` fallback in the `analyze_image` result row (Pi ≥ 1.1.0, feature-detected)** — the pi 1.1.0 tool render context carries `durationMs`, the monotonic-clock `execute()` time recorded with the final result. `formatAnalyzeImageResult` accepts it as a display fallback when `details` carry no `latencyMs`, so rows replayed from foreign sessions, HTML exports, and results recorded before `details` existed still render `✓ … · 1.2s`. `details.latencyMs` (the vision call itself, also reported to codemode scripts via `structuredContent`) always wins — the host duration covers consent checks and crop encoding too, so the two measure different things. Older hosts pass no `durationMs` and rendering is unchanged.
|
|
13
|
+
- **Per-invocation tool toggling documented** — pi 1.1.0's `--tools +name/-name` entries change the default tool selection instead of replacing it. README now shows `pi -t -analyze_image` (one-shot session without the re-query tool) and `pi -t +analyze_image` (add it back to a trimmed selection) alongside the existing persistent toggles (`/multimodal-proxy tool off`, `PI_VISION_PROXY_TOOL=off`). Privacy section updated to spell out the full current default-model chain.
|
|
14
|
+
- Six new tests (546 total, all green): Haiku placement/ordering in the chain, key-aware preference of Haiku over Sonnet, catalog-without-Haiku skip, `durationMs` fallback/preference/finite-guard in the result formatter. Syntax check clean (8 files).
|
|
15
|
+
|
|
7
16
|
## [1.21.0] - 2026-10-03
|
|
8
17
|
|
|
9
18
|
### Added
|
package/README.md
CHANGED
|
@@ -10,6 +10,14 @@ When **video or audio files** are detected, they are routed to a **multimodal mo
|
|
|
10
10
|
|
|
11
11
|
**Compact tool rows (Pi ≥ 1.0.1)** — `analyze_image` and `generate_image` calls render as one tidy line each (`analyze_image "What does the legend say?" · 2 images (image="deadbeef…", shot.png) · crop bottom-right@0`) with themed result summaries (`✓ zai/glm-5.3-flash · cached · 1.2s`, `✓ 1 image via google/imagen-4 · $0.0311`), instead of the default JSON-args dump. Generated images still render inline from the result blocks; expand a row to see the full analysis fence.
|
|
12
12
|
|
|
13
|
+
## What's new in 1.22.0
|
|
14
|
+
|
|
15
|
+
Borrowing from Pi 1.1.0:
|
|
16
|
+
|
|
17
|
+
- **Claude Haiku 5.5 joins the implicit-model candidate chain** — `zai/glm-5.3-flash` → `deepseek/deepseek-v4-flash-vision-exp` → `anthropic/claude-haiku-5-5` (new, Pi ≥ 1.1.0) → `anthropic/claude-sonnet-5` → `anthropic/claude-sonnet-4-5`. When your cheaper candidates have no API key, an anthropic-keyed setup now lands on Haiku 5.5 instead of Sonnet 5 for image descriptions — $0.1/M in, $0.5/M out vs $2/M, $10/M (20× cheaper output at typical description-prompt sizes; above 100K input tokens Haiku is $0.5/$2.5, still 4× cheaper output). Same key, still vision-capable, and its catalog `inputLimits.images.resize` feeds the model-aware upload downscale. Catalogs without Haiku (pi < 1.1.0) skip it and behave exactly as before; explicit choices are never rewritten.
|
|
18
|
+
- **Result rows show a duration even without `details`** — pi 1.1.0's render context carries `durationMs` (the recorded `execute()` time of a final tool result). The `analyze_image` result line now falls back to it when its `details` have no `latencyMs`, so rows replayed from foreign/HTML-exported sessions still show `✓ … · 1.2s`. Our own `latencyMs` (the vision call itself, reported to codemode scripts via `structuredContent`) always wins.
|
|
19
|
+
- **Per-invocation tool toggling documented** — pi 1.1.0's `--tools +name/-name` entries modify the default tool selection instead of replacing it, so `pi -t -analyze_image` runs a one-shot session without the re-query tool while keeping everything else (see [Slash commands](#slash-commands)).
|
|
20
|
+
|
|
13
21
|
## What's new in 1.21.0
|
|
14
22
|
|
|
15
23
|
Adopting Pi 1.0.1's new `pi.registerToolRenderer()` API:
|
|
@@ -249,6 +257,8 @@ Legacy alias: /vision-proxy <args> works identically.
|
|
|
249
257
|
|
|
250
258
|
When an env var is set, the matching `/multimodal-proxy` subcommand is locked.
|
|
251
259
|
|
|
260
|
+
For one-shot sessions you can also toggle tools per invocation (Pi ≥ 1.1.0): `pi -t -analyze_image` removes the re-query tool from the default selection, `pi -t +analyze_image` adds it back to a trimmed one. This complements the persistent `/multimodal-proxy tool off` and `PI_VISION_PROXY_TOOL=off`, which survive across sessions.
|
|
261
|
+
|
|
252
262
|
## How it works — Images
|
|
253
263
|
|
|
254
264
|
```
|
|
@@ -375,7 +385,7 @@ When a model is in the grounding registry, a format-specific instruction is appe
|
|
|
375
385
|
|
|
376
386
|
## Privacy & security
|
|
377
387
|
|
|
378
|
-
This extension **sends data to a third-party provider**. By default that is `zai/glm-5.3-flash` for images
|
|
388
|
+
This extension **sends data to a third-party provider**. By default that is `zai/glm-5.3-flash` for images, falling back through `deepseek/deepseek-v4-flash-vision-exp` → `anthropic/claude-haiku-5-5` (Pi ≥ 1.1.0) → `anthropic/claude-sonnet-5` → `anthropic/claude-sonnet-4-5` to the first candidate in your catalog **and keyed** (`xai/grok-4.3` for video/audio). Be aware:
|
|
379
389
|
|
|
380
390
|
1. **Image and video data is uploaded** to the configured provider on every proxied request. Crop coordinates are applied locally before upload — only the cropped region is sent.
|
|
381
391
|
2. **Recent conversation context** (last 8 messages, truncated) is uploaded with the image unless you set `/multimodal-proxy context off` or `PI_VISION_PROXY_INCLUDE_CONTEXT=false`. Disable it for sensitive sessions.
|
|
@@ -3312,6 +3312,41 @@ describe("1.18.0 key-aware default model resolution", () => {
|
|
|
3312
3312
|
assert.equal(DEFAULT_MODEL_FALLBACKS[0]!.modelId, "deepseek-v4-flash-vision-exp");
|
|
3313
3313
|
});
|
|
3314
3314
|
|
|
3315
|
+
it("places Claude Haiku 5.5 between DeepSeek and Sonnet 5 (pi ≥ 1.1.0 catalogs)", () => {
|
|
3316
|
+
const haiku = DEFAULT_MODEL_FALLBACKS.find((m) => m.modelId === "claude-haiku-5-5")!;
|
|
3317
|
+
assert.equal(haiku.provider, "anthropic");
|
|
3318
|
+
const deepseekIdx = DEFAULT_MODEL_FALLBACKS.findIndex((m) => m.provider === "deepseek");
|
|
3319
|
+
const sonnetIdx = DEFAULT_MODEL_FALLBACKS.findIndex((m) => m.modelId === "claude-sonnet-5");
|
|
3320
|
+
assert.ok(deepseekIdx < DEFAULT_MODEL_FALLBACKS.indexOf(haiku));
|
|
3321
|
+
assert.ok(DEFAULT_MODEL_FALLBACKS.indexOf(haiku) < sonnetIdx);
|
|
3322
|
+
});
|
|
3323
|
+
|
|
3324
|
+
it("prefers Haiku 5.5 over Sonnet 5 when both are keyed anthropic candidates", () => {
|
|
3325
|
+
const out = applyDefaultModelFallback(
|
|
3326
|
+
builtin,
|
|
3327
|
+
registryWith(
|
|
3328
|
+
`${builtin.provider}/${builtin.modelId}`,
|
|
3329
|
+
"anthropic/claude-haiku-5-5",
|
|
3330
|
+
"anthropic/claude-sonnet-5",
|
|
3331
|
+
),
|
|
3332
|
+
false,
|
|
3333
|
+
keyWith("anthropic/claude-haiku-5-5", "anthropic/claude-sonnet-5"),
|
|
3334
|
+
);
|
|
3335
|
+
assert.equal(out.provider, "anthropic");
|
|
3336
|
+
assert.equal(out.modelId, "claude-haiku-5-5");
|
|
3337
|
+
});
|
|
3338
|
+
|
|
3339
|
+
it("skips Haiku 5.5 on pre-1.1.0 catalogs and still lands on Sonnet 5", () => {
|
|
3340
|
+
const out = applyDefaultModelFallback(
|
|
3341
|
+
builtin,
|
|
3342
|
+
registryWith(`${builtin.provider}/${builtin.modelId}`, "anthropic/claude-sonnet-5"),
|
|
3343
|
+
false,
|
|
3344
|
+
keyWith("anthropic/claude-haiku-5-5", "anthropic/claude-sonnet-5"),
|
|
3345
|
+
);
|
|
3346
|
+
assert.equal(out.provider, "anthropic");
|
|
3347
|
+
assert.equal(out.modelId, "claude-sonnet-5");
|
|
3348
|
+
});
|
|
3349
|
+
|
|
3315
3350
|
it("keeps the default when it is in the catalog and keyed", () => {
|
|
3316
3351
|
const out = applyDefaultModelFallback(
|
|
3317
3352
|
builtin,
|
|
@@ -196,6 +196,30 @@ describe("formatAnalyzeImageResult", () => {
|
|
|
196
196
|
assert.equal(segs[0]!.style, "success");
|
|
197
197
|
assert.equal(segs[1]!.style, "muted");
|
|
198
198
|
});
|
|
199
|
+
|
|
200
|
+
describe("durationMs fallback (pi ≥ 1.1.0 render context)", () => {
|
|
201
|
+
it("uses the host durationMs when details carry no latencyMs", () => {
|
|
202
|
+
assert.equal(
|
|
203
|
+
plain(formatAnalyzeImageResult({ ok: true, provider: "zai", model: "m" }, false, 2500)),
|
|
204
|
+
"✓ zai/m · 2.5s",
|
|
205
|
+
);
|
|
206
|
+
// rows with no details at all (replayed foreign sessions, HTML exports)
|
|
207
|
+
assert.equal(plain(formatAnalyzeImageResult(undefined, false, 987)), "✓ · 1.0s");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("prefers details.latencyMs over the host durationMs", () => {
|
|
211
|
+
const segs = formatAnalyzeImageResult(
|
|
212
|
+
{ ok: true, provider: "zai", model: "glm-5.3-flash", latencyMs: 1234 },
|
|
213
|
+
false,
|
|
214
|
+
99999,
|
|
215
|
+
);
|
|
216
|
+
assert.equal(plain(segs), "✓ zai/glm-5.3-flash · 1.2s");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("ignores non-finite durationMs", () => {
|
|
220
|
+
assert.equal(plain(formatAnalyzeImageResult({ ok: true }, false, Number.NaN)), "✓");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
199
223
|
});
|
|
200
224
|
|
|
201
225
|
describe("formatGenerateImageResult", () => {
|
package/extensions/internal.ts
CHANGED
|
@@ -1318,6 +1318,7 @@ export function resolveConfig(
|
|
|
1318
1318
|
*/
|
|
1319
1319
|
export const DEFAULT_MODEL_FALLBACKS: ReadonlyArray<{ provider: string; modelId: string }> = [
|
|
1320
1320
|
{ provider: "deepseek", modelId: "deepseek-v4-flash-vision-exp" }, // Pi ≥ 0.84.4, cheap, common key
|
|
1321
|
+
{ provider: "anthropic", modelId: "claude-haiku-5-5" }, // Pi ≥ 1.1.0, vision-capable, $0.1/$0.5 per M
|
|
1321
1322
|
{ provider: "anthropic", modelId: "claude-sonnet-5" },
|
|
1322
1323
|
{ provider: "anthropic", modelId: "claude-sonnet-4-5" },
|
|
1323
1324
|
];
|
|
@@ -3401,8 +3402,19 @@ function recordOf(details: unknown): Record<string, unknown> | undefined {
|
|
|
3401
3402
|
: undefined;
|
|
3402
3403
|
}
|
|
3403
3404
|
|
|
3404
|
-
/**
|
|
3405
|
-
|
|
3405
|
+
/**
|
|
3406
|
+
* One-line transcript summary of an analyze_image result from its `details`.
|
|
3407
|
+
* `durationMs` (pi ≥ 1.1.0 host render context) is a display fallback for rows
|
|
3408
|
+
* whose `details` carry no `latencyMs` — replayed sessions, foreign/HTML
|
|
3409
|
+
* exports, results recorded before `details` existed. Details always win:
|
|
3410
|
+
* `latencyMs` measures the vision call itself, while the host's `durationMs`
|
|
3411
|
+
* covers the whole `execute()` (consent checks, crop encoding included).
|
|
3412
|
+
*/
|
|
3413
|
+
export function formatAnalyzeImageResult(
|
|
3414
|
+
details: unknown,
|
|
3415
|
+
isError: boolean,
|
|
3416
|
+
durationMs?: number,
|
|
3417
|
+
): TranscriptSegment[] {
|
|
3406
3418
|
const d = recordOf(details);
|
|
3407
3419
|
if (isError || d?.ok === false) {
|
|
3408
3420
|
const msg = typeof d?.error === "string" && d.error ? d.error : "analysis failed";
|
|
@@ -3414,8 +3426,9 @@ export function formatAnalyzeImageResult(details: unknown, isError: boolean): Tr
|
|
|
3414
3426
|
if (provider && modelId) segments.push({ text: ` ${summarizeForTranscript(`${provider}/${modelId}`, 80)}`, style: "muted" });
|
|
3415
3427
|
else if (modelId) segments.push({ text: ` ${summarizeForTranscript(modelId, 80)}`, style: "muted" });
|
|
3416
3428
|
if (d?.cached === true) segments.push({ text: " · cached", style: "dim" });
|
|
3417
|
-
|
|
3418
|
-
|
|
3429
|
+
const latency = isFiniteNumber(d?.latencyMs) ? d.latencyMs : isFiniteNumber(durationMs) ? durationMs : undefined;
|
|
3430
|
+
if (latency !== undefined) {
|
|
3431
|
+
segments.push({ text: ` · ${(Math.max(0, latency) / 1000).toFixed(1)}s`, style: "dim" });
|
|
3419
3432
|
}
|
|
3420
3433
|
return segments;
|
|
3421
3434
|
}
|
|
@@ -2407,6 +2407,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
2407
2407
|
isPartial?: boolean;
|
|
2408
2408
|
expanded?: boolean;
|
|
2409
2409
|
showImages?: boolean;
|
|
2410
|
+
/** pi ≥ 1.1.0: recorded execute() duration from the final result. */
|
|
2411
|
+
durationMs?: number;
|
|
2410
2412
|
}
|
|
2411
2413
|
|
|
2412
2414
|
/** AgentToolResult subset the result renderer receives. */
|
|
@@ -2580,7 +2582,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2580
2582
|
renderResult: (result, options, theme, context) => {
|
|
2581
2583
|
if (options.isPartial) return textComponent(theme.fg("warning", "Analyzing…"));
|
|
2582
2584
|
return resultComponent(
|
|
2583
|
-
formatAnalyzeImageResult(result?.details, context?.isError === true),
|
|
2585
|
+
formatAnalyzeImageResult(result?.details, context?.isError === true, context?.durationMs),
|
|
2584
2586
|
result ?? {},
|
|
2585
2587
|
options,
|
|
2586
2588
|
theme,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-multimodal-proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "Automatic image, video and audio description for any model in Pi. Routes media to a multimodal model and injects descriptions into context.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|