Skip to content

Commit 651aabb

Browse files
committed
fix: parse models as object map and retry on empty list
1 parent 432c2a3 commit 651aabb

File tree

3 files changed

+64
-13
lines changed

3 files changed

+64
-13
lines changed

src-tauri/src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
22
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
33

4+
use std::thread;
5+
46
fn main() {
5-
if let Err(err) = fix_path_env::fix() {
6-
eprintln!("Failed to sync PATH from shell: {err}");
7-
}
7+
// Sync PATH from shell in background - don't block app launch
8+
thread::spawn(|| {
9+
if let Err(err) = fix_path_env::fix() {
10+
eprintln!("Failed to sync PATH from shell: {err}");
11+
}
12+
});
13+
814
opencode_monitor_lib::run()
915
}

src-tauri/src/shared/codex_core.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -831,12 +831,13 @@ pub(crate) async fn model_list_core(
831831
}
832832
};
833833

834-
// REST returns { providers: [{ id, models: [{ id, name }] }], default: { ... } }.
835-
let default_config = providers.get("default").cloned().unwrap_or(json!({}));
836-
let default_model = default_config
837-
.as_object()
838-
.and_then(|m| m.values().next())
839-
.and_then(|v| v.as_str())
834+
// REST returns:
835+
// providers: [{ id, models: { "model-id": { id, name, ... }, ... } }]
836+
// default: { "provider-id": "model-id", ... }
837+
let defaults = providers
838+
.get("default")
839+
.and_then(|v| v.as_object())
840+
.cloned()
840841
.unwrap_or_default();
841842

842843
let provider_list = providers
@@ -851,12 +852,17 @@ pub(crate) async fn model_list_core(
851852
.get("id")
852853
.and_then(|v| v.as_str())
853854
.unwrap_or_default();
854-
let models = provider
855+
let default_for_provider = defaults
856+
.get(provider_id)
857+
.and_then(|v| v.as_str())
858+
.unwrap_or_default();
859+
// `models` is a map { "model-id": { id, name, ... } }, not an array.
860+
let models_map = provider
855861
.get("models")
856-
.and_then(|v| v.as_array())
862+
.and_then(|v| v.as_object())
857863
.cloned()
858864
.unwrap_or_default();
859-
for model in models {
865+
for (_key, model) in &models_map {
860866
let model_id = model
861867
.get("id")
862868
.and_then(|v| v.as_str())
@@ -873,7 +879,7 @@ pub(crate) async fn model_list_core(
873879
.trim()
874880
.to_string();
875881
let qualified_id = format!("{provider_id}/{model_id}");
876-
let is_default = qualified_id == default_model || model_id == default_model;
882+
let is_default = model_id == default_for_provider;
877883
data.push(json!({
878884
"id": qualified_id,
879885
"model": model_id,

src/features/models/hooks/useModels.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,45 @@ export function useModels({
312312
return unsub;
313313
}, [isConnected, refreshModels, workspaceId]);
314314

315+
// Retry loading models when connected but the list is empty.
316+
// The initial fetch may return empty if the OpenCode server hasn't finished
317+
// starting, and the codex/modelsReady event may fire before the frontend
318+
// subscribes — leaving models stuck at "Loading models..." with no trigger
319+
// to retry.
320+
useEffect(() => {
321+
if (!workspaceId || !isConnected || models.length > 0) {
322+
return;
323+
}
324+
let cancelled = false;
325+
let attempts = 0;
326+
let timerId: ReturnType<typeof setTimeout>;
327+
const MAX_ATTEMPTS = 10;
328+
const BASE_DELAY = 1500;
329+
const MAX_DELAY = 15_000;
330+
331+
const scheduleRetry = () => {
332+
if (cancelled || attempts >= MAX_ATTEMPTS) {
333+
return;
334+
}
335+
const delay = Math.min(BASE_DELAY * Math.pow(2, attempts), MAX_DELAY);
336+
attempts += 1;
337+
timerId = setTimeout(() => {
338+
if (cancelled) {
339+
return;
340+
}
341+
refreshModels();
342+
scheduleRetry();
343+
}, delay);
344+
};
345+
346+
scheduleRetry();
347+
348+
return () => {
349+
cancelled = true;
350+
clearTimeout(timerId);
351+
};
352+
}, [workspaceId, isConnected, models.length, refreshModels]);
353+
315354
useEffect(() => {
316355
if (!selectedModel) {
317356
return;

0 commit comments

Comments
 (0)