@@ -282,8 +282,9 @@ pub fn main() !void {
282282 if (detected ) | d | {
283283 if (d .model_available ) {
284284 // Server found with model — write config and proceed with embeddings
285+ const model_name = d .default_model orelse settings .embedding_model ;
285286 _ = stderr .print (" Detected {s} on {s} with model '{s}'. Saved to .codescan/config.ini.\n " , .{
286- if (d .dialect == .ollama ) "Ollama" else "oMLX" , d .url , settings . embedding_model ,
287+ if (d .dialect == .ollama ) "Ollama" else "oMLX" , d .url , model_name ,
287288 }) catch {};
288289 _ = stderr .flush () catch {};
289290 use_embeddings = true ;
@@ -295,8 +296,13 @@ pub fn main() !void {
295296 d .url , settings .embedding_model , settings .embedding_model ,
296297 }) catch {};
297298 } else {
298- _ = stderr .print (" Found oMLX on {s}. Configure embedding_api_key in .codescan/config.ini\n " ++
299- " then run 'codescan index' for semantic search.\n " , .{d .url }) catch {};
299+ if (d .default_model ) | dm | {
300+ _ = stderr .print (" Found oMLX on {s} with model '{s}'.\n " ++
301+ " Set embedding_api_key in .codescan/config.ini then run 'codescan index'.\n " , .{ d .url , dm }) catch {};
302+ } else {
303+ _ = stderr .print (" Found oMLX on {s}. Configure embedding_api_key in .codescan/config.ini\n " ++
304+ " then run 'codescan index' for semantic search.\n " , .{d .url }) catch {};
305+ }
300306 }
301307 _ = stderr .flush () catch {};
302308 _ = stderr .print (" Index in lexical-only mode? [Y/n] " , .{}) catch {};
@@ -306,8 +312,8 @@ pub fn main() !void {
306312 return ;
307313 }
308314 }
309- // Write detected server config
310- writeDetectedConfig (allocator , config_root , d .url , d .dialect ) catch | err | {
315+ // Write detected server config (including model name if discovered)
316+ writeDetectedConfig (allocator , config_root , d .url , d .dialect , d . default_model ) catch | err | {
311317 _ = stderr .print (" warning: could not update config: {s}\n " , .{@errorName (err )}) catch {};
312318 _ = stderr .flush () catch {};
313319 };
@@ -332,10 +338,11 @@ pub fn main() !void {
332338
333339 const emb_url = if (detected ) | d | d .url else settings .embedding_url ;
334340 const emb_dialect = if (detected ) | d | d .dialect else settings .embedding_dialect ;
341+ const emb_model = if (detected ) | d | (d .default_model orelse settings .embedding_model ) else settings .embedding_model ;
335342 var embedder_adapter = embedding.HttpEmbedder {
336343 .transport = http_client .transport (),
337344 .base_url = emb_url ,
338- .model = settings . embedding_model ,
345+ .model = emb_model ,
339346 .dialect = emb_dialect ,
340347 .auth_header = settings .embedding_auth_header ,
341348 };
@@ -659,10 +666,11 @@ pub fn main() !void {
659666
660667 const emb_url = if (detected ) | d | d .url else settings .embedding_url ;
661668 const emb_dialect = if (detected ) | d | d .dialect else settings .embedding_dialect ;
669+ const emb_model = if (detected ) | d | (d .default_model orelse settings .embedding_model ) else settings .embedding_model ;
662670 var embedder_adapter = embedding.HttpEmbedder {
663671 .transport = http_client .transport (),
664672 .base_url = emb_url ,
665- .model = settings . embedding_model ,
673+ .model = emb_model ,
666674 .dialect = emb_dialect ,
667675 .auth_header = settings .embedding_auth_header ,
668676 };
@@ -1713,6 +1721,7 @@ const DetectedServer = struct {
17131721 url : []const u8 ,
17141722 dialect : embedding_http.ApiDialect ,
17151723 model_available : bool ,
1724+ default_model : ? []const u8 = null , // from oMLX /health response
17161725};
17171726
17181727/// Probes well-known embedding server ports and returns the first that responds.
@@ -1786,10 +1795,13 @@ fn probeOpenAI(
17861795 })) | response | {
17871796 defer allocator .free (response .body );
17881797 if (response .status == 200 ) {
1798+ // Try to parse default_model from health response
1799+ const model_name = parseHealthDefaultModel (allocator , response .body );
17891800 return .{
17901801 .url = base_url ,
17911802 .dialect = .openai ,
1792- .model_available = false , // /health confirms server exists but not auth/model
1803+ .model_available = false , // can't verify auth via /health alone
1804+ .default_model = model_name ,
17931805 };
17941806 }
17951807 } else | _ | {}
@@ -1822,6 +1834,18 @@ fn buildUrl(allocator: std.mem.Allocator, base_url: []const u8, path: []const u8
18221834
18231835/// Prompts the user with a yes/no question. Returns true for yes.
18241836/// In non-TTY mode, returns `non_tty_default`.
1837+ /// Parse "default_model" from an oMLX /health JSON response.
1838+ /// Returns an allocator-owned string, or null if not found.
1839+ fn parseHealthDefaultModel (allocator : std.mem.Allocator , body : []const u8 ) ? []const u8 {
1840+ var parsed = std .json .parseFromSlice (std .json .Value , allocator , body , .{}) catch return null ;
1841+ defer parsed .deinit ();
1842+
1843+ if (parsed .value != .object ) return null ;
1844+ const model_value = parsed .value .object .get ("default_model" ) orelse return null ;
1845+ if (model_value != .string ) return null ;
1846+ return allocator .dupe (u8 , model_value .string ) catch null ;
1847+ }
1848+
18251849fn promptYesNo (stderr : * std.Io.Writer , non_tty_default : bool ) bool {
18261850 _ = stderr .flush () catch {};
18271851 if (! std .fs .File .stdin ().isTty ()) return non_tty_default ;
@@ -1832,17 +1856,21 @@ fn promptYesNo(stderr: *std.Io.Writer, non_tty_default: bool) bool {
18321856 return input_buf [0 ] == 'y' or input_buf [0 ] == 'Y' ;
18331857}
18341858
1835- fn writeDetectedConfig (allocator : std.mem.Allocator , config_root : []const u8 , url : []const u8 , dialect : embedding_http.ApiDialect ) ! void {
1859+ fn writeDetectedConfig (allocator : std.mem.Allocator , config_root : []const u8 , url : []const u8 , dialect : embedding_http.ApiDialect , detected_model : ? [] const u8 ) ! void {
18361860 const cfg_path = try configPath (allocator , config_root );
18371861 defer allocator .free (cfg_path );
18381862 const content = try std .fs .cwd ().readFileAlloc (allocator , cfg_path , 64 * 1024 );
18391863 defer allocator .free (content );
18401864 const dialect_str = if (dialect == .ollama ) "ollama" else "openai" ;
1841- const kvs = [_ ]config.KV {
1842- .{ .key = "embedding_url" , .value = url },
1843- .{ .key = "embedding_api" , .value = dialect_str },
1844- };
1845- const updated = try config .writeConfigValues (allocator , content , & kvs );
1865+ var kvs_buf : [3 ]config.KV = undefined ;
1866+ kvs_buf [0 ] = .{ .key = "embedding_url" , .value = url };
1867+ kvs_buf [1 ] = .{ .key = "embedding_api" , .value = dialect_str };
1868+ var kv_count : usize = 2 ;
1869+ if (detected_model ) | m | {
1870+ kvs_buf [2 ] = .{ .key = "embedding_model" , .value = m };
1871+ kv_count = 3 ;
1872+ }
1873+ const updated = try config .writeConfigValues (allocator , content , kvs_buf [0.. kv_count ]);
18461874 defer allocator .free (updated );
18471875 const file = try std .fs .cwd ().createFile (cfg_path , .{ .truncate = true });
18481876 defer file .close ();
@@ -5759,7 +5787,7 @@ pub fn runRegexSearch(
57595787const OpenAIFallbackMock = struct {
57605788 fn send (_ : * anyopaque , allocator : std.mem.Allocator , req : embedding_http.HttpRequest ) ! embedding_http.HttpResponse {
57615789 if (std .mem .endsWith (u8 , req .url , "/health" )) {
5762- return .{ .status = 200 , .body = try allocator .dupe (u8 , "{\" status\" :\" healthy\" }" ) };
5790+ return .{ .status = 200 , .body = try allocator .dupe (u8 , "{\" status\" :\" healthy\" , \" default_model \" : \" test-model-mlx \" }" ) };
57635791 }
57645792 if (std .mem .endsWith (u8 , req .url , "/v1/models" )) {
57655793 return .{ .status = 200 , .body = try allocator .dupe (u8 , "{\" data\" :[]}" ) };
@@ -5814,6 +5842,10 @@ test "detectEmbeddingServer finds oMLX when Ollama unavailable" {
58145842 try std .testing .expect (result != null );
58155843 try std .testing .expectEqualStrings ("http://localhost:8000" , result .? .url );
58165844 try std .testing .expectEqual (embedding_http .ApiDialect .openai , result .? .dialect );
5845+ try std .testing .expect (! result .? .model_available ); // can't verify auth via /health
5846+ try std .testing .expect (result .? .default_model != null );
5847+ try std .testing .expectEqualStrings ("test-model-mlx" , result .? .default_model .? );
5848+ allocator .free (result .? .default_model .? );
58175849}
58185850
58195851test "detectEmbeddingServer returns null when nothing available" {
0 commit comments