pi-vision-guard 0.1.0 → 0.2.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 +8 -0
- package/README.md +1 -1
- package/extensions/vision-context-guard.ts +46 -39
- package/package.json +18 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- Ranking tries each registered classifier in order until one answers: a keyless or failing first pick (e.g. a built-in cloud entry with no API key) no longer blocks a working backend later in the list. Recency fallback now only fires when no classifier answers at all.
|
|
6
|
+
|
|
7
|
+
## 0.2.0
|
|
8
|
+
|
|
9
|
+
- Ranking uses the first available registered classifier with no built-in model preference (previously preferred a local clef provider); fully backend-agnostic, pairs with `pi-local-classifier` for offline use.
|
|
10
|
+
|
|
3
11
|
## 0.1.0 — Initial release
|
|
4
12
|
|
|
5
13
|
- Cap image payloads per request (default 5, `/vision-guard 1..6`), dropping to 1 past 35% context usage.
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Three layers, all automatic:
|
|
|
32
32
|
|
|
33
33
|
## Classifier ranking
|
|
34
34
|
|
|
35
|
-
Ranking lights up automatically with
|
|
35
|
+
Ranking lights up automatically with your registered Pi classifiers — no settings. Each is tried in order until one answers, so a keyless cloud entry never blocks a working local backend. The questions are plain keep/drop judgments, so any Jev-family decision model works. With no classifier present, the guard silently uses newest-first order. Pairs with `pi-local-classifier` for fully offline ranking with no API key.
|
|
36
36
|
|
|
37
37
|
## ⚠️ Cache-churn caveat
|
|
38
38
|
|
|
@@ -8,11 +8,8 @@ const MAX_EDGE = 1600;
|
|
|
8
8
|
const DEFAULT_RETAINED_IMAGES = 5;
|
|
9
9
|
const LONG_CONTEXT_FRACTION = 0.35;
|
|
10
10
|
const MAX_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// available one. Recency fallback covers no-classifier setups.
|
|
14
|
-
const CLEF_PROVIDER = "clef-local";
|
|
15
|
-
const CLEF_MODEL = "clef-flash";
|
|
11
|
+
// Relevance ranking uses the first available registry classifier, if any.
|
|
12
|
+
// Recency fallback covers no-classifier setups.
|
|
16
13
|
const OMITTED_TEXT =
|
|
17
14
|
"[Older image omitted from this request by the local vision context guard. Use existing textual observations or reread one specific image if it is essential.]";
|
|
18
15
|
|
|
@@ -158,16 +155,19 @@ function lastUserText(messages: any[]): string {
|
|
|
158
155
|
return "(unknown)";
|
|
159
156
|
}
|
|
160
157
|
|
|
161
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* One batched classifier call scoring every candidate. Tries each registered
|
|
160
|
+
* classifier in order until one returns usable scores — a keyless or failing
|
|
161
|
+
* first pick must not block a working backend later in the list. Returns
|
|
162
|
+
* scores aligned with slots, or undefined when none answer. Abort still throws.
|
|
163
|
+
*/
|
|
162
164
|
async function scoreKeepers(
|
|
163
165
|
slots: ImageSlot[],
|
|
164
166
|
taskHint: string,
|
|
165
167
|
ctx: ExtensionContext,
|
|
166
168
|
): Promise<number[] | undefined> {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
ctx.modelRegistry.getModelsOfType("classifier")[0];
|
|
170
|
-
if (!clf) return undefined;
|
|
169
|
+
const models = ctx.modelRegistry.getModelsOfType("classifier");
|
|
170
|
+
if (models.length === 0) return undefined;
|
|
171
171
|
const questions: Record<string, ClassifierQuestion> = {};
|
|
172
172
|
for (let i = 0; i < slots.length; i++) {
|
|
173
173
|
questions[`keep_${i}`] = {
|
|
@@ -179,44 +179,51 @@ async function scoreKeepers(
|
|
|
179
179
|
},
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
182
|
+
for (const clf of models) {
|
|
183
|
+
if (ctx.signal?.aborted) throw new Error("aborted");
|
|
184
|
+
try {
|
|
185
|
+
const result = await ctx.modelRegistry.classify(
|
|
186
|
+
clf,
|
|
187
|
+
{
|
|
188
|
+
state: {
|
|
189
|
+
task: taskHint,
|
|
190
|
+
images: slots.map((s, i) => ({
|
|
191
|
+
index: i,
|
|
192
|
+
role: s.role,
|
|
193
|
+
context: s.text.slice(0, 300) || "(no surrounding text)",
|
|
194
|
+
})),
|
|
195
|
+
},
|
|
196
|
+
questions,
|
|
193
197
|
},
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
{ signal: ctx.signal },
|
|
197
|
-
);
|
|
198
|
-
if (result.stopReason !== "stop") {
|
|
198
|
+
{ signal: ctx.signal },
|
|
199
|
+
);
|
|
199
200
|
if (result.stopReason === "aborted" || ctx.signal?.aborted) throw new Error("aborted");
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
201
|
+
if (result.stopReason !== "stop") continue;
|
|
202
|
+
const scores: number[] = [];
|
|
203
|
+
let usable = true;
|
|
204
|
+
for (let i = 0; i < slots.length; i++) {
|
|
205
|
+
const a = result.answers[`keep_${i}`];
|
|
206
|
+
if (a?.type !== "bool") {
|
|
207
|
+
usable = false;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
scores.push(a.probability);
|
|
211
|
+
}
|
|
212
|
+
if (!usable) continue;
|
|
213
|
+
return scores;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
if (ctx.signal?.aborted) throw e;
|
|
216
|
+
continue;
|
|
207
217
|
}
|
|
208
|
-
return scores;
|
|
209
|
-
} catch (e) {
|
|
210
|
-
if (ctx.signal?.aborted) throw e;
|
|
211
|
-
return undefined;
|
|
212
218
|
}
|
|
219
|
+
return undefined;
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
/**
|
|
216
223
|
* Relevance-ranked prune within the same budget the recency guard used.
|
|
217
|
-
* Newest image is always kept; remaining slots go to the highest
|
|
224
|
+
* Newest image is always kept; remaining slots go to the highest classifier
|
|
218
225
|
* keep-scores, falling back to recency when there is no ranking signal
|
|
219
|
-
* (bare images) or
|
|
226
|
+
* (bare images) or no classifier answers.
|
|
220
227
|
*/
|
|
221
228
|
async function pruneRanked(
|
|
222
229
|
messages: any[],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-vision-guard",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Pi extension that caps, shrinks, and relevance-ranks image payloads so long vision sessions stop 413ing",
|
|
5
5
|
"author": "Jesse Wise",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,14 +12,28 @@
|
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/jwise7/pi-vision-guard/issues"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"pi",
|
|
18
|
+
"vision",
|
|
19
|
+
"images",
|
|
20
|
+
"context",
|
|
21
|
+
"coding-agent"
|
|
22
|
+
],
|
|
16
23
|
"publishConfig": {
|
|
17
24
|
"access": "public"
|
|
18
25
|
},
|
|
19
26
|
"pi": {
|
|
20
|
-
"extensions": [
|
|
27
|
+
"extensions": [
|
|
28
|
+
"./extensions"
|
|
29
|
+
]
|
|
21
30
|
},
|
|
22
|
-
"files": [
|
|
31
|
+
"files": [
|
|
32
|
+
"extensions/",
|
|
33
|
+
"README.md",
|
|
34
|
+
"CHANGELOG.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
23
37
|
"scripts": {
|
|
24
38
|
"test": "node --test tests/vision-guard/scoping.test.mjs tests/vision-guard/ranking.test.mjs"
|
|
25
39
|
},
|