@@ -1469,6 +1469,10 @@ function isAutoLoadableLocalRow(row: LocalModelInfo): boolean {
14691469 if ( ! AUTO_LOAD_LOCAL_SOURCES . has ( row . source ) ) return false ;
14701470 if ( row . capabilities ?. can_chat !== true ) return false ;
14711471 if ( row . partial ) return false ;
1472+ // Adapters are chat-capable but load by resolving their base model, which
1473+ // for a Hub-id base can trigger the implicit remote fetch a background
1474+ // auto-load must never start. Adapters stay interactive-only.
1475+ if ( row . model_format === "adapter" ) return false ;
14721476 if ( isHiddenModelId ( row . model_id , row . id , row . path ) ) return false ;
14731477 if (
14741478 row . model_format === "gguf" &&
@@ -1504,11 +1508,50 @@ function localRowToCandidate(
15041508 } ;
15051509}
15061510
1511+ /**
1512+ * Build a loadable candidate for a backend-indexed local row. Directory-based
1513+ * GGUFs (LM Studio, models dir, custom folders) are flagged requires_variant
1514+ * by the backend, so without a remembered quant the folder is scanned through
1515+ * the same variants API the picker card uses and the smallest complete,
1516+ * auto-loadable quant is chosen. Returns null when no quant can be resolved.
1517+ */
1518+ async function resolveLocalRowCandidate (
1519+ row : LocalModelInfo ,
1520+ rememberedVariant : string | null = null ,
1521+ ) : Promise < AutoLoadCandidate | null > {
1522+ const isGguf = row . model_format === "gguf" ;
1523+ if ( row . capabilities ?. requires_variant === true ) {
1524+ // Only GGUF folders have an automatic quant resolution path.
1525+ if ( ! isGguf ) return null ;
1526+ if ( ! rememberedVariant ) {
1527+ const variants = await listGgufVariants ( row . model_id || row . id , undefined , {
1528+ preferLocalCache : true ,
1529+ localPath : row . path ,
1530+ } ) ;
1531+ const downloaded = variants . variants
1532+ . filter (
1533+ ( entry ) =>
1534+ entry . downloaded && ! entry . partial && isAutoLoadableGgufVariant ( entry ) ,
1535+ )
1536+ . sort ( ( a , b ) => a . size_bytes - b . size_bytes ) ;
1537+ if ( downloaded . length === 0 ) return null ;
1538+ return localRowToCandidate ( row , downloaded [ 0 ] . quant ) ;
1539+ }
1540+ }
1541+ return localRowToCandidate ( row , isGguf ? rememberedVariant : null ) ;
1542+ }
1543+
15071544/** Resolve a remembered local model against current backend inventory. */
15081545function matchesRememberedLocalRow (
15091546 row : LocalModelInfo ,
15101547 remembered : LastLocalModelLoad ,
15111548) : boolean {
1549+ // A folder holding both GGUF and safetensors weights yields two rows with
1550+ // the same path/load target, so the format must agree with the remembered
1551+ // kind before identifier matching can accept the row.
1552+ if ( ( row . model_format === "gguf" ) !== ( remembered . kind === "gguf" ) ) {
1553+ return false ;
1554+ }
15121555 if (
15131556 remembered . inventoryId &&
15141557 row . inventory_id &&
@@ -1911,17 +1954,19 @@ export async function autoLoadOnDeviceModel(): Promise<{
19111954 if ( row ) {
19121955 markSeen ( row . load_id , row . id , row . path , row . model_id ) ;
19131956 try {
1914- toast ( "Loading last used model…" , {
1915- id : toastId ,
1916- description : row . display_name || row . id ,
1917- duration : 5000 ,
1918- } ) ;
1919- if (
1920- await loadAutoLoadCandidate (
1921- localRowToCandidate ( row , lastLoaded . ggufVariant ) ,
1922- )
1923- ) {
1924- return { loaded : true , blockedByTrustRemoteCode : false } ;
1957+ const rememberedCandidate = await resolveLocalRowCandidate (
1958+ row ,
1959+ lastLoaded . ggufVariant ,
1960+ ) ;
1961+ if ( rememberedCandidate ) {
1962+ toast ( "Loading last used model…" , {
1963+ id : toastId ,
1964+ description : row . display_name || row . id ,
1965+ duration : 5000 ,
1966+ } ) ;
1967+ if ( await loadAutoLoadCandidate ( rememberedCandidate ) ) {
1968+ return { loaded : true , blockedByTrustRemoteCode : false } ;
1969+ }
19251970 }
19261971 } catch {
19271972 hadNonTrustFailure = true ;
@@ -2030,9 +2075,12 @@ export async function autoLoadOnDeviceModel(): Promise<{
20302075 bytes && bytes > 0 ? bytes : Number . MAX_SAFE_INTEGER ;
20312076 const bySizeAsc = ( a : FallbackCandidate , b : FallbackCandidate ) : number =>
20322077 a . sizeBytes - b . sizeBytes ;
2033- // Background loads cannot ask which quant/variant to use.
2078+ // Directory-based GGUF rows resolve a quant automatically below; only
2079+ // non-GGUF variant-requiring rows have no background resolution path.
20342080 const cascadeLocalRows = localRows . filter (
2035- ( row ) => row . capabilities ?. requires_variant !== true ,
2081+ ( row ) =>
2082+ row . model_format === "gguf" ||
2083+ row . capabilities ?. requires_variant !== true ,
20362084 ) ;
20372085 const ggufGroup : FallbackCandidate [ ] = [
20382086 ...ggufRepos . map ( ( repo ) => ( {
@@ -2141,20 +2189,23 @@ export async function autoLoadOnDeviceModel(): Promise<{
21412189 if ( isSeen ( row . load_id , row . id , row . path , row . model_id ) ) {
21422190 continue ;
21432191 }
2144- const localCandidate = localRowToCandidate ( row ) ;
2145- if (
2146- skippedAutoLoadCandidates . has (
2147- autoLoadCandidateKey (
2148- localCandidate . kind ,
2149- localCandidate . id ,
2150- localCandidate . ggufVariant ,
2151- ) ,
2152- )
2153- ) {
2154- continue ;
2155- }
21562192 markSeen ( row . load_id , row . id , row . path , row . model_id ) ;
21572193 try {
2194+ const localCandidate = await resolveLocalRowCandidate ( row ) ;
2195+ if ( ! localCandidate ) {
2196+ continue ;
2197+ }
2198+ if (
2199+ skippedAutoLoadCandidates . has (
2200+ autoLoadCandidateKey (
2201+ localCandidate . kind ,
2202+ localCandidate . id ,
2203+ localCandidate . ggufVariant ,
2204+ ) ,
2205+ )
2206+ ) {
2207+ continue ;
2208+ }
21582209 if ( await loadAutoLoadCandidate ( localCandidate ) ) {
21592210 return { loaded : true , blockedByTrustRemoteCode : false } ;
21602211 }
0 commit comments