diff --git a/apps/fabro-web/app/routes/settings-models.tsx b/apps/fabro-web/app/routes/settings-models.tsx
index 1bde2da0e..091283ac8 100644
--- a/apps/fabro-web/app/routes/settings-models.tsx
+++ b/apps/fabro-web/app/routes/settings-models.tsx
@@ -1,4 +1,5 @@
import { useMemo, useState } from "react";
+import { Link } from "react-router";
import { ChevronDownIcon } from "@heroicons/react/16/solid";
import type { Provider } from "@qltysh/fabro-api-client";
import { useProviders } from "../lib/queries";
@@ -153,6 +154,14 @@ function ProviderStatus({ provider }: { provider: Provider }) {
Get API key →
) : null}
+ {!provider.configured && provider.expected_secret_name ? (
+
+ Add secret →
+
+ ) : null}
);
}
diff --git a/apps/fabro-web/app/routes/settings-secrets-new.tsx b/apps/fabro-web/app/routes/settings-secrets-new.tsx
index 8040f4e69..111d43e3a 100644
--- a/apps/fabro-web/app/routes/settings-secrets-new.tsx
+++ b/apps/fabro-web/app/routes/settings-secrets-new.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { Link, useNavigate } from "react-router";
+import { Link, useNavigate, useSearchParams } from "react-router";
import { useSWRConfig } from "swr";
import { ArrowLeftIcon } from "@heroicons/react/16/solid";
import { SecretType } from "@qltysh/fabro-api-client";
@@ -46,12 +46,13 @@ export default function SettingsSecretsNew() {
function CreateSecretForm() {
const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
const { mutate } = useSWRConfig();
const toast = useToast();
const [type, setType] = useState(
SecretType.TOKEN,
);
- const [name, setName] = useState("");
+ const [name, setName] = useState(() => searchParams.get("name") ?? "");
const [value, setValue] = useState("");
const [description, setDescription] = useState("");
const [submitting, setSubmitting] = useState(false);
diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml
index 91f812041..58a1e02e9 100644
--- a/docs/public/api-reference/fabro-api.yaml
+++ b/docs/public/api-reference/fabro-api.yaml
@@ -6275,6 +6275,14 @@ components:
Whether credential material is present for this provider on the
server when this response was produced. Does NOT imply requests
will succeed.
+ expected_secret_name:
+ type: ["string", "null"]
+ description: |
+ Suggested vault secret name for configuring this provider,
+ derived from the first vault credential reference in the
+ provider catalog. Null when the provider has no vault
+ credential (e.g. no-auth or env-only providers). Used to
+ prefill the create-secret form.
ProviderId:
description: LLM provider identifier.
diff --git a/lib/crates/fabro-model/src/catalog.rs b/lib/crates/fabro-model/src/catalog.rs
index d0fa36cb9..b1c931651 100644
--- a/lib/crates/fabro-model/src/catalog.rs
+++ b/lib/crates/fabro-model/src/catalog.rs
@@ -482,6 +482,20 @@ pub struct CatalogProvider {
pub aliases: Vec,
}
+impl CatalogProvider {
+ #[must_use]
+ pub fn vault_secret_name(&self) -> Option<&str> {
+ self.auth
+ .as_ref()?
+ .credentials
+ .iter()
+ .find_map(|credential_ref| match credential_ref {
+ CredentialRef::Vault(name) => Some(name.as_str()),
+ CredentialRef::Env(_) => None,
+ })
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CatalogModelControls {
pub reasoning_effort: Vec,
@@ -841,15 +855,7 @@ impl Catalog {
#[must_use]
pub fn provider_vault_secret_name(&self, id: &ProviderId) -> Option<&str> {
- self.provider(id)?
- .auth
- .as_ref()?
- .credentials
- .iter()
- .find_map(|credential_ref| match credential_ref {
- CredentialRef::Vault(name) => Some(name.as_str()),
- CredentialRef::Env(_) => None,
- })
+ self.provider(id)?.vault_secret_name()
}
#[must_use]
diff --git a/lib/crates/fabro-model/src/provider.rs b/lib/crates/fabro-model/src/provider.rs
index 03b0caa8c..5d3e227be 100644
--- a/lib/crates/fabro-model/src/provider.rs
+++ b/lib/crates/fabro-model/src/provider.rs
@@ -11,27 +11,33 @@ use crate::ids::ProviderId;
/// `agent_profile`) so credential material never reaches the wire.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Provider {
- pub id: ProviderId,
- pub display_name: String,
- pub adapter: AdapterKind,
+ pub id: ProviderId,
+ pub display_name: String,
+ pub adapter: AdapterKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
- pub base_url: Option,
+ pub base_url: Option,
#[serde(default, skip_serializing_if = "Option::is_none")]
- pub api_key_url: Option,
- pub priority: i32,
+ pub api_key_url: Option,
+ pub priority: i32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub aliases: Vec,
+ pub aliases: Vec,
/// Number of catalog models for this provider. Stamped by the handler.
- pub model_count: u32,
+ pub model_count: u32,
/// Catalog default model ID for this provider, if any. Stamped by the
/// handler.
#[serde(default, skip_serializing_if = "Option::is_none")]
- pub default_model: Option,
+ pub default_model: Option,
/// True if the server has credential material configured for this provider
/// when the response is produced. Always `false` in static catalog data;
/// stamped by `GET /providers` per request.
#[serde(default)]
- pub configured: bool,
+ pub configured: bool,
+ /// Suggested vault secret name for configuring this provider, derived
+ /// from the first vault credential in the catalog. `None` when the
+ /// provider has no vault credential (e.g. Ollama, env-only providers).
+ /// Used by the web UI to prefill the create-secret form.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub expected_secret_name: Option,
}
impl Provider {
@@ -53,6 +59,7 @@ impl Provider {
model_count,
default_model,
configured,
+ expected_secret_name: provider.vault_secret_name().map(str::to_owned),
}
}
}
@@ -80,5 +87,9 @@ mod tests {
assert_eq!(provider.model_count, 7);
assert_eq!(provider.default_model.as_deref(), Some("claude-opus-4-7"));
assert!(provider.configured);
+ assert_eq!(
+ provider.expected_secret_name.as_deref(),
+ Some("ANTHROPIC_API_KEY"),
+ );
}
}
diff --git a/lib/packages/fabro-api-client/src/models/provider.ts b/lib/packages/fabro-api-client/src/models/provider.ts
index 6d18c4473..901f1ca37 100644
--- a/lib/packages/fabro-api-client/src/models/provider.ts
+++ b/lib/packages/fabro-api-client/src/models/provider.ts
@@ -58,6 +58,10 @@ export interface Provider {
* Whether credential material is present for this provider on the server when this response was produced. Does NOT imply requests will succeed.
*/
'configured': boolean;
+ /**
+ * Suggested vault secret name for configuring this provider, derived from the first vault credential reference in the provider catalog. Null when the provider has no vault credential (e.g. no-auth or env-only providers). Used to prefill the create-secret form.
+ */
+ 'expected_secret_name'?: string | null;
}
export const ProviderAdapterEnum = {