Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions interface/src/routes/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const PROVIDERS = [
description: "Fast inference for popular OSS models",
placeholder: "...",
envVar: "FIREWORKS_API_KEY",
defaultModel: "fireworks/accounts/fireworks/models/llama-v3p3-70b-instruct",
defaultModel: "fireworks/accounts/fireworks/models/minimax-m2p5",
},
{
id: "deepseek",
Expand Down Expand Up @@ -262,6 +262,38 @@ export function Settings() {
enabled: activeSection === "providers",
});

// Fetch agents list and default agent config so we can pre-populate the
// model field with the currently active routing model when editing an
// already-configured provider (instead of always showing the hardcoded
// defaultModel).
const { data: agentsData } = useQuery({
queryKey: ["agents"],
queryFn: api.agents,
staleTime: 10_000,
enabled: activeSection === "providers",
});
const defaultAgentId =
agentsData?.agents?.find((agent) => agent.id === "main")?.id ?? agentsData?.agents?.[0]?.id;
const { data: defaultAgentConfig } = useQuery({
queryKey: ["agent-config", defaultAgentId],
queryFn: () => api.agentConfig(defaultAgentId!),
staleTime: 10_000,
enabled: activeSection === "providers" && !!defaultAgentId,
});
Comment on lines +277 to +282
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

["agent-config"] is never invalidated after provider mutations — pre-populated model will be stale.

After updateMutation succeeds (or the ChatGPT OAuth flow completes), the backend updates the routing config, but neither updateMutation.onSuccess nor monitorOpenAiBrowserOAuth invalidates the ["agent-config", ...] cache. With staleTime: 10_000, the old routing model stays cached for up to 10 seconds. If the user saves a provider, then immediately clicks "Update" again, the dialog pre-populates the old channel — the opposite of what this PR intends.

🐛 Proposed fix — add agent-config invalidation in both mutation handlers

In updateMutation.onSuccess (around line 296):

 setTimeout(() => {
     queryClient.invalidateQueries({ queryKey: ["agents"] });
+    queryClient.invalidateQueries({ queryKey: ["agent-config"] });
     queryClient.invalidateQueries({ queryKey: ["overview"] });
 }, 3000);

In monitorOpenAiBrowserOAuth success branch (around line 391):

 setTimeout(() => {
     queryClient.invalidateQueries({queryKey: ["agents"]});
+    queryClient.invalidateQueries({queryKey: ["agent-config"]});
     queryClient.invalidateQueries({queryKey: ["overview"]});
 }, 3000);

Using the prefix key ["agent-config"] (no agent ID) invalidates all variants, so it works even before defaultAgentId is known.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/Settings.tsx` around lines 270 - 275, The agent-config
query (useQuery with queryKey ["agent-config", defaultAgentId]) is never
invalidated after provider updates, causing stale routing info; update both
updateMutation.onSuccess and the success branch of monitorOpenAiBrowserOAuth to
call the React Query invalidation for the agent-config prefix (e.g.,
queryClient.invalidateQueries(["agent-config"])) so all variants (including
["agent-config", defaultAgentId]) are refreshed after provider mutations or
OAuth completion.


// If the routing config loads *after* the edit dialog is already open (race
// condition: user clicks edit before the agent-config query resolves), update
// the model field to show the active routing model instead of defaultModel.
useEffect(() => {
if (!editingProvider) return;
const currentChannel = defaultAgentConfig?.routing?.channel;
if (!currentChannel?.startsWith(`${editingProvider}/`)) return;
setModelInput(currentChannel);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultAgentConfig]);
// Note: intentionally omitting editingProvider and modelInput from deps — we
// only want this to fire when the config data arrives, not on every keystroke.
Comment on lines +284 to +295
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against overwriting user edits when the routing config arrives late.

If a user starts typing before defaultAgentConfig resolves, the effect will overwrite their input. Consider only replacing the model when it’s still the untouched default (or empty).

💡 Safer update to avoid clobbering user input
useEffect(() => {
    if (!editingProvider) return;
    const currentChannel = defaultAgentConfig?.routing?.channel;
    if (!currentChannel?.startsWith(`${editingProvider}/`)) return;
-   setModelInput(currentChannel);
+   const providerDefault =
+       PROVIDERS.find((p) => p.id === editingProvider)?.defaultModel ?? "";
+   setModelInput((prev) =>
+       prev === providerDefault || !prev ? currentChannel : prev
+   );
 // eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultAgentConfig]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/Settings.tsx` around lines 278 - 289, The effect in
useEffect that sets modelInput from defaultAgentConfig can clobber a user's
in-progress edits; modify the effect to only call setModelInput when the input
is still untouched (e.g., modelInput is empty or equal to the default value).
Concretely, in the useEffect that currently checks
defaultAgentConfig?.routing?.channel and calls setModelInput(currentChannel),
add a guard like if (!modelInput || modelInput === defaultModel) before
setModelInput(currentChannel), and include modelInput and defaultModel in the
effect dependency array so the guard observes the latest input state; keep
editingProvider out if you still only want to trigger on config arrival.


// Fetch global settings (only when on api-keys, server, or worker-logs tabs)
const { data: globalSettings, isLoading: globalSettingsLoading } = useQuery({
queryKey: ["global-settings"],
Expand Down Expand Up @@ -591,7 +623,16 @@ export function Settings() {
onEdit={() => {
setEditingProvider(provider.id);
setKeyInput("");
setModelInput(provider.defaultModel ?? "");
// When the provider is already configured, pre-populate
// the model field with the current routing model so the
// user sees (and can adjust) what's actually active,
// rather than the hardcoded defaultModel placeholder.
const currentChannel = defaultAgentConfig?.routing?.channel;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One edge case: if the user clicks “Update” before defaultAgentConfig finishes loading, this still sets modelInput to defaultModel and won’t update when the query resolves. Might be worth re-computing modelInput when defaultAgentConfig arrives (while the dialog is open), or disabling the edit action until routing is available.

const currentModel =
isConfigured(provider.id) && currentChannel?.startsWith(`${provider.id}/`)
? currentChannel
: null;
setModelInput(currentModel ?? provider.defaultModel ?? "");
setTestedSignature(null);
setTestResult(null);
setMessage(null);
Expand Down
5 changes: 3 additions & 2 deletions src/api/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ pub(super) async fn get_providers(
) -> Result<Json<ProvidersResponse>, StatusCode> {
let config_path = state.config_path.read().await.clone();
let instance_dir = (**state.instance_dir.load()).clone();
let anthropic_oauth_configured = crate::auth::credentials_path(&instance_dir).exists();
let openai_oauth_configured = crate::openai_auth::credentials_path(&instance_dir).exists();

let (
Expand Down Expand Up @@ -470,7 +471,7 @@ pub(super) async fn get_providers(
};

(
has_value("anthropic_key", "ANTHROPIC_API_KEY"),
has_value("anthropic_key", "ANTHROPIC_API_KEY") || anthropic_oauth_configured,
has_value("openai_key", "OPENAI_API_KEY"),
openai_oauth_configured,
has_value("openrouter_key", "OPENROUTER_API_KEY"),
Expand All @@ -493,7 +494,7 @@ pub(super) async fn get_providers(
)
} else {
(
std::env::var("ANTHROPIC_API_KEY").is_ok(),
std::env::var("ANTHROPIC_API_KEY").is_ok() || anthropic_oauth_configured,
std::env::var("OPENAI_API_KEY").is_ok(),
openai_oauth_configured,
std::env::var("OPENROUTER_API_KEY").is_ok(),
Expand Down