2026-06-15 12:08:05 +07:00
// REAL survey: send an image to EVERY chat LLM (all providers with active creds) and
// record how each model behaves vs its declared vision capability.
// Purpose: discover where non-vision models 400 on image input (need auto-strip) and
// where capability data is wrong. This is a SURVEY: it logs a table, it does not fail
// on capability mismatches (only real harness errors throw).
//
// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/vision-capability-survey.real.test.js
// RUN_REAL=1 REAL_PROVIDERS=mistral,nvidia npx vitest run ... (optional filter)
import { describe , it , expect , afterAll } from "vitest" ;
import { getProviderCredentials } from "../../../src/sse/services/auth.js" ;
import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js" ;
import { handleChatCore } from "../../../open-sse/handlers/chatCore.js" ;
import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js" ;
import { getCapabilitiesForModel } from "../../../open-sse/providers/capabilities.js" ;
const RUN_REAL = process . env . RUN_REAL === "1" ;
const TIMEOUT_MS = 90000 ;
const CRED_ISSUE = [ 401 , 402 , 403 , 429 ];
const NON_CHAT_KINDS = new Set ([ "embedding" , "image" , "imageToText" , "tts" , "stt" , "video" , "music" , "webSearch" ]);
const PROVIDER_FILTER = ( process . env . REAL_PROVIDERS || "" )
. split ( "," ). map (( s ) => s . trim ()). filter ( Boolean );
// Valid 16x16 red PNG (1x1 is rejected as malformed by some strict providers).
const PNG_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAFklEQVR4nGO4I2JDEmIY1TCqYfhqAAAeBCwQ8YdREQAAAABJRU5ErkJggg==" ;
// Tiny silent WAV (44-byte header, no samples) — enough to probe audioInput acceptance.
const WAV_B64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=" ;
2026-06-15 18:18:04 +07:00
// Stable public image URL (probe remote-URL handling vs base64).
const IMAGE_REMOTE_URL = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" ;
// Tiny valid PDF (probe file/document handling).
const PDF_B64 = "JVBERi0xLjEKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjAwIDIwMF0+PmVuZG9iagp4cmVmCjAgNAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA1MiAwMDAwMCBuIAowMDAwMDAwMTAxIDAwMDAwIG4gCnRyYWlsZXI8PC9TaXplIDQvUm9vdCAxIDAgUj4+CnN0YXJ0eHJlZgoxNjYKJSVFT0Y=" ;
const PDF_DATA_URI = `data:application/pdf;base64, ${ PDF_B64 } ` ;
2026-06-15 12:08:05 +07:00
// Capability probes: each sends one modality-specific content block. cap = capability flag tested.
const CAPABILITY_PROBES = {
vision : {
cap : "vision" ,
content : [
{ type : "text" , text : "What is in this image? One word." },
{ type : "image_url" , image_url : { url : PNG_DATA_URI } },
],
},
audio : {
cap : "audioInput" ,
content : [
{ type : "text" , text : "Transcribe this audio. One word." },
{ type : "input_audio" , input_audio : { data : WAV_B64 , format : "wav" } },
],
},
2026-06-15 18:18:04 +07:00
imageUrl : {
cap : "vision" ,
content : [
{ type : "text" , text : "What is in this image? One word." },
{ type : "image_url" , image_url : { url : IMAGE_REMOTE_URL } },
],
},
file : {
cap : "pdf" ,
content : [
{ type : "text" , text : "Summarize this document. One word." },
{ type : "file" , file : { filename : "doc.pdf" , file_data : PDF_DATA_URI } },
],
},
2026-06-15 12:08:05 +07:00
};
// Set PROBES=vision,audio (default vision only to limit live quota).
const ACTIVE_PROBES = ( process . env . PROBES || "vision" ). split ( "," ). map (( s ) => s . trim ()). filter ( Boolean );
// Status/message patterns that indicate account/credential issues (not a vision-capability signal).
const CRED_MSG_RE = /subscription|unauthorized|invalid api key|invalid access token|insufficient|credits|payment|spending|organization policy|disallowed|quota|exhausted|not supported when using|not available for integrator|requires a subscription|model.*not found|does not exist|not yet known|requires a role/i ;
// Collected rows for the end-of-run summary table.
const results = [];
async function drainSSE ( response ) {
if ( ! response ? . body ) return "" ;
const reader = response . body . getReader ();
const decoder = new TextDecoder ();
let out = "" ;
while ( true ) {
const { done , value } = await reader . read ();
if ( done ) break ;
out += decoder . decode ( value , { stream : true });
}
return out ;
}
function chatModels ( providerId ) {
return getModelsByProviderId ( providerId ). filter (( m ) => ! NON_CHAT_KINDS . has ( m . kind || m . type || "llm" ));
}
function targetProviders () {
try {
const Database = require ( "better-sqlite3" );
const os = require ( "os" );
const path = require ( "path" );
const dbPath = process . env . DATA_DIR
? path . join ( process . env . DATA_DIR , "db" , "data.sqlite" )
: path . join ( os . homedir (), ".9router" , "db" , "data.sqlite" );
const db = new Database ( dbPath , { readonly : true });
const rows = db . prepare ( "SELECT DISTINCT provider FROM providerConnections WHERE isActive = 1" ). all ();
db . close ();
let list = rows . map (( r ) => r . provider ). sort ();
if ( PROVIDER_FILTER . length ) list = list . filter (( p ) => PROVIDER_FILTER . includes ( p ));
return list ;
} catch {
return [];
}
}
describe . skipIf ( ! RUN_REAL )( "REAL vision capability survey" , () => {
const providers = RUN_REAL ? targetProviders () : [];
it ( "has active providers in DB" , () => {
expect ( providers . length ). toBeGreaterThan ( 0 );
});
for ( const providerId of providers ) {
for ( const m of ( RUN_REAL ? chatModels ( providerId ) : [])) {
const model = m . id ;
for ( const probeName of ACTIVE_PROBES ) {
const probe = CAPABILITY_PROBES [ probeName ];
it . concurrent ( ` ${ probeName } | ${ providerId } / ${ model } ` , async () => {
const caps = getCapabilitiesForModel ( providerId , model );
const capOn = !! caps [ probe . cap ];
const credentials = await getProviderCredentials ( providerId , new Set (), model );
if ( ! credentials || credentials . allRateLimited ) {
results . push ({ probe : probeName , providerId , model , cap : capOn , status : "no-cred" , verdict : "skip" });
return expect ( true ). toBe ( true );
}
const refreshed = await checkAndRefreshToken ( providerId , credentials );
const result = await handleChatCore ({
body : {
model : ` ${ providerId } / ${ model } ` ,
stream : true ,
max_tokens : 64 ,
messages : [{ role : "user" , content : probe . content }],
},
modelInfo : { provider : providerId , model },
credentials : refreshed ,
connectionId : credentials . connectionId ,
});
const status = Number ( result . status ) || ( result . success ? 200 : 0 );
const errMsg = result . success ? "" : String ( result . error || "" );
const credIssue = CRED_ISSUE . includes ( status ) || CRED_MSG_RE . test ( errMsg );
const ok = result . success ;
// Classify outcome vs declared capability (cred/account noise filtered out).
let verdict ;
if ( credIssue ) verdict = "skip-cred" ;
else if ( ok && capOn ) verdict = `ok- ${ probeName } ` ;
else if ( ok && ! capOn ) verdict = `ok-no ${ probeName } -tolerated` ; // provider silently ignored modality
else if ( ! ok && ! capOn ) verdict = `FAIL-no ${ probeName } -needs-strip` ; // target problem
else verdict = `FAIL- ${ probeName } -capdata-wrong` ; // declared cap but rejected
if ( ok ) await drainSSE ( result . response ). catch (() => {});
results . push ({ probe : probeName , providerId , model , cap : capOn , status , verdict , error : ok ? "" : String ( result . error || "" ). slice ( 0 , 80 ) });
// Survey never fails on capability outcome.
return expect ( true ). toBe ( true );
}, TIMEOUT_MS );
}
}
}
afterAll (() => {
if ( ! results . length ) return ;
// FAIL groups first (most actionable), then ok/skip; alphabetical within rank.
const rank = ( v ) => ( v . startsWith ( "FAIL" ) ? 0 : v . startsWith ( "ok" ) ? 1 : 2 );
const groups = [... new Set ( results . map (( r ) => r . verdict ))]
. sort (( a , b ) => rank ( a ) - rank ( b ) || a . localeCompare ( b ));
console . log ( "\n================ CAPABILITY SURVEY ================" );
for ( const g of groups ) {
const rows = results . filter (( r ) => r . verdict === g );
if ( ! rows . length ) continue ;
console . log ( `\n### ${ g } ( ${ rows . length } )` );
for ( const r of rows ) {
console . log ( ` [ ${ r . status } ] ${ r . probe } ${ r . providerId } / ${ r . model } cap= ${ r . cap }${ r . error ? ` :: ${ r . error } ` : "" } ` );
}
}
console . log ( "\n================ END SURVEY ================\n" );
});
});