blob: cd51c1816f3c5439b7fd7762f69b25665d86a1a1 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::{connections::encode, contract::SetupResult};
use maka_agent_facts::{
configuration::ConnectionCredentialTarget,
configuration::onboarding::{OnboardingRejection, OnboardingSaveResult},
oauth::Target,
};
use maka_agent_ports::provider;
use maka_config::{ConfigurationStore, onboarding::OnboardingPreparation};
use maka_host_runtime::application::Context;
use serde_json::Value;
use std::{
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
pub(crate) async fn discover(
store: &Arc<ConfigurationStore>,
catalog: &maka_providers::catalog::Catalog,
input: Value,
context: Context,
) -> Result<Value, String> {
context.operation().ok_or("settings_mutation_required")?;
let expected: ConnectionCredentialTarget =
serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
// Explicit refresh schedules the Root-owned public sources without waiting
// for an unrelated public endpoint. Account discovery uses last-good facts.
if let Ok(completed) = catalog.request_refresh() {
drop(completed);
}
let snapshot = catalog
.snapshot
.read()
.await
.as_ref()
.map_err(|_| "settings_store_unavailable")?
.clone();
let Ok(definition) = crate::connections::definition(&expected.provider, snapshot) else {
return encode(SetupResult::Failed);
};
let ticket = match store
.prepare_onboarding(Target::Existing {
configuration: expected.configuration.clone(),
expected,
})
.await
.map_err(|_| "settings_store_unavailable")?
{
OnboardingPreparation::Ready(ticket) => ticket,
OnboardingPreparation::Rejected(_) => return encode(SetupResult::Superseded),
};
let network = ticket.network_configuration();
let Ok(policy) =
maka_network::Policy::from_host_settings(&network.proxy, network.password.as_deref())
else {
return encode(SetupResult::Failed);
};
let Ok(transport) = maka_model::provider_transport(&policy) else {
return encode(SetupResult::Failed);
};
discover_prepared(
ticket,
definition,
provider::Context {
transport,
cancellation: context.scope().cancellation.clone(),
interaction: None,
},
)
.await
}
pub(crate) async fn discover_prepared(
ticket: Box<maka_config::onboarding::PreparedOnboarding>,
definition: provider::Definition,
context: provider::Context,
) -> Result<Value, String> {
let Ok(credential) = ticket.provider_credential() else {
return encode(SetupResult::Failed);
};
let Ok(request_headers) = ticket
.request_headers()
.map(serde_json::from_str)
.transpose()
else {
return encode(SetupResult::Failed);
};
let row = ticket.connection();
let request = provider::Discovery {
connection: provider::Connection {
id: row.connection_id.clone(),
revision: row.revision,
configuration: row.configuration.clone(),
},
credential: credential.map(|value| value.credential().clone()),
request_headers: request_headers.unwrap_or_default(),
};
let cancellation = context.cancellation.clone();
let models = match definition.discover(request, context).await {
Ok(models) if !models.is_empty() => models,
_ => return encode(SetupResult::Failed),
};
if cancellation.is_cancelled() {
return encode(SetupResult::Failed);
}
// Preserve existing choices; first-time setup exposes the discovered models.
let mut enabled: Vec<String> = row
.enabled_model_ids
.iter()
.filter(|id| models.iter().any(|model| &model.id == *id))
.cloned()
.collect();
if enabled.is_empty() {
enabled = models.iter().map(|model| model.id.clone()).collect();
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| "settings_clock_unavailable")?
.as_secs();
match ticket
.complete(models, enabled, now)
.await
.map_err(|_| "settings_effect_unknown")?
{
OnboardingSaveResult::Saved { connection } => encode(SetupResult::Ready {
connection: connection.basis(),
}),
OnboardingSaveResult::Rejected {
reason: OnboardingRejection::Superseded | OnboardingRejection::ConnectionNotFound,
} => encode(SetupResult::Superseded),
_ => encode(SetupResult::Failed),
}
}