Skip to content

Commit c55b54b

Browse files
committed
fix: reuse stored credentials for model discovery
1 parent e4fda77 commit c55b54b

5 files changed

Lines changed: 27 additions & 8 deletions

File tree

‎docs/openapi/ai-provider-v1.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,11 +458,12 @@
458458
"DiscoverModelsRequest": {
459459
"type": "object",
460460
"additionalProperties": false,
461-
"required": ["kind", "endpoint", "credential"],
461+
"required": ["kind", "endpoint"],
462462
"properties": {
463+
"providerId": {"type": "string", "format": "uuid", "description": "Use a visible enabled Provider and its stored credential when credential is omitted."},
463464
"kind": {"$ref": "#/components/schemas/ProviderKind"},
464465
"endpoint": {"type": "string", "format": "uri", "pattern": "^https://", "maxLength": 2048},
465-
"credential": {"type": "string", "minLength": 1, "maxLength": 8192, "writeOnly": true}
466+
"credential": {"type": "string", "maxLength": 8192, "writeOnly": true}
466467
}
467468
},
468469
"DiscoveredModel": {

‎src/api/ai/providers.rs‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use axum::{
99
routing::{get, post},
1010
};
1111
use reqwest::redirect::Policy;
12-
use secrecy::SecretString;
12+
use secrecy::{ExposeSecret, SecretString};
1313
use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned, de::Visitor};
1414
use time::{OffsetDateTime, UtcOffset, macros::format_description};
1515
use uuid::Uuid;
@@ -239,8 +239,11 @@ struct ProviderListResponse {
239239
#[derive(Deserialize)]
240240
#[serde(rename_all = "camelCase", deny_unknown_fields)]
241241
struct DiscoverModelsRequest {
242+
#[serde(default)]
243+
provider_id: Option<String>,
242244
kind: ProviderKindRequest,
243245
endpoint: String,
246+
#[serde(default)]
244247
credential: String,
245248
}
246249

@@ -367,10 +370,22 @@ async fn discover_models(
367370
.provider_mutation_limiter
368371
.check(&user.id)
369372
.map_err(map_limiter_rejection)?;
370-
let kind: ProviderKind = request.kind.into();
371-
let endpoint = ProviderEndpoint::new(kind, Some(request.endpoint.trim()))
373+
let mut kind: ProviderKind = request.kind.into();
374+
let mut endpoint_value = request.endpoint;
375+
let mut credential = request.credential.trim().to_owned();
376+
if credential.is_empty() {
377+
if let Some(provider_id) = request.provider_id.as_deref() {
378+
let binding = query_repository(&state)?
379+
.load_enabled_binding(provider_id, &user.id)
380+
.await
381+
.map_err(map_provider_error)?;
382+
kind = binding.metadata().kind();
383+
endpoint_value = binding.metadata().endpoint().as_str().to_owned();
384+
credential = binding.credential().expose_secret().to_owned();
385+
}
386+
}
387+
let endpoint = ProviderEndpoint::new(kind, Some(endpoint_value.trim()))
372388
.map_err(|_| ApiError::validation().with_field("endpoint", "Endpoint is invalid"))?;
373-
let credential = request.credential.trim();
374389
if credential.is_empty() || credential.len() > 8192 {
375390
return Err(ApiError::validation().with_field("credential", "Credential is invalid"));
376391
}
@@ -408,7 +423,7 @@ async fn discover_models(
408423
let mut request = client.get(url.clone()).header("accept", "application/json");
409424
match kind {
410425
ProviderKind::GoogleGemini => {
411-
url.query_pairs_mut().append_pair("key", credential);
426+
url.query_pairs_mut().append_pair("key", &credential);
412427
request = client.get(url).header("accept", "application/json");
413428
}
414429
ProviderKind::AnthropicMessages => {

‎src/content/provider/model.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ impl ProviderBinding {
439439
&self.metadata
440440
}
441441

442-
pub(super) const fn credential(&self) -> &SecretString {
442+
pub(crate) const fn credential(&self) -> &SecretString {
443443
&self.credential
444444
}
445445
}

‎web/src/features/ai/api/providers.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "./provider.generated"
1111

1212
export interface ProviderModelDiscoveryRequest {
13+
providerId?: string | null
1314
kind: ProviderKind
1415
endpoint: string
1516
credential: string
@@ -81,6 +82,7 @@ export async function discoverProviderModels(
8182
method: "POST",
8283
headers: { "x-csrf-token": csrfToken },
8384
body: JSON.stringify({
85+
providerId: request.providerId ?? undefined,
8486
kind: request.kind,
8587
endpoint: request.endpoint,
8688
credential,

‎web/src/features/ai/settings/ProviderForm.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export function ProviderForm(props: ProviderFormProps) {
7070
setIsDiscoveringModels(true)
7171
try {
7272
const result = await discoverProviderModels(props.csrfToken, {
73+
providerId: props.draft.providerId,
7374
kind: props.draft.kind,
7475
endpoint: props.draft.endpoint,
7576
credential: props.draft.credential,

0 commit comments

Comments
 (0)