mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host. Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean. Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
116 lines
4.3 KiB
TypeScript
116 lines
4.3 KiB
TypeScript
/**
|
|
* Task-aware vision description prompt (codex-vision-proxy pattern) —
|
|
* Modality Bridge PR-1. The describe path appends the user's last question as
|
|
* a focus hint so the vision model describes what is relevant to answering it
|
|
* instead of producing a generic caption. Default ON; disabled via
|
|
* `modalityBridgeVisionTaskAware: false`.
|
|
*
|
|
* Guardrail-level cases use `model: "auto/..."` + `mode: "describe"` so the
|
|
* whole flow is DB-free (the auto prefix skips the capability/combo lookups
|
|
* that open SQLite, and the forced describe mode skips the reroute block).
|
|
*/
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { composeVisionPrompt } from "../../src/lib/guardrails/visionBridgeHelpers.ts";
|
|
import { VisionBridgeGuardrail } from "../../src/lib/guardrails/visionBridge.ts";
|
|
import type { VisionModelConfig } from "../../src/lib/guardrails/visionBridgeHelpers.ts";
|
|
|
|
// ── composeVisionPrompt (pure) ──────────────────────────────────────────────
|
|
|
|
test("appends user focus hint when taskAware", () => {
|
|
const p = composeVisionPrompt("Describe the image.", "qual o erro no screenshot?", true);
|
|
assert.ok(p.startsWith("Describe the image."));
|
|
assert.ok(p.includes("qual o erro no screenshot?"));
|
|
});
|
|
|
|
test("no hint when disabled or no user text", () => {
|
|
assert.equal(composeVisionPrompt("Base.", "pergunta", false), "Base.");
|
|
assert.equal(composeVisionPrompt("Base.", undefined, true), "Base.");
|
|
assert.equal(composeVisionPrompt("Base.", " ", true), "Base.");
|
|
});
|
|
|
|
test("hint truncated to 500 chars", () => {
|
|
const p = composeVisionPrompt("Base.", "x".repeat(2000), true);
|
|
assert.ok(p.length < 700, `expected truncated prompt, got length ${p.length}`);
|
|
assert.ok(p.includes("x".repeat(500)));
|
|
assert.ok(!p.includes("x".repeat(501)));
|
|
});
|
|
|
|
// ── Guardrail describe path wiring ──────────────────────────────────────────
|
|
|
|
function describeGuardrail(
|
|
settings: Record<string, unknown>,
|
|
capturedPrompts: string[]
|
|
): InstanceType<typeof VisionBridgeGuardrail> {
|
|
return new VisionBridgeGuardrail({
|
|
deps: {
|
|
getSettings: async () => ({ modalityBridgeVisionMode: "describe", ...settings }),
|
|
callVisionModel: async (_imageDataUri: string, config: VisionModelConfig) => {
|
|
capturedPrompts.push(config.prompt);
|
|
return "descrição";
|
|
},
|
|
hasUsableCredentials: async () => null,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Unique per-test payload: the describe path caches by image+prompt+model
|
|
* (Task 8), so reusing the same data URI across tests would make a later
|
|
* describe a cache hit and hide the upstream call whose prompt is asserted.
|
|
*/
|
|
function autoImageBody(uniqueRef: string, userText: string): Record<string, unknown> {
|
|
return {
|
|
model: "auto/task-aware",
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [
|
|
{ type: "text", text: userText },
|
|
{
|
|
type: "image_url",
|
|
image_url: {
|
|
url: `data:image/png;base64,${Buffer.from(uniqueRef).toString("base64")}`,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
test("describe call prompt contains the last user question (taskAware default on)", async () => {
|
|
const prompts: string[] = [];
|
|
const guardrail = describeGuardrail({}, prompts);
|
|
|
|
const result = await guardrail.preCall(
|
|
autoImageBody("task-aware-default-on-test", "qual o erro no screenshot?"),
|
|
{ model: "auto/task-aware", log: console }
|
|
);
|
|
|
|
assert.equal((result.meta ?? {}).imagesProcessed, 1);
|
|
assert.equal(prompts.length, 1);
|
|
assert.ok(
|
|
prompts[0].includes("qual o erro no screenshot?"),
|
|
`prompt should carry the user question, got: ${prompts[0]}`
|
|
);
|
|
});
|
|
|
|
test("modalityBridgeVisionTaskAware=false keeps the base prompt untouched", async () => {
|
|
const prompts: string[] = [];
|
|
const guardrail = describeGuardrail(
|
|
{ modalityBridgeVisionTaskAware: false, modalityBridgeVisionPrompt: "Base prompt." },
|
|
prompts
|
|
);
|
|
|
|
const result = await guardrail.preCall(
|
|
autoImageBody("task-aware-disabled-test", "pergunta que não deve vazar"),
|
|
{ model: "auto/task-aware", log: console }
|
|
);
|
|
|
|
assert.equal((result.meta ?? {}).imagesProcessed, 1);
|
|
assert.equal(prompts.length, 1);
|
|
assert.equal(prompts[0], "Base prompt.");
|
|
});
|