blob: b4c0f14a61a47f66ae7e3452ee789986eaa28cfa [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 super::{Error, authentication::Method};
use maka_agent_facts::configuration::ModelInfo;
use maka_agent_facts::configuration::ModelOverride;
use maka_agent_facts::execution::ThinkingLevel;
use maka_agent_facts::model::request::ProviderKind;
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// Persistent user configuration is opaque to other providers, not a credential.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Connection {
pub id: String,
pub revision: u64,
pub configuration: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Descriptor {
pub label: String,
/// Offers a user-selected endpoint instead of a named service.
#[serde(default)]
pub custom_endpoint: bool,
pub configuration_schema: Value,
pub configuration_defaults: Value,
pub authentication: Vec<Method>,
/// Whether the provider supports a connection without a stored credential.
/// Presentation metadata; authorization remains the provider's decision.
pub anonymous: bool,
pub discovery: bool,
}
impl Descriptor {
pub fn validate(&self) -> Result<(), Error> {
if self.label.trim().is_empty()
|| self.label.chars().any(char::is_control)
|| !self.configuration_schema.is_object()
|| !self.configuration_defaults.is_object()
{
return Err(Error::Invalid("invalid provider descriptor".into()));
}
let mut methods = std::collections::HashSet::new();
for method in &self.authentication {
method.validate()?;
if !methods.insert(&method.id) {
return Err(Error::Invalid("duplicate authentication method".into()));
}
}
Ok(())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Resolve {
pub connection: Connection,
pub model: ModelInfo,
pub overrides: Option<ModelOverride>,
pub thinking_level: Option<ThinkingLevel>,
}
/// Resolved protocol settings. No prompt, tools or credential writes are exposed.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Model {
pub adapter: String,
pub protocol: ProviderKind,
pub base_url: String,
pub info: ModelInfo,
pub thinking_levels: Vec<ThinkingLevel>,
pub provider_options: Value,
/// Resolved reply budget, distinct from the model's advertised capacity.
pub main_output_limit: Option<u64>,
}
impl Model {
pub fn validate(&self) -> Result<(), Error> {
crate::name(&self.adapter).map_err(|e| Error::Invalid(e.to_string()))?;
maka_agent_facts::configuration::validation::normalize_base_url(&self.base_url)
.map_err(Error::Invalid)?;
self.info.validate().map_err(Error::Invalid)?;
if let Some(limit) = self.main_output_limit {
maka_agent_facts::configuration::validation::revision(limit, true)
.map_err(Error::Invalid)?;
if self
.info
.max_output_tokens
.is_some_and(|capacity| limit > capacity)
{
return Err(Error::Invalid("reply budget exceeds model capacity".into()));
}
}
if !self.provider_options.is_object() && !self.provider_options.is_null() {
return Err(Error::Invalid("provider options must be an object".into()));
}
for (index, level) in self.thinking_levels.iter().enumerate() {
if self.thinking_levels[..index].contains(level) {
return Err(Error::Invalid("duplicate thinking level".into()));
}
}
Ok(())
}
}