You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/10/22 14:51:33 UTC
(maka) 09/18: Use injected public model snapshots for settings and admission
This is an automated email from the ASF dual-hosted git repository.
M4n5ter pushed a commit to branch rust-preview
in repository https://gitbox.apache.org/repos/asf/maka.git
commit 50b9d1bcd256155808ec1169091fc97be0b2a095
Author: M4n5ter <m4...@gmail.com>
AuthorDate: Thu Oct 22 21:59:26 2026 +0800
Use injected public model snapshots for settings and admission
---
plugins/agent/native/src/lib.rs | 10 +-
plugins/agent/native/src/model.rs | 28 ++++-
plugins/agent/native/src/owner.rs | 26 +++--
plugins/agent/native/src/owner/compaction.rs | 1 +
plugins/agent/native/src/owner/execution.rs | 1 +
plugins/agent/native/src/owner/preferences.rs | 8 +-
plugins/agent/native/src/owner/recap.rs | 1 +
.../agent/native/src/owner/scheduler/dispatch.rs | 1 +
plugins/agent/native/src/owner/skills.rs | 18 +--
plugins/agent/native/src/owner/tests.rs | 20 +++-
plugins/agent/native/src/owner/tests/execution.rs | 4 +-
plugins/agent/native/src/owner/tests/executor.rs | 4 +-
plugins/agent/native/src/owner/tests/health.rs | 2 +-
.../agent/native/src/owner/tests/large_messages.rs | 2 +-
.../native/src/owner/tests/prompt_language.rs | 2 +-
plugins/agent/native/src/owner/tests/queue.rs | 2 +-
plugins/agent/native/src/owner/tests/scheduler.rs | 2 +-
plugins/agent/native/src/owner/tests/selection.rs | 108 +++++++++++++++++
.../agent/native/src/owner/tests/service_tools.rs | 4 +-
.../owner/tests/service_tools/storm/combined.rs | 4 +-
plugins/agent/native/src/owner/tests/settings.rs | 2 +-
.../native/src/owner/tests/skill_preparation.rs | 2 +-
plugins/agent/native/src/owner/tests/terminal.rs | 2 +-
plugins/agent/native/src/owner/tests/todo.rs | 4 +-
plugins/agent/native/src/owner/tests/workhub.rs | 4 +-
plugins/agent/native/src/owner/tests/workspaces.rs | 2 +-
plugins/agent/native/src/owner/tests/worktrees.rs | 4 +-
plugins/agent/native/src/workhub.rs | 10 +-
plugins/agent/native/src/workhub/presets.rs | 8 +-
plugins/agent/providers/src/api.rs | 18 ++-
plugins/agent/providers/src/api/effects.rs | 8 +-
plugins/agent/providers/src/api/metadata.rs | 98 +---------------
plugins/agent/providers/src/api/options.rs | 82 +++++--------
.../agent/providers/src/api/options/compatible.rs | 54 ++-------
plugins/agent/providers/src/api/options/relay.rs | 8 +-
plugins/agent/providers/src/lib.rs | 3 +-
plugins/agent/providers/tests/api.rs | 16 ++-
plugins/agent/providers/tests/api/discovery.rs | 1 +
plugins/agent/providers/tests/api/effects.rs | 15 ++-
plugins/agent/providers/tests/api/policy.rs | 1 +
plugins/settings/native/src/connections.rs | 26 +++--
plugins/settings/native/src/credential.rs | 10 +-
plugins/settings/native/src/credential/tests.rs | 24 +++-
plugins/settings/native/src/lib.rs | 8 +-
plugins/settings/native/src/service.rs | 130 +++++++++++++++++----
plugins/settings/native/src/setup.rs | 15 ++-
.../maka/terminal-shell/tests/real_host/oauth.rs | 7 +-
47 files changed, 493 insertions(+), 317 deletions(-)
diff --git a/plugins/agent/native/src/lib.rs b/plugins/agent/native/src/lib.rs
index 7e8117f02d..7fffcb8271 100644
--- a/plugins/agent/native/src/lib.rs
+++ b/plugins/agent/native/src/lib.rs
@@ -34,12 +34,16 @@ use maka_host_runtime::application::{NativeActivation, NativeContext, NativeRegi
use maka_workspace_support as workspace;
use std::sync::Arc;
-pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
+pub fn provide(
+ registry: &mut NativeRegistry,
+ catalog: Arc<maka_providers::catalog::Catalog>,
+) -> Result<(), String> {
registry.insert(
maka_agent_api::IMPLEMENTATION.into(),
- Arc::new(|context, configuration| {
+ Arc::new(move |context, configuration| {
+ let catalog = catalog.clone();
Box::pin(async move {
- match owner::Owner::open(context, configuration).await {
+ match owner::Owner::open(context, configuration, catalog).await {
Ok(owner) => {
let service = Arc::new(service::Agent(owner));
NativeActivation {
diff --git a/plugins/agent/native/src/model.rs b/plugins/agent/native/src/model.rs
index 2efd1aef69..b15b7f7d8c 100644
--- a/plugins/agent/native/src/model.rs
+++ b/plugins/agent/native/src/model.rs
@@ -82,6 +82,9 @@ pub(crate) struct AcceptedModel {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub editing_tools: Option<EditingTools>,
context: ModelRequestContext,
+ /// Public defaults at admission only; account reports and overrides stay live.
+ #[serde(deserialize_with = "Option::deserialize")]
+ public_model: Option<ModelInfo>,
provider: maka_agent_facts::provider::Identity,
provider_configuration: Value,
basis: ExecutionBasis,
@@ -102,6 +105,7 @@ pub(crate) struct Source {
impl Source {
pub async fn select(
configuration: Arc<ConfigurationStore>,
+ catalog: Arc<maka_providers::catalog::Catalog>,
credentials: Credentials,
selection: ModelSelection,
thinking_level: Option<ThinkingLevel>,
@@ -111,6 +115,8 @@ impl Source {
let span = tracing::warn_span!("agent_model_preparation", session_id = %session,
connection_id = %selection.connection_id, activation = %revision.activation);
async move {
+ let public_guard = catalog.snapshot.read().await;
+ let snapshot = public_guard.as_ref().map_err(|_| unavailable())?.clone();
let material = configuration
.observe_selected_model(selection.clone())
.await
@@ -119,9 +125,14 @@ impl Source {
unavailable()
})?
.ok_or_else(unavailable)?;
+ drop(public_guard);
let row = &material.connection;
+ let public_model = snapshot.get(&row.provider.name, &selection.model_id).cloned();
+ let snapshot = Arc::new(maka_providers::catalog::Snapshot::for_model(
+ &row.provider.name, &selection.model_id, public_model.clone(),
+ ).map_err(|_| unavailable())?);
let definition =
- Arc::new(maka_providers::definition(&row.provider).map_err(|_| {
+ Arc::new(maka_providers::definition(&row.provider, snapshot).map_err(|_| {
tracing::warn!(stage = "provider_definition", cause = "provider_definition_unavailable",
provider = %row.provider.name, "Model preparation failed");
unavailable()
@@ -133,6 +144,7 @@ impl Source {
credentials,
accepted: AcceptedModel {
context: request_context(row, &model.info)?,
+ public_model,
editing_tools: Some(editing_tools(row, &selection.model_id, &model.base_url)),
tool_mode: ToolMode::for_model(
&selection.model_id,
@@ -171,8 +183,14 @@ impl Source {
let _span = tracing::warn_span!("agent_model_preparation", session_id = %session,
connection_id = %accepted.binding.connection_id, activation = %revision.activation)
.entered();
+ let snapshot = maka_providers::catalog::Snapshot::for_model(
+ &accepted.provider.name,
+ &accepted.binding.model,
+ accepted.public_model.clone(),
+ )
+ .map_err(|_| unavailable())?;
let definition =
- Arc::new(maka_providers::definition(&accepted.provider).map_err(|_| {
+ Arc::new(maka_providers::definition(&accepted.provider, Arc::new(snapshot)).map_err(|_| {
tracing::warn!(stage = "restore_provider_definition", cause = "provider_definition_unavailable",
provider = %accepted.provider.name, "Model preparation failed");
unavailable()
@@ -420,9 +438,12 @@ impl ModelSource for Source {
/// Resolve tool compatibility without preparing credentials or starting execution.
pub(crate) async fn candidate_editing_tools(
configuration: &Arc<ConfigurationStore>,
+ catalog: &maka_providers::catalog::Catalog,
selection: ModelSelection,
thinking_level: Option<ThinkingLevel>,
) -> Result<EditingTools, RunError> {
+ let public_guard = catalog.snapshot.read().await;
+ let snapshot = public_guard.as_ref().map_err(|_| unavailable())?.clone();
let material = configuration
.observe_selected_model(selection.clone())
.await
@@ -435,8 +456,9 @@ pub(crate) async fn candidate_editing_tools(
unavailable()
})?
.ok_or_else(unavailable)?;
+ drop(public_guard);
let row = &material.connection;
- let definition = maka_providers::definition(&row.provider).map_err(|_| {
+ let definition = maka_providers::definition(&row.provider, snapshot).map_err(|_| {
tracing::warn!(stage = "provider_definition", cause = "provider_definition_unavailable", connection_id = %row.connection_id,
provider = %row.provider.name, "Model preparation failed");
unavailable()
diff --git a/plugins/agent/native/src/owner.rs b/plugins/agent/native/src/owner.rs
index eac9659db5..53d5d7085b 100644
--- a/plugins/agent/native/src/owner.rs
+++ b/plugins/agent/native/src/owner.rs
@@ -171,6 +171,7 @@ pub(crate) struct Owner {
pub prefix: String,
pub data_source: DataSource,
pub(crate) configuration: Arc<ConfigurationStore>,
+ pub(crate) catalog: Arc<maka_providers::catalog::Catalog>,
credentials: crate::model::Credentials,
revision: SourceRevision,
behavior: Option<BuiltinBehavior>,
@@ -221,27 +222,26 @@ impl Owner {
self.executors.setup(input, context).await
}
- pub async fn open(context: NativeContext, input: Value) -> Result<Arc<Self>, String> {
- Self::open_behavior(
- context,
- input,
- IMPLEMENTATION,
- None,
- vec![],
- BTreeMap::new(),
- None,
- )
- .await
+ pub async fn open(
+ context: NativeContext,
+ input: Value,
+ catalog: Arc<maka_providers::catalog::Catalog>,
+ ) -> Result<Arc<Self>, String> {
+ Self::open_behavior(context, input, None, vec![], BTreeMap::new(), None, catalog).await
}
pub(crate) async fn open_behavior(
context: NativeContext,
input: Value,
- implementation: &'static str,
behavior: Option<BuiltinBehavior>,
mut additional: Vec<maka_agent_facts::tools::ToolRegistration>,
internal_bindings: BTreeMap<String, Option<String>>,
graphs: Option<Arc<crate::workhub::tasks::graph::Graphs>>,
+ catalog: Arc<maka_providers::catalog::Catalog>,
) -> Result<Arc<Self>, String> {
+ let implementation = match behavior {
+ Some(BuiltinBehavior::WorkHub) => maka_agent_api::workhub::IMPLEMENTATION,
+ None => IMPLEMENTATION,
+ };
if !jsonschema_valid(&input) {
return Err("agent_configuration_invalid".into());
}
@@ -463,6 +463,7 @@ impl Owner {
internal_bindings,
settings,
configuration,
+ catalog,
credentials,
engine,
tools,
@@ -911,6 +912,7 @@ impl Owner {
Selection::Model(selection, thinking_level) => PreparedBackend::Model(Box::new(
crate::model::Source::select(
self.configuration.clone(),
+ self.catalog.clone(),
self.credentials.clone(),
selection,
thinking_level,
diff --git a/plugins/agent/native/src/owner/compaction.rs b/plugins/agent/native/src/owner/compaction.rs
index 17731a8968..8c43202490 100644
--- a/plugins/agent/native/src/owner/compaction.rs
+++ b/plugins/agent/native/src/owner/compaction.rs
@@ -118,6 +118,7 @@ impl Owner {
}
let (source, prepared) = crate::model::Source::select(
self.configuration.clone(),
+ self.catalog.clone(),
self.credentials.clone(),
input.model,
input.thinking_level,
diff --git a/plugins/agent/native/src/owner/execution.rs b/plugins/agent/native/src/owner/execution.rs
index c3b0d10b99..bf9e8d9ddb 100644
--- a/plugins/agent/native/src/owner/execution.rs
+++ b/plugins/agent/native/src/owner/execution.rs
@@ -256,6 +256,7 @@ impl Owner {
}
let (mut source, prepared) = crate::model::Source::select(
self.configuration.clone(),
+ self.catalog.clone(),
self.credentials.clone(),
input.model,
input.thinking_level,
diff --git a/plugins/agent/native/src/owner/preferences.rs b/plugins/agent/native/src/owner/preferences.rs
index 193a6b3353..53adca556d 100644
--- a/plugins/agent/native/src/owner/preferences.rs
+++ b/plugins/agent/native/src/owner/preferences.rs
@@ -46,6 +46,11 @@ impl Owner {
session: Box::new(crate::service::summary(self, session)),
});
}
+ let public_guard = self.catalog.snapshot.read().await;
+ let snapshot = public_guard
+ .as_ref()
+ .map_err(|_| "agent_configuration_unavailable")?
+ .clone();
let available = match &input.backend {
BackendPreference::Model {
connection_id,
@@ -62,7 +67,7 @@ impl Owner {
row.connection_id == *connection_id
&& row.enabled
&& row.enabled_model_ids.contains(model_id)
- && maka_providers::definition(&row.provider).is_ok()
+ && maka_providers::definition(&row.provider, snapshot.clone()).is_ok()
}),
BackendPreference::Executor { selection } => self
.settings
@@ -70,6 +75,7 @@ impl Owner {
.iter()
.any(|entry| entry.name == selection.executor),
};
+ drop(public_guard);
if !available {
return Ok(MetadataResult::Rejected {
reason: Rejection::ToolsUnavailable,
diff --git a/plugins/agent/native/src/owner/recap.rs b/plugins/agent/native/src/owner/recap.rs
index 22bcf8090e..c64a611379 100644
--- a/plugins/agent/native/src/owner/recap.rs
+++ b/plugins/agent/native/src/owner/recap.rs
@@ -255,6 +255,7 @@ impl Owner {
input.validate().map_err(failed)?;
let (source, _) = crate::model::Source::select(
self.configuration.clone(),
+ self.catalog.clone(),
self.credentials.clone(),
model,
None,
diff --git a/plugins/agent/native/src/owner/scheduler/dispatch.rs b/plugins/agent/native/src/owner/scheduler/dispatch.rs
index 5e1e98fe81..0ff07837d1 100644
--- a/plugins/agent/native/src/owner/scheduler/dispatch.rs
+++ b/plugins/agent/native/src/owner/scheduler/dispatch.rs
@@ -140,6 +140,7 @@ impl Owner {
.await?;
let (source, _) = crate::model::Source::select(
self.configuration.clone(),
+ self.catalog.clone(),
self.credentials.clone(),
model,
thinking,
diff --git a/plugins/agent/native/src/owner/skills.rs b/plugins/agent/native/src/owner/skills.rs
index dd4a31fd9e..66307b3d0c 100644
--- a/plugins/agent/native/src/owner/skills.rs
+++ b/plugins/agent/native/src/owner/skills.rs
@@ -119,13 +119,17 @@ impl Owner {
else {
return Ok(SkillCandidates::default());
};
- let editing =
- match crate::model::candidate_editing_tools(&self.configuration, model, thinking_level)
- .await
- {
- Ok(editing) => editing,
- Err(_) => return Ok(SkillCandidates::default()),
- };
+ let editing = match crate::model::candidate_editing_tools(
+ &self.configuration,
+ &self.catalog,
+ model,
+ thinking_level,
+ )
+ .await
+ {
+ Ok(editing) => editing,
+ Err(_) => return Ok(SkillCandidates::default()),
+ };
workspace::validate(
&self.workspace_roots,
&session.configuration.workspace,
diff --git a/plugins/agent/native/src/owner/tests.rs b/plugins/agent/native/src/owner/tests.rs
index 51e6d75026..ed06630586 100644
--- a/plugins/agent/native/src/owner/tests.rs
+++ b/plugins/agent/native/src/owner/tests.rs
@@ -483,6 +483,12 @@ fn log_text(file: &mut std::fs::File) -> String {
text
}
+fn bundled_catalog() -> Arc<maka_providers::catalog::Catalog> {
+ Arc::new(maka_providers::catalog::Catalog::new(
+ maka_providers::catalog::Snapshot::bundled().unwrap(),
+ ))
+}
+
struct Fixture {
services: maka_host_runtime::application::NativeServices,
_dir: tempfile::TempDir,
@@ -647,7 +653,8 @@ impl Fixture {
let capture = captured.clone();
let replacement = Arc::new(StdMutex::new(model));
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ let catalog = bundled_catalog();
+ maka_settings_native::provide(&mut native, catalog.clone()).unwrap();
if let Some(tooling) = &tooling {
tooling.provide(&mut native);
}
@@ -658,10 +665,11 @@ impl Fixture {
IMPLEMENTATION.into(),
Arc::new(move |context, configuration| {
let capture = capture.clone();
+ let catalog = catalog.clone();
let replacement = replacement.lock().unwrap().take();
Box::pin(async move {
let services = context.services();
- let mut owner = Owner::open(context, configuration).await.unwrap();
+ let mut owner = Owner::open(context, configuration, catalog).await.unwrap();
if let Some(model) = replacement {
let store = owner.store.clone();
Arc::get_mut(&mut owner).unwrap().engine = Engine::new(
@@ -1499,7 +1507,7 @@ async fn public_availability_resumes_stable_sessions_and_separates_deployments()
let deployment = fixture.app.source().unwrap().deployment;
fixture.app.shutdown().await.unwrap();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
crate::owner::tests::provide_all(&mut native).unwrap();
let resumed = Application::start_from_store(
fixture.store.clone(),
@@ -1540,7 +1548,7 @@ async fn public_availability_resumes_stable_sessions_and_separates_deployments()
.await
.unwrap();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
crate::owner::tests::provide_all(&mut native).unwrap();
let other = Application::start_from_store(
fixture.store.clone(),
@@ -1894,7 +1902,7 @@ fn catalog_interaction(
}
fn provide_all(registry: &mut NativeRegistry) -> Result<(), String> {
- crate::provide(registry)?;
+ crate::provide(registry, bundled_catalog())?;
maka_acp_native::provide(registry)?;
maka_files_native::provide(registry)?;
maka_processes_native::provide(registry)?;
@@ -1903,6 +1911,6 @@ fn provide_all(registry: &mut NativeRegistry) -> Result<(), String> {
maka_interactions_native::forms::provide(registry)?;
maka_web::provide(registry)?;
maka_jev_native::provide(registry)?;
- crate::workhub::provide(registry)?;
+ crate::workhub::provide(registry, bundled_catalog())?;
Ok(())
}
diff --git a/plugins/agent/native/src/owner/tests/execution.rs b/plugins/agent/native/src/owner/tests/execution.rs
index 7d2b4cb6ab..323a4808e9 100644
--- a/plugins/agent/native/src/owner/tests/execution.rs
+++ b/plugins/agent/native/src/owner/tests/execution.rs
@@ -408,14 +408,14 @@ async fn application_reactivation_resumes_sealed_source_through_original_engine(
let captured = Arc::new(StdMutex::new(None));
let capture = captured.clone();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
native
.insert(
IMPLEMENTATION.into(),
Arc::new(move |context, configuration| {
let capture = capture.clone();
Box::pin(async move {
- let owner = Owner::open(context, configuration).await.unwrap();
+ let owner = Owner::open(context, configuration, crate::owner::tests::bundled_catalog()).await.unwrap();
*capture.lock().unwrap() = Some(owner.clone());
let service = Arc::new(Agent(owner));
NativeActivation {
diff --git a/plugins/agent/native/src/owner/tests/executor.rs b/plugins/agent/native/src/owner/tests/executor.rs
index aedf457592..1cefd1e323 100644
--- a/plugins/agent/native/src/owner/tests/executor.rs
+++ b/plugins/agent/native/src/owner/tests/executor.rs
@@ -394,7 +394,7 @@ async fn activate_backend(
.unwrap();
}
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
if acp.is_some() {
maka_acp_native::provide(&mut native).unwrap();
maka_processes_native::provide(&mut native).unwrap();
@@ -410,7 +410,7 @@ async fn activate_backend(
let capture = capture.clone();
Box::pin(async move {
let services = context.services();
- let owner = Owner::open(context, configuration).await.unwrap();
+ let owner = Owner::open(context, configuration, crate::owner::tests::bundled_catalog()).await.unwrap();
*capture.lock().unwrap() = Some((owner.clone(), services));
let agent = Arc::new(Agent(owner));
NativeActivation {
diff --git a/plugins/agent/native/src/owner/tests/health.rs b/plugins/agent/native/src/owner/tests/health.rs
index 838b9e98c8..85b35f38ae 100644
--- a/plugins/agent/native/src/owner/tests/health.rs
+++ b/plugins/agent/native/src/owner/tests/health.rs
@@ -198,7 +198,7 @@ mod acceptance {
let original_receipt = f.store.operation_observation(&original).await.unwrap().unwrap();
f.app.shutdown().await.unwrap();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
provide_all(&mut native).unwrap();
let policy = service_tools::Setup::health().policy();
f.issuer = policy.issuer();
diff --git a/plugins/agent/native/src/owner/tests/large_messages.rs b/plugins/agent/native/src/owner/tests/large_messages.rs
index 8df9ff6397..2cfc619155 100644
--- a/plugins/agent/native/src/owner/tests/large_messages.rs
+++ b/plugins/agent/native/src/owner/tests/large_messages.rs
@@ -154,7 +154,7 @@ async fn public_send_preserves_six_hundred_kib_through_durable_admission_and_mai
f.store.shutdown().await.unwrap();
f.store = Arc::new(EventLog::for_root(root_owner).await.unwrap());
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
provide_all(&mut native).unwrap();
f.app = Application::start_from_store(
f.store.clone(), deployment, native, f.issuer.clone(),
diff --git a/plugins/agent/native/src/owner/tests/prompt_language.rs b/plugins/agent/native/src/owner/tests/prompt_language.rs
index 9ac2f024dd..df897877c7 100644
--- a/plugins/agent/native/src/owner/tests/prompt_language.rs
+++ b/plugins/agent/native/src/owner/tests/prompt_language.rs
@@ -145,7 +145,7 @@ async fn update_preferences(
async fn start_settings_app(fixture: &Fixture, deployment: Uuid) -> Application {
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
provide_all(&mut native).unwrap();
let app = Application::start_from_store(
fixture.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/queue.rs b/plugins/agent/native/src/owner/tests/queue.rs
index fa60a56b3e..fa871ba3fe 100644
--- a/plugins/agent/native/src/owner/tests/queue.rs
+++ b/plugins/agent/native/src/owner/tests/queue.rs
@@ -329,7 +329,7 @@ use maka_agent_facts::execution::ThinkingLevel::High;
drop(raw);
let mut native = NativeRegistry::default();
crate::owner::tests::provide_all(&mut native).unwrap();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
let app = Application::start_from_store(fixture.store.clone(), deployment, native, fixture.issuer.clone(), Arc::new(Grant(fixture.issuer.clone(), None)), Some(maka_host_js::factory(maka_js_runtime::plugin::Limits::default()))).await.unwrap();
app.ready().await.unwrap();
let mut efforts = Vec::new();
diff --git a/plugins/agent/native/src/owner/tests/scheduler.rs b/plugins/agent/native/src/owner/tests/scheduler.rs
index a1bf9257cb..adabe12323 100644
--- a/plugins/agent/native/src/owner/tests/scheduler.rs
+++ b/plugins/agent/native/src/owner/tests/scheduler.rs
@@ -58,7 +58,7 @@ async fn reopen(f: &mut Fixture) {
let deployment = f.app.source().unwrap().deployment;
f.app.shutdown().await.unwrap();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
crate::owner::tests::provide_all(&mut native).unwrap();
f.app = Application::start_from_store(
f.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/selection.rs b/plugins/agent/native/src/owner/tests/selection.rs
index 1b069f47cd..14ba6e17e0 100644
--- a/plugins/agent/native/src/owner/tests/selection.rs
+++ b/plugins/agent/native/src/owner/tests/selection.rs
@@ -198,8 +198,28 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
{
let fixture = Fixture::new("http://127.0.0.1:1/v1", None).await;
let session = fixture.create("Original execution basis").await;
+ let public = ModelInfo {
+ context_window: Some(128_000),
+ max_output_tokens: Some(4096),
+ thinking_levels: Some(vec![ThinkingLevel::Low, ThinkingLevel::High]),
+ default_thinking_level: Some(ThinkingLevel::High),
+ capabilities: Some(ModelCapabilities {
+ vision: Some(true),
+ ..Default::default()
+ }),
+ ..ModelInfo::new("fixture-model")
+ };
+ *fixture.owner.catalog.snapshot.write().await = Ok(Arc::new(
+ maka_providers::catalog::Snapshot::for_model(
+ "openai-compatible",
+ "fixture-model",
+ Some(public.clone()),
+ )
+ .unwrap(),
+ ));
let (mut source, _) = crate::model::Source::select(
fixture.config.clone(),
+ fixture.owner.catalog.clone(),
fixture.owner.credentials.clone(),
fixture.selection().await,
None,
@@ -210,6 +230,43 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
.unwrap();
let persisted = serde_json::to_string(&source.accepted).unwrap();
assert!(!persisted.contains("private-fixture-key"));
+ let stored: Value = serde_json::from_str(&persisted).unwrap();
+ assert_eq!(stored["public_model"]["maxOutputTokens"], 4096);
+ let mut missing = stored.clone();
+ missing.as_object_mut().unwrap().remove("public_model");
+ assert!(
+ serde_json::from_value::<crate::model::AcceptedModel>(missing).is_err(),
+ "captured absence must be explicit null, never inferred from a missing field"
+ );
+ let refreshed_public = ModelInfo {
+ context_window: Some(1_000_000),
+ max_output_tokens: Some(8192),
+ default_thinking_level: Some(ThinkingLevel::Low),
+ capabilities: Some(ModelCapabilities {
+ vision: Some(false),
+ ..Default::default()
+ }),
+ ..public
+ };
+ *fixture.owner.catalog.snapshot.write().await = Ok(Arc::new(
+ maka_providers::catalog::Snapshot::for_model(
+ "openai-compatible",
+ "fixture-model",
+ Some(refreshed_public),
+ )
+ .unwrap(),
+ ));
+ for prepared in [
+ source.capture().await.unwrap(),
+ source.capture_auxiliary().await.unwrap(),
+ ] {
+ assert_eq!(prepared.main_output_limit, Some(4096));
+ assert_eq!(
+ prepared.context.unwrap().model_context_window,
+ Some(128_000)
+ );
+ assert!(prepared.supports_vision);
+ }
source = crate::model::Source::restore(
fixture.config.clone(),
fixture.owner.credentials.clone(),
@@ -218,6 +275,39 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
fixture.owner.revision.clone(),
)
.unwrap();
+ let recovered = source.capture().await.unwrap();
+ assert_eq!(recovered.main_output_limit, Some(4096));
+ assert_eq!(
+ recovered
+ .options
+ .as_object()
+ .unwrap()
+ .values()
+ .cloned()
+ .collect::<Vec<_>>(),
+ vec![json!({"reasoningEffort":"high"})],
+ );
+ assert!(recovered.supports_vision);
+ let choices = models(&fixture, json!({"kind":"start","query":"fixture-model"}))
+ .await
+ .unwrap();
+ assert_eq!(choices["choices"][0]["limits"]["contextWindow"], 1_000_000);
+ assert_eq!(choices["choices"][0]["limits"]["maxOutputTokens"], 8192);
+ assert_eq!(choices["choices"][0]["defaultThinkingLevel"], "low");
+ let (_, next) = crate::model::Source::select(
+ fixture.config.clone(),
+ fixture.owner.catalog.clone(),
+ fixture.owner.credentials.clone(),
+ fixture.selection().await,
+ None,
+ "new-public-defaults".into(),
+ fixture.owner.revision.clone(),
+ )
+ .await
+ .unwrap();
+ assert_eq!(next.main_output_limit, Some(8192));
+ assert_eq!(next.context.unwrap().model_context_window, Some(1_000_000));
+ assert!(!next.supports_vision);
edit(&fixture, |changes| {
changes.name = Some("A different label".into());
changes
@@ -286,6 +376,7 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
assert!(source.capture().await.is_err());
let (current, _) = crate::model::Source::select(
fixture.config.clone(),
+ fixture.owner.catalog.clone(),
fixture.owner.credentials.clone(),
fixture.selection().await,
None,
@@ -301,6 +392,7 @@ async fn captured_execution_ignores_catalog_labels_and_other_models_but_rejects_
assert!(current.capture().await.is_err());
let (current, _) = crate::model::Source::select(
fixture.config.clone(),
+ fixture.owner.catalog.clone(),
fixture.owner.credentials.clone(),
fixture.selection().await,
None,
@@ -678,6 +770,7 @@ async fn restored_thinking_preserves_explicit_choice_and_refuses_changed_default
for level in [None, Some(High)] {
let (source, prepared) = crate::model::Source::select(
fixture.config.clone(),
+ fixture.owner.catalog.clone(),
fixture.owner.credentials.clone(),
fixture.selection().await,
level,
@@ -865,6 +958,7 @@ async fn accepted_code_mode_survives_settings_changes_and_legacy_messages_remain
.await;
let (source, prepared) = crate::model::Source::select(
fixture.config.clone(),
+ fixture.owner.catalog.clone(),
fixture.owner.credentials.clone(),
fixture.selection().await,
None,
@@ -883,6 +977,19 @@ async fn accepted_code_mode_survives_settings_changes_and_legacy_messages_remain
assert_eq!(context.context_window, Some(900_000));
assert_eq!(context.declared_window, Some(700_000));
let stored = serde_json::to_value(&source.accepted).unwrap();
+ assert!(stored.as_object().unwrap().contains_key("public_model"));
+ assert!(stored["public_model"].is_null());
+ *fixture.owner.catalog.snapshot.write().await = Ok(Arc::new(
+ maka_providers::catalog::Snapshot::for_model(
+ "openai-compatible",
+ "fixture-model",
+ Some(ModelInfo {
+ max_output_tokens: Some(4096),
+ ..ModelInfo::new("fixture-model")
+ }),
+ )
+ .unwrap(),
+ ));
edit(&fixture, |changes| {
changes.model_overrides = Patch::Set(BTreeMap::from([(
"fixture-model".into(),
@@ -912,6 +1019,7 @@ async fn accepted_code_mode_survives_settings_changes_and_legacy_messages_remain
);
let (next, prepared) = crate::model::Source::select(
fixture.config.clone(),
+ fixture.owner.catalog.clone(),
fixture.owner.credentials.clone(),
fixture.selection().await,
None,
diff --git a/plugins/agent/native/src/owner/tests/service_tools.rs b/plugins/agent/native/src/owner/tests/service_tools.rs
index 391f3b352e..2de72bba3f 100644
--- a/plugins/agent/native/src/owner/tests/service_tools.rs
+++ b/plugins/agent/native/src/owner/tests/service_tools.rs
@@ -2699,7 +2699,7 @@ async fn editing_policy_and_code_mode_survive_actual_host_restart_with_original_
drop(raw);
let mut native = NativeRegistry::default();
provide_all(&mut native).unwrap();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
let restored = Application::start_from_store(
fixture.store.clone(), deployment, native, fixture.issuer.clone(),
Arc::new(Grant(fixture.issuer.clone(), Some(policy))), Some(maka_host_js::factory(Default::default())),
@@ -2757,7 +2757,7 @@ async fn queued_messages_resume_with_equivalent_native_service_targets_after_res
drop(raw);
let mut native = NativeRegistry::default();
provide_all(&mut native).unwrap();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
let restored = Application::start_from_store(
fixture.store.clone(), deployment, native, fixture.issuer.clone(),
Arc::new(Grant(fixture.issuer.clone(), Some(policy))), Some(maka_host_js::factory(Default::default())),
diff --git a/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs b/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs
index 1a6bfd60d5..c2a6d3ba0e 100644
--- a/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs
+++ b/plugins/agent/native/src/owner/tests/service_tools/storm/combined.rs
@@ -220,7 +220,7 @@ struct LocalOwner {
fn providers(setup: &Setup, capture: Arc<StdMutex<Option<Arc<Owner>>>>) -> NativeRegistry {
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
setup.provide(&mut native);
native
.insert(
@@ -228,7 +228,7 @@ fn providers(setup: &Setup, capture: Arc<StdMutex<Option<Arc<Owner>>>>) -> Nativ
Arc::new(move |context, configuration| {
let capture = capture.clone();
Box::pin(async move {
- let owner = Owner::open(context, configuration).await.unwrap();
+ let owner = Owner::open(context, configuration, crate::owner::tests::bundled_catalog()).await.unwrap();
*capture.lock().unwrap() = Some(owner.clone());
let service = Arc::new(Agent(owner));
NativeActivation {
diff --git a/plugins/agent/native/src/owner/tests/settings.rs b/plugins/agent/native/src/owner/tests/settings.rs
index 634da025f5..c9ee0590a1 100644
--- a/plugins/agent/native/src/owner/tests/settings.rs
+++ b/plugins/agent/native/src/owner/tests/settings.rs
@@ -764,7 +764,7 @@ async fn saved_model_survives_restart_and_resolves_locally_at_the_current_revisi
assert_eq!(current["choice"]["limits"]["contextWindow"], 100000);
assert_eq!(overrides["fixture-model"].max_output_tokens, Some(4000));
let (_, prepared) = crate::model::Source::select(
- f.config.clone(), f.owner.credentials.clone(), f.selection().await,
+ f.config.clone(), f.owner.catalog.clone(), f.owner.credentials.clone(), f.selection().await,
None, session.clone(), f.owner.revision.clone(),
).await.unwrap();
assert_eq!(prepared.main_output_limit, Some(4000));
diff --git a/plugins/agent/native/src/owner/tests/skill_preparation.rs b/plugins/agent/native/src/owner/tests/skill_preparation.rs
index 3c84e4c1f7..35b0dab17a 100644
--- a/plugins/agent/native/src/owner/tests/skill_preparation.rs
+++ b/plugins/agent/native/src/owner/tests/skill_preparation.rs
@@ -199,7 +199,7 @@ async fn skill_preparation_survives_host_restart_without_preparing_again() {
document(dir.path(), "CHANGED METADATA", "CHANGED INSTRUCTIONS");
let mut native = NativeRegistry::default();
crate::owner::tests::provide_all(&mut native).unwrap();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
// Restore has no native prepare grant: it must use the original durable message.
let resumed = Application::start_from_store(f.store.clone(), deployment, native,
f.issuer.clone(), Arc::new(Grant(f.issuer.clone(), None)), Some(maka_host_js::factory(Default::default()))).await.unwrap();
diff --git a/plugins/agent/native/src/owner/tests/terminal.rs b/plugins/agent/native/src/owner/tests/terminal.rs
index 8f60477f05..3bc0c6ba03 100644
--- a/plugins/agent/native/src/owner/tests/terminal.rs
+++ b/plugins/agent/native/src/owner/tests/terminal.rs
@@ -717,7 +717,7 @@ async fn terminal_styled_display_survives_retirement_cancel_and_host_reopen_beyo
f.store.shutdown().await.unwrap();
f.store = Arc::new(EventLog::for_root(root).await.unwrap());
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
provide_all(&mut native).unwrap();
let tooling = service_tools::Setup::processes();
f.app = Application::start_from_store(f.store.clone(), deployment, native, f.issuer.clone(),
diff --git a/plugins/agent/native/src/owner/tests/todo.rs b/plugins/agent/native/src/owner/tests/todo.rs
index d7c493ce69..20bed3e8da 100644
--- a/plugins/agent/native/src/owner/tests/todo.rs
+++ b/plugins/agent/native/src/owner/tests/todo.rs
@@ -64,7 +64,7 @@ pub(super) async fn reopen(f: &mut Fixture) {
let deployment = f.app.source().unwrap().deployment;
f.app.shutdown().await.unwrap();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
provide_all(&mut native).unwrap();
f.app = Application::start_from_store(
f.store.clone(),
@@ -188,7 +188,7 @@ async fn todo_archived_result_next_is_readable_by_the_selected_native_read_tool(
let deployment = f.app.source().unwrap().deployment;
f.app.shutdown().await.unwrap();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
provide_all(&mut native).unwrap();
f.app = Application::start_from_store(
f.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/workhub.rs b/plugins/agent/native/src/owner/tests/workhub.rs
index 5c04794503..7355bcf072 100644
--- a/plugins/agent/native/src/owner/tests/workhub.rs
+++ b/plugins/agent/native/src/owner/tests/workhub.rs
@@ -308,7 +308,7 @@ async fn activate_worker_mode(
let behavior = behavior.clone();
Box::pin(async move {
let owner = Arc::new(Worker(
- Agent(Owner::open(context, settings).await.unwrap()),
+ Agent(Owner::open(context, settings, crate::owner::tests::bundled_catalog()).await.unwrap()),
behavior,
));
NativeActivation {
@@ -326,7 +326,7 @@ async fn activate_worker_mode(
)
.unwrap();
}
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
fixture.app = Application::start_from_store(
fixture.store.clone(),
source.deployment,
diff --git a/plugins/agent/native/src/owner/tests/workspaces.rs b/plugins/agent/native/src/owner/tests/workspaces.rs
index 6eaafe4032..a8571c42c5 100644
--- a/plugins/agent/native/src/owner/tests/workspaces.rs
+++ b/plugins/agent/native/src/owner/tests/workspaces.rs
@@ -42,7 +42,7 @@ pub(super) async fn publish_with_policy(
.await
.unwrap();
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
crate::owner::tests::provide_all(&mut native).unwrap();
fixture.app = Application::start_from_store(
fixture.store.clone(),
diff --git a/plugins/agent/native/src/owner/tests/worktrees.rs b/plugins/agent/native/src/owner/tests/worktrees.rs
index 8fb1366743..a2ec815b47 100644
--- a/plugins/agent/native/src/owner/tests/worktrees.rs
+++ b/plugins/agent/native/src/owner/tests/worktrees.rs
@@ -141,7 +141,7 @@ async fn isolated_native_files_and_immutable_patch_survive_successor_and_restart
let deployment=f.app.source().unwrap().deployment;
f.app.shutdown().await.unwrap();
let mut native=NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
crate::owner::tests::provide_all(&mut native).unwrap();
f.app=Application::start_from_store(f.store.clone(),deployment,native,f.issuer.clone(),Arc::new(Grant(f.issuer.clone(),None)),Some(maka_host_js::factory(Default::default()))).await.unwrap();
f.app.ready().await.unwrap();
@@ -314,7 +314,7 @@ async fn large_completed_isolated_patch_is_complete_and_immutable_after_successo
let deployment = f.app.source().unwrap().deployment;
f.app.shutdown().await?;
let mut native = NativeRegistry::default();
- maka_settings_native::provide(&mut native).unwrap();
+ maka_settings_native::provide(&mut native, crate::owner::tests::bundled_catalog()).unwrap();
crate::owner::tests::provide_all(&mut native).unwrap();
f.app = Application::start_from_store(
f.store.clone(),
diff --git a/plugins/agent/native/src/workhub.rs b/plugins/agent/native/src/workhub.rs
index 41128aab03..3b7a8c9c80 100644
--- a/plugins/agent/native/src/workhub.rs
+++ b/plugins/agent/native/src/workhub.rs
@@ -66,10 +66,14 @@ fn namespace() -> Namespace {
Namespace::new(IMPLEMENTATION, maka_plugin_core::scope::Scope::Profile)
.expect("built-in WorkHub namespace")
}
-pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
+pub fn provide(
+ registry: &mut NativeRegistry,
+ catalog: Arc<maka_providers::catalog::Catalog>,
+) -> Result<(), String> {
registry.insert(
maka_agent_api::workhub::IMPLEMENTATION.into(),
- Arc::new(|context, settings| {
+ Arc::new(move |context, settings| {
+ let catalog = catalog.clone();
Box::pin(async move {
let prepared = tasks::prepare(&context, settings);
let (settings, tasks, additional, link) = match prepared {
@@ -84,11 +88,11 @@ pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
match Owner::open_behavior(
context,
settings,
- IMPLEMENTATION,
Some(crate::owner::BuiltinBehavior::WorkHub),
additional,
tasks.bindings(),
Some(tasks.graphs.clone()),
+ catalog,
)
.await
{
diff --git a/plugins/agent/native/src/workhub/presets.rs b/plugins/agent/native/src/workhub/presets.rs
index 7328592c43..098fbdcd46 100644
--- a/plugins/agent/native/src/workhub/presets.rs
+++ b/plugins/agent/native/src/workhub/presets.rs
@@ -64,6 +64,11 @@ impl Hub {
.get(&preset.worker)
.and_then(Option::as_ref)
.ok_or("workhub_worker_unavailable")?;
+ let public_guard = self.agent.0.catalog.snapshot.read().await;
+ let snapshot = public_guard
+ .as_ref()
+ .map_err(|_| "workhub_configuration_unavailable")?
+ .clone();
let connection = self
.agent
.0
@@ -77,7 +82,8 @@ impl Hub {
connection.connection_id == preset.model.connection_id && connection.enabled
})
.ok_or("workhub_preset_connection_unavailable")?;
- if maka_providers::definition(&connection.provider).is_err() {
+ drop(public_guard);
+ if maka_providers::definition(&connection.provider, snapshot).is_err() {
return Err("workhub_preset_connection_unavailable".into());
}
if !connection
diff --git a/plugins/agent/providers/src/api.rs b/plugins/agent/providers/src/api.rs
index 25b44e041d..9448bb898d 100644
--- a/plugins/agent/providers/src/api.rs
+++ b/plugins/agent/providers/src/api.rs
@@ -57,8 +57,11 @@ pub struct ApiProviders;
impl ApiProviders {
/// A compiled API implementation for a native application owner. Availability
/// does not activate legacy contributions or grant execution authority.
- pub fn definition(id: &str) -> Result<Definition, String> {
- Self::provider(id)?.definition()
+ pub fn definition(
+ id: &str,
+ snapshot: Arc<crate::catalog::Snapshot>,
+ ) -> Result<Definition, String> {
+ Self::provider(id, snapshot)?.definition()
}
/// Selectable model metadata, checked by the same preparation policy as
@@ -66,11 +69,12 @@ impl ApiProviders {
/// configured default is an error, never silently replaced by another level.
pub fn model_metadata(
id: &str,
+ snapshot: Arc<crate::catalog::Snapshot>,
connection: Connection,
model: ModelInfo,
overrides: Option<ModelOverride>,
) -> Result<ModelMetadata, String> {
- let provider = Self::provider(id)?;
+ let provider = Self::provider(id, snapshot)?;
let mut metadata = provider.model_metadata(model.clone(), overrides.as_ref());
let mut request = Resolve {
connection,
@@ -88,7 +92,7 @@ impl ApiProviders {
Ok(metadata)
}
- fn provider(id: &str) -> Result<ApiProvider, String> {
+ fn provider(id: &str, snapshot: Arc<crate::catalog::Snapshot>) -> Result<ApiProvider, String> {
let (id, facts) = facts::all()
.map_err(|error| error.to_string())?
.iter()
@@ -99,6 +103,7 @@ impl ApiProviders {
Ok(ApiProvider {
id: id.as_str(),
facts,
+ snapshot,
})
}
@@ -117,6 +122,7 @@ impl ApiProviders {
struct ApiProvider {
id: &'static str,
facts: &'static ProviderFacts,
+ snapshot: Arc<crate::catalog::Snapshot>,
}
#[derive(Deserialize, Serialize, schemars::JsonSchema)]
@@ -179,7 +185,7 @@ impl ApiProvider {
}
fn model_metadata(&self, model: ModelInfo, overrides: Option<&ModelOverride>) -> ModelMetadata {
- let reported = metadata::enrich(self.id, self.facts, model);
+ let reported = metadata::enrich(&self.snapshot, self.id, model);
let info =
overrides.map_or_else(|| reported.clone(), |overrides| overrides.apply(&reported));
let thinking_levels = overrides
@@ -216,7 +222,7 @@ impl ApiProvider {
if level.is_some_and(|level| !thinking_levels.contains(&level)) {
return Err(Error::UnsupportedThinkingLevel);
}
- let options = options::resolve(&selection, self.facts, level, &route)?;
+ let options = options::resolve(&selection, self.facts, &thinking_levels, level, &route)?;
let output = output::resolve(&selection, route.wire, &options)?;
let adapter = request
.overrides
diff --git a/plugins/agent/providers/src/api/effects.rs b/plugins/agent/providers/src/api/effects.rs
index 8c3a53a2b8..c22dabb131 100644
--- a/plugins/agent/providers/src/api/effects.rs
+++ b/plugins/agent/providers/src/api/effects.rs
@@ -17,7 +17,7 @@
* under the License.
*/
-use super::{ApiProvider, Configuration, Selection, metadata, route};
+use super::{ApiProvider, Configuration, Selection, route};
use maka_agent_facts::configuration::ModelInfo;
use maka_agent_ports::model::Credentials;
use maka_agent_ports::model::ProviderKind;
@@ -34,11 +34,7 @@ pub(super) async fn discover(
request: Discovery,
context: Context,
) -> Result<Vec<ModelInfo>, Error> {
- let inventory = super::discovery::discover(provider, request, context).await?;
- Ok(inventory
- .into_iter()
- .map(|model| metadata::enrich(provider.id, provider.facts, model))
- .collect())
+ super::discovery::discover(provider, request, context).await
}
pub(super) async fn verify(
diff --git a/plugins/agent/providers/src/api/metadata.rs b/plugins/agent/providers/src/api/metadata.rs
index 46887bb745..f45b8af4c5 100644
--- a/plugins/agent/providers/src/api/metadata.rs
+++ b/plugins/agent/providers/src/api/metadata.rs
@@ -17,99 +17,11 @@
* under the License.
*/
-use crate::facts::ProviderFacts;
-use maka_agent_facts::configuration::ModelCapabilities;
+use crate::catalog::Snapshot;
use maka_agent_facts::configuration::ModelInfo;
-use maka_agent_facts::execution::ThinkingLevel;
-/// Account facts win field by field; explicit false and empty choices are facts.
-pub(super) fn enrich(provider: &str, facts: &ProviderFacts, mut model: ModelInfo) -> ModelInfo {
- if let Some(known) = facts.models.get(&model.id) {
- let metadata = &known.metadata;
- macro_rules! fallback {
- ($($field:ident),* $(,)?) => { $(if model.$field.is_none() {
- model.$field = metadata.$field.clone();
- })* };
- }
- fallback!(
- display_name,
- description,
- context_window,
- input_limit,
- max_output_tokens,
- knowledge_cutoff,
- structured_output,
- last_updated,
- modalities
- );
- if model.thinking_levels.is_none() {
- model.thinking_levels = Some(known.entry.thinking_levels.clone());
- if provider == "openai"
- && known
- .metadata
- .thinking_options
- .as_ref()
- .and_then(|options| options.efforts.as_ref())
- .is_some_and(|efforts| efforts.iter().any(|effort| effort == "medium"))
- {
- model.default_thinking_level =
- model.default_thinking_level.or(Some(ThinkingLevel::Medium));
- model.supports_reasoning_summary = model.supports_reasoning_summary.or(Some(true));
- }
- }
- let mut defaults = metadata.capabilities.unwrap_or_default();
- defaults.chat = defaults.chat.or(Some(known.entry.can_use_as_chat_default));
- defaults.vision = defaults.vision.or(Some(known.entry.supports_vision));
- let caps = model.capabilities.get_or_insert_default();
- fill(caps, defaults);
- }
- if provider == "openai" || provider == "anthropic" {
- let caps = model.capabilities.get_or_insert_default();
- caps.web_search = caps.web_search.or(Some(true));
- }
- if provider == "anthropic" && claude_vision(&model.id) {
- let caps = model.capabilities.get_or_insert_default();
- caps.vision = caps.vision.or(Some(true));
- }
- model
-}
-
-fn claude_vision(id: &str) -> bool {
- let Some(mut tail) = id.strip_prefix("claude-") else {
- return false;
- };
- loop {
- for family in ["opus", "sonnet", "haiku", "fable"] {
- if let Some(rest) = tail.strip_prefix(family)
- && rest
- .chars()
- .next()
- .is_none_or(|c| !c.is_ascii_alphanumeric() && c != '_')
- {
- return true;
- }
- }
- let Some((part, rest)) = tail.split_once('-') else {
- return false;
- };
- if part.is_empty() || !part.chars().all(|c| c.is_ascii_digit() || c == '.') {
- return false;
- }
- tail = rest;
- }
-}
-
-fn fill(caps: &mut ModelCapabilities, defaults: ModelCapabilities) {
- macro_rules! fallback {
- ($($field:ident),* $(,)?) => { $(caps.$field = caps.$field.or(defaults.$field);)* };
- }
- fallback!(
- chat,
- vision,
- reasoning,
- function_calling,
- parallel_tool_calls,
- image_generation,
- web_search
- );
+/// The same immutable public base fills account omissions for selection and
+/// execution. Explicit account false/empty values remain authoritative.
+pub(super) fn enrich(snapshot: &Snapshot, provider: &str, model: ModelInfo) -> ModelInfo {
+ snapshot.resolve(provider, model)
}
diff --git a/plugins/agent/providers/src/api/options.rs b/plugins/agent/providers/src/api/options.rs
index c45e68f7c5..230d921088 100644
--- a/plugins/agent/providers/src/api/options.rs
+++ b/plugins/agent/providers/src/api/options.rs
@@ -34,37 +34,29 @@ mod relay;
pub(super) fn resolve(
row: &Selection<'_>,
facts: &ProviderFacts,
+ thinking_levels: &[ThinkingLevel],
thinking_level: Option<ThinkingLevel>,
route: &Route,
) -> Result<Value, Error> {
- let model = row.model.id.as_str();
let wire = route.wire;
- let known = facts.models.get(model);
- let stored = Some(row.model);
let parallel = row
- .overrides
- .and_then(|value| value.capabilities.as_ref())
- .and_then(|value| value.parallel_tool_calls)
- .or_else(|| {
- stored
- .and_then(|item| item.capabilities)
- .and_then(|caps| caps.parallel_tool_calls)
- })
- .or_else(|| {
- known
- .and_then(|item| item.metadata.capabilities)
- .and_then(|caps| caps.parallel_tool_calls)
- });
+ .model
+ .capabilities
+ .and_then(|caps| caps.parallel_tool_calls);
match &route.kind {
- ProviderKind::Anthropic => Ok(anthropic(facts, model, thinking_level, parallel)),
+ ProviderKind::Anthropic => Ok(anthropic(
+ facts,
+ row.model,
+ thinking_levels,
+ thinking_level,
+ parallel,
+ )),
ProviderKind::OpenaiResponses if row.provider == "openai-responses-compatible" => {
relay::responses(row, thinking_level, parallel.unwrap_or(true))
}
ProviderKind::OpenaiChat | ProviderKind::OpenaiResponses => {
// An explicit account capability can opt out; it never gates rendering.
- let summary = stored
- .and_then(|model| model.supports_reasoning_summary)
- .unwrap_or(false);
+ let summary = row.model.supports_reasoning_summary.unwrap_or(false);
Ok(openai(
wire,
thinking_level,
@@ -73,7 +65,7 @@ pub(super) fn resolve(
))
}
ProviderKind::OpenaiCompatible { name } => {
- compatible::chat(row.provider, name, model, thinking_level, parallel)
+ Ok(compatible::chat(name, thinking_level, parallel))
}
ProviderKind::OpenResponses(contract) => {
if parallel == Some(false) {
@@ -98,17 +90,6 @@ pub(super) fn resolve(
}
}
-fn default_medium(facts: &ProviderFacts, model: &str) -> bool {
- let family = model.rsplit('/').next().unwrap_or(model);
- facts.models.get(family).is_some_and(|item| {
- item.metadata
- .thinking_options
- .as_ref()
- .and_then(|thinking| thinking.efforts.as_ref())
- .is_some_and(|efforts| efforts.iter().any(|effort| effort == "medium"))
- })
-}
-
fn openai(wire: Wire, level: Option<ThinkingLevel>, summary: bool, parallel: bool) -> Value {
let mut options = json!({"store": false, "parallelToolCalls": parallel});
if let Some(level) = level {
@@ -125,42 +106,35 @@ fn openai(wire: Wire, level: Option<ThinkingLevel>, summary: bool, parallel: boo
fn anthropic(
facts: &ProviderFacts,
- model: &str,
+ model: &maka_agent_facts::configuration::ModelInfo,
+ levels: &[ThinkingLevel],
level: Option<ThinkingLevel>,
parallel: Option<bool>,
) -> Value {
- let family = claude_family(model);
- let known = facts.models.get(model);
+ let family = claude_family(&model.id);
+ let known = facts.models.get(&model.id);
let family_facts = facts.models.get(family.as_ref());
let metadata = known.map(|item| &item.metadata);
let family_metadata = family_facts.map(|item| &item.metadata);
- let thinking = metadata
+ // The SDK's off encoding and adaptive wire support remain compiled policy.
+ let off_behavior = metadata
.and_then(|metadata| metadata.thinking_options.as_ref())
- .or_else(|| family_metadata.and_then(|metadata| metadata.thinking_options.as_ref()));
+ .and_then(|thinking| thinking.off_behavior)
+ .or_else(|| {
+ family_metadata
+ .and_then(|metadata| metadata.thinking_options.as_ref())
+ .and_then(|thinking| thinking.off_behavior)
+ });
let bare_legacy = matches!(family.as_ref(), "claude-opus-4" | "claude-sonnet-4");
let visible = family.starts_with("claude-")
- && (thinking.is_some_and(|thinking| {
- thinking.toggle == Some(true)
- || thinking
- .efforts
- .as_ref()
- .is_some_and(|efforts| !efforts.is_empty())
- }) || metadata
- .and_then(|metadata| metadata.capabilities)
- .and_then(|caps| caps.reasoning)
- == Some(true)
- || family_metadata
- .and_then(|metadata| metadata.capabilities)
- .and_then(|caps| caps.reasoning)
- == Some(true)
- || bare_legacy);
+ && (model.capabilities.and_then(|caps| caps.reasoning) == Some(true)
+ || levels.iter().any(|level| *level != ThinkingLevel::Off));
let mut options = json!({"cacheControl":{"type":"ephemeral"}});
if parallel == Some(false) {
options["disableParallelToolUse"] = json!(true);
}
if level == Some(ThinkingLevel::Off)
- && thinking.and_then(|thinking| thinking.off_behavior)
- == Some(ThinkingOffBehavior::AnthropicThinkingDisabled)
+ && off_behavior == Some(ThinkingOffBehavior::AnthropicThinkingDisabled)
{
options["thinking"] = json!({"type":"disabled"});
} else if visible {
diff --git a/plugins/agent/providers/src/api/options/compatible.rs b/plugins/agent/providers/src/api/options/compatible.rs
index 0ce1f439ae..bc23d576b5 100644
--- a/plugins/agent/providers/src/api/options/compatible.rs
+++ b/plugins/agent/providers/src/api/options/compatible.rs
@@ -17,24 +17,9 @@
* under the License.
*/
-use super::{Error, ThinkingLevel, Value, json};
-use crate::facts::provider_facts;
+use super::{ThinkingLevel, Value, json};
-pub(super) fn chat(
- provider: &str,
- name: &str,
- model: &str,
- level: Option<ThinkingLevel>,
- parallel: Option<bool>,
-) -> Result<Value, Error> {
- let level = if provider == "openrouter" {
- // Its own catalog controls the default; a similarly named OpenAI model
- // does not establish the routed model's enabled state or effort.
- level
- } else {
- let facts = provider_facts("openai").map_err(|_| Error::ProviderFactsUnavailable)?;
- level.or_else(|| super::default_medium(facts, model).then_some(ThinkingLevel::Medium))
- };
+pub(super) fn chat(name: &str, level: Option<ThinkingLevel>, parallel: Option<bool>) -> Value {
let mut options = json!({});
if let Some(level) = level {
options["reasoningEffort"] = match level {
@@ -46,11 +31,11 @@ pub(super) fn chat(
if let Some(parallel) = parallel {
options["parallel_tool_calls"] = json!(parallel);
}
- Ok(if options.as_object().unwrap().is_empty() {
+ if options.as_object().unwrap().is_empty() {
json!({})
} else {
json!({camel_case(name): options})
- })
+ }
}
// Match model-factory.ts and the SDK alias: /[_-]([a-z])/g.
@@ -72,39 +57,20 @@ mod tests {
use super::*;
#[test]
- fn compatible_policy_preserves_explicit_effort_and_only_defaults_known_families() {
+ fn compatible_policy_encodes_only_resolved_effort_and_declared_parallel_support() {
for (level, wire) in [(ThinkingLevel::High, "high"), (ThinkingLevel::Max, "max")] {
assert_eq!(
- chat(
- "openai-compatible",
- "custom-chat_relay",
- "unknown",
- Some(level),
- None
- )
- .unwrap(),
+ chat("custom-chat_relay", Some(level), None),
json!({"customChatRelay":{"reasoningEffort":wire}}),
);
}
+ assert_eq!(chat("relay", None, None), json!({}));
assert_eq!(
- chat("openai-compatible", "relay", "unknown", None, None).unwrap(),
- json!({})
+ chat("custom-chat", None, Some(false)),
+ json!({"customChat":{"parallel_tool_calls":false}}),
);
- for model in ["gpt-5.2", "relay/gpt-5.2"] {
- assert_eq!(
- chat("openai-compatible", "custom-chat", model, None, Some(false)).unwrap(),
- json!({"customChat":{"reasoningEffort":"medium","parallel_tool_calls":false}}),
- );
- }
assert_eq!(
- chat(
- "openai-compatible",
- "relay",
- "gpt-5.2",
- Some(ThinkingLevel::Off),
- Some(true)
- )
- .unwrap(),
+ chat("relay", Some(ThinkingLevel::Off), Some(true)),
json!({"relay":{"reasoningEffort":"none","parallel_tool_calls":true}}),
);
assert_eq!(camel_case("relay--chat_A-2_b"), "relay-Chat_A-2B");
diff --git a/plugins/agent/providers/src/api/options/relay.rs b/plugins/agent/providers/src/api/options/relay.rs
index a0e896cbd2..c1a7764957 100644
--- a/plugins/agent/providers/src/api/options/relay.rs
+++ b/plugins/agent/providers/src/api/options/relay.rs
@@ -18,7 +18,6 @@
*/
use super::{Error, Selection, ThinkingLevel, Value, Wire};
-use crate::facts::provider_facts;
pub(super) fn responses(
row: &Selection<'_>,
@@ -26,13 +25,10 @@ pub(super) fn responses(
parallel: bool,
) -> Result<Value, Error> {
let model = row.model.id.as_str();
- // Relay catalogs may have no models. Defaults and summaries follow canonical
- // OpenAI family facts, never the relay's declared thinking levels.
- let facts = provider_facts("openai").map_err(|_| Error::ProviderFactsUnavailable)?;
let mut options = super::openai(
Wire::OpenaiResponses,
- level.or_else(|| super::default_medium(facts, model).then_some(ThinkingLevel::Medium)),
- super::default_medium(facts, model),
+ level,
+ row.model.supports_reasoning_summary.unwrap_or(false),
parallel,
);
options["openai"]["forceReasoning"] = Value::Bool(true);
diff --git a/plugins/agent/providers/src/lib.rs b/plugins/agent/providers/src/lib.rs
index 0126c052ca..dfbcd9754d 100644
--- a/plugins/agent/providers/src/lib.rs
+++ b/plugins/agent/providers/src/lib.rs
@@ -27,13 +27,14 @@ mod facts;
/// code or change the provider scope accepted by a native application.
pub fn definition(
identity: &maka_agent_facts::provider::Identity,
+ snapshot: std::sync::Arc<catalog::Snapshot>,
) -> Result<maka_agent_ports::provider::Definition, String> {
use maka_plugin_core::scope::Scope;
if identity.scope != Scope::Profile || identity.package_id != identity.entry_id {
return Err("provider is unavailable".into());
}
match identity.package_id.as_str() {
- api::ID => api::ApiProviders::definition(&identity.name),
+ api::ID => api::ApiProviders::definition(&identity.name, snapshot),
codex::ID if identity.name == "chatgpt" => codex::Codex::default()
.definition()
.map_err(|e| e.to_string()),
diff --git a/plugins/agent/providers/tests/api.rs b/plugins/agent/providers/tests/api.rs
index 27c8f06cd7..c4013d6eb6 100644
--- a/plugins/agent/providers/tests/api.rs
+++ b/plugins/agent/providers/tests/api.rs
@@ -40,8 +40,12 @@ mod effects;
#[path = "api/policy.rs"]
mod policy;
+fn snapshot() -> std::sync::Arc<maka_providers::catalog::Snapshot> {
+ std::sync::Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap())
+}
+
fn binding(name: &str) -> Definition {
- ApiProviders::definition(name).unwrap()
+ ApiProviders::definition(name, snapshot()).unwrap()
}
fn request(binding: &Definition, id: &str, overrides: ModelOverride) -> Resolve {
let configuration = binding
@@ -212,10 +216,9 @@ async fn account_metadata_and_reply_capacity_survive_plugin_policy_resolution()
let input = request(&openai, "gpt-5.2", ModelOverride::default());
let model = openai.prepare(input.clone()).await.unwrap();
assert_eq!(
- model.provider_options["openai"]["reasoningEffort"],
- "medium"
+ model.provider_options,
+ json!({"openai":{"store":false,"parallelToolCalls":true}})
);
- assert_eq!(model.provider_options["openai"]["reasoningSummary"], "auto");
let mut account = input;
account.model.thinking_levels = Some(vec![High]);
let model = openai.prepare(account).await.unwrap();
@@ -272,6 +275,7 @@ async fn catalog_metadata_and_execution_share_provider_facts_and_explicit_overri
let mut input = request(&binding, "gpt-5.2", overrides.clone());
let metadata = ApiProviders::model_metadata(
"openai",
+ snapshot(),
input.connection.clone(),
reported.clone(),
Some(overrides),
@@ -300,6 +304,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
);
let metadata = ApiProviders::model_metadata(
"anthropic-compatible",
+ snapshot(),
input.connection.clone(),
input.model.clone(),
input.overrides.clone(),
@@ -327,6 +332,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
input.overrides.as_mut().unwrap().default_thinking_level = Some(High);
let metadata = ApiProviders::model_metadata(
"anthropic-compatible",
+ snapshot(),
input.connection.clone(),
input.model.clone(),
input.overrides.clone(),
@@ -347,6 +353,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
assert!(
ApiProviders::model_metadata(
"anthropic-compatible",
+ snapshot(),
input.connection.clone(),
input.model.clone(),
input.overrides.clone(),
@@ -357,6 +364,7 @@ async fn selectable_thinking_choices_respect_native_encoder_and_invalid_default(
assert!(
ApiProviders::model_metadata(
"openai-compatible",
+ snapshot(),
input.connection,
input.model,
input.overrides
diff --git a/plugins/agent/providers/tests/api/discovery.rs b/plugins/agent/providers/tests/api/discovery.rs
index 4b93f19035..a06fa02a90 100644
--- a/plugins/agent/providers/tests/api/discovery.rs
+++ b/plugins/agent/providers/tests/api/discovery.rs
@@ -183,6 +183,7 @@ async fn openrouter_discovery_preserves_reported_efforts_defaults_and_explicit_p
resolve.model = model.clone();
let metadata = ApiProviders::model_metadata(
"openrouter",
+ snapshot(),
resolve.connection.clone(),
resolve.model.clone(),
resolve.overrides.clone(),
diff --git a/plugins/agent/providers/tests/api/effects.rs b/plugins/agent/providers/tests/api/effects.rs
index 9e013896f3..a211598eb8 100644
--- a/plugins/agent/providers/tests/api/effects.rs
+++ b/plugins/agent/providers/tests/api/effects.rs
@@ -210,16 +210,19 @@ async fn discovery_survives_persistence_and_verification_does_not_require_stream
let persisted: Vec<ModelInfo> =
serde_json::from_slice(&serde_json::to_vec(&inventory).unwrap()).unwrap();
input.model = persisted.into_iter().find(|model| model.id == id).unwrap();
+ assert_eq!(
+ serde_json::to_value(&input.model).unwrap(),
+ json!({"id":id})
+ );
let model = binding.prepare(input.clone()).await.unwrap();
if name == "openai" {
assert_eq!(
- model.provider_options["openai"]["reasoningEffort"],
- "medium"
+ model.provider_options,
+ json!({"openai":{"store":false,"parallelToolCalls":true}})
);
- assert_eq!(model.provider_options["openai"]["reasoningSummary"], "auto");
} else {
- assert_eq!(model.info.capabilities.unwrap().vision, Some(true));
- input.model.capabilities.as_mut().unwrap().vision = Some(false);
+ assert_eq!(model.info.capabilities, None);
+ input.model.capabilities.get_or_insert_default().vision = Some(false);
assert_eq!(
binding
.prepare(input.clone())
@@ -312,7 +315,7 @@ async fn discovery_survives_persistence_and_verification_does_not_require_stream
#[tokio::test]
async fn compiled_definition_authenticates_without_legacy_registration_or_secret_diagnostics() {
use maka_agent_ports::provider::authentication::Authenticate;
- let definition = ApiProviders::definition("openai-compatible").unwrap();
+ let definition = ApiProviders::definition("openai-compatible", snapshot()).unwrap();
let transport = Arc::new(Exchange::default());
let context = Context {
transport: transport.clone(),
diff --git a/plugins/agent/providers/tests/api/policy.rs b/plugins/agent/providers/tests/api/policy.rs
index 924bf4cda9..5c5a543bf1 100644
--- a/plugins/agent/providers/tests/api/policy.rs
+++ b/plugins/agent/providers/tests/api/policy.rs
@@ -68,6 +68,7 @@ async fn kimi_catalog_choices_and_supported_default_remain_selectable() {
);
let metadata = ApiProviders::model_metadata(
"kimi-coding-plan",
+ snapshot(),
input.connection.clone(),
input.model.clone(),
input.overrides.clone(),
diff --git a/plugins/settings/native/src/connections.rs b/plugins/settings/native/src/connections.rs
index 5bc3ee34d0..e7a4b0641b 100644
--- a/plugins/settings/native/src/connections.rs
+++ b/plugins/settings/native/src/connections.rs
@@ -23,18 +23,28 @@ use maka_agent_facts::provider::Identity;
use maka_agent_ports::provider::Definition;
use maka_config::ConfigurationStore;
use maka_plugin_core::scope::Scope;
+use maka_providers::catalog::Snapshot;
use serde::Serialize;
use serde_json::Value;
+use std::sync::Arc;
pub(crate) fn encode(value: impl Serialize) -> Result<Value, String> {
let value = serde_json::to_value(value).map_err(|_| "settings_output_invalid")?;
Ok(value)
}
-pub(crate) fn definition(identity: &Identity) -> Result<Definition, String> {
- maka_providers::definition(identity).map_err(|_| "settings_provider_unavailable".into())
+pub(crate) fn definition(
+ identity: &Identity,
+ snapshot: Arc<Snapshot>,
+) -> Result<Definition, String> {
+ maka_providers::definition(identity, snapshot)
+ .map_err(|_| "settings_provider_unavailable".into())
}
-pub(crate) async fn page(store: &ConfigurationStore, input: Value) -> Result<Value, String> {
+pub(crate) async fn page(
+ store: &ConfigurationStore,
+ input: Value,
+ snapshot: Arc<Snapshot>,
+) -> Result<Value, String> {
let input: ConnectionsRead =
serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
let catalog = store
@@ -59,7 +69,7 @@ pub(crate) async fn page(store: &ConfigurationStore, input: Value) -> Result<Val
let connections = catalog.connections[offset..end]
.iter()
.map(|row| ConnectionSummary {
- editable: definition(&row.provider).is_ok(),
+ editable: definition(&row.provider, snapshot.clone()).is_ok(),
basis: ConnectionVersionBasis {
connection_id: row.connection_id.clone(),
revision: row.revision,
@@ -117,7 +127,7 @@ pub(crate) async fn detail(store: &ConfigurationStore, input: Value) -> Result<V
}),
}
}
-pub(crate) fn providers(input: Value) -> Result<Value, String> {
+pub(crate) fn providers(input: Value, snapshot: Arc<Snapshot>) -> Result<Value, String> {
let input: ProvidersRead =
serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
let mut identities = vec![Identity {
@@ -151,7 +161,7 @@ pub(crate) fn providers(input: Value) -> Result<Value, String> {
.map_err(|_| "settings_output_invalid")?
.len();
for (offset, identity) in identities.into_iter().enumerate().skip(input.offset) {
- let provider = definition(&identity)?;
+ let provider = definition(&identity, snapshot.clone())?;
let descriptor = provider.descriptor();
let summary = ProviderSummary {
identity,
@@ -180,9 +190,9 @@ pub(crate) fn providers(input: Value) -> Result<Value, String> {
encode(ProvidersPage { providers, next })
}
-pub(crate) fn provider(input: Value) -> Result<Value, String> {
+pub(crate) fn provider(input: Value, snapshot: Arc<Snapshot>) -> Result<Value, String> {
let identity = serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
- let descriptor = definition(&identity)?.descriptor().clone();
+ let descriptor = definition(&identity, snapshot)?.descriptor().clone();
encode(Provider {
identity,
descriptor,
diff --git a/plugins/settings/native/src/credential.rs b/plugins/settings/native/src/credential.rs
index e42c5b60c8..cd82ee1b2a 100644
--- a/plugins/settings/native/src/credential.rs
+++ b/plugins/settings/native/src/credential.rs
@@ -72,6 +72,7 @@ fn rejected(reason: CredentialRejection) -> Result<Value, String> {
}
pub(crate) async fn save(
store: &Arc<ConfigurationStore>,
+ snapshot: Arc<maka_providers::catalog::Snapshot>,
input: Value,
context: Context,
) -> Result<Value, String> {
@@ -79,7 +80,7 @@ pub(crate) async fn save(
let Ok(request) = serde_json::from_value::<SaveCredential>(input) else {
return rejected(CredentialRejection::InvalidInput);
};
- let Ok(definition) = connections::definition(&request.expected.provider) else {
+ let Ok(definition) = connections::definition(&request.expected.provider, snapshot) else {
return rejected(CredentialRejection::ProviderUnavailable);
};
if !definition
@@ -108,6 +109,7 @@ pub(crate) async fn save(
}
enroll(
store,
+ definition,
request,
AuthenticationInput {
method: "api-key".into(),
@@ -144,6 +146,7 @@ impl provider::authentication::Interaction for LoginInteraction {
pub(crate) async fn login(
store: &Arc<ConfigurationStore>,
+ snapshot: Arc<maka_providers::catalog::Snapshot>,
input: Value,
context: Context,
) -> Result<Value, String> {
@@ -151,7 +154,7 @@ pub(crate) async fn login(
let Ok(input) = serde_json::from_value::<LoginCredential>(input) else {
return rejected(CredentialRejection::InvalidInput);
};
- let Ok(definition) = connections::definition(&input.expected.provider) else {
+ let Ok(definition) = connections::definition(&input.expected.provider, snapshot) else {
return rejected(CredentialRejection::ProviderUnavailable);
};
if !definition
@@ -164,6 +167,7 @@ pub(crate) async fn login(
}
enroll(
store,
+ definition,
SaveCredential {
expected: input.expected,
expected_credential: input.expected_credential,
@@ -180,13 +184,13 @@ pub(crate) async fn login(
async fn enroll(
store: &Arc<ConfigurationStore>,
+ definition: provider::Definition,
request: SaveCredential,
authentication: AuthenticationInput,
context: Context,
interactive: bool,
) -> Result<Value, String> {
let operation = context.operation().ok_or("settings_mutation_required")?;
- let definition = connections::definition(&request.expected.provider)?;
let attempt_id = format!(
"settings-{}",
hex::encode(Sha256::digest(
diff --git a/plugins/settings/native/src/credential/tests.rs b/plugins/settings/native/src/credential/tests.rs
index a7537e688e..c8be43959a 100644
--- a/plugins/settings/native/src/credential/tests.rs
+++ b/plugins/settings/native/src/credential/tests.rs
@@ -110,7 +110,11 @@ async fn chatgpt_login_saves_privately_and_rejects_cancelled_or_changed_connecti
Arc::new(RootOwner::create(&directory.path().join("root"), &namespaces).unwrap());
let store = Arc::new(ConfigurationStore::for_root(root).await.unwrap());
let identity: provider::Identity = serde_json::from_value(json!({"packageId":"maka.codex","entryId":"maka.codex","scope":"profile","name":"chatgpt"})).unwrap();
- let definition = connections::definition(&identity).unwrap();
+ let definition = connections::definition(
+ &identity,
+ Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+ )
+ .unwrap();
let revision = store.catalog().await.unwrap().revision;
store.create_connection(serde_json::from_value(json!({"expectedCatalogRevision":revision,"connection":{"slug":"chatgpt","name":"ChatGPT","provider":identity,"configuration":definition.descriptor().configuration_defaults,"enabled":true,"enabledModelIds":[]}})).unwrap()).await.unwrap();
let row = store.catalog().await.unwrap().connections.remove(0);
@@ -207,7 +211,11 @@ async fn chatgpt_login_saves_privately_and_rejects_cancelled_or_changed_connecti
}
let discovered = crate::setup::discover_prepared(
ticket,
- connections::definition(&identity).unwrap(),
+ connections::definition(
+ &identity,
+ Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+ )
+ .unwrap(),
provider::Context {
transport: Arc::new(Script {
fail: discovery == "network",
@@ -290,7 +298,11 @@ fn compiled_catalog_projects_search_metadata_and_resolves_selected_provider() {
let mut offset = 0;
let mut found = false;
loop {
- let page = connections::providers(json!({"offset":offset})).unwrap();
+ let page = connections::providers(
+ json!({"offset":offset}),
+ Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+ )
+ .unwrap();
let rows = page["providers"].as_array().unwrap();
assert!(page.to_string().len() <= 128 * 1024 || rows.len() == 1);
for row in rows {
@@ -299,7 +311,11 @@ fn compiled_catalog_projects_search_metadata_and_resolves_selected_provider() {
assert_eq!(row["label"], "ChatGPT Subscription");
assert_eq!(row["authentication"][0]["interactive"], true);
assert!(row.get("descriptor").is_none());
- let selected = connections::provider(row["identity"].clone()).unwrap();
+ let selected = connections::provider(
+ row["identity"].clone(),
+ Arc::new(maka_providers::catalog::Snapshot::bundled().unwrap()),
+ )
+ .unwrap();
assert_eq!(selected["identity"], row["identity"]);
assert_eq!(selected["descriptor"]["label"], "ChatGPT Subscription");
assert_eq!(selected["descriptor"]["authentication"][0]["id"], "chatgpt");
diff --git a/plugins/settings/native/src/lib.rs b/plugins/settings/native/src/lib.rs
index 39cdcbac83..71e55ac6e4 100644
--- a/plugins/settings/native/src/lib.rs
+++ b/plugins/settings/native/src/lib.rs
@@ -46,7 +46,10 @@ fn configuration_schema() -> serde_json::Value {
pub const IMPLEMENTATION: &str = "maka.settings";
pub const EXPORT: &str = "Settings";
-pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
+pub fn provide(
+ registry: &mut NativeRegistry,
+ catalog: Arc<maka_providers::catalog::Catalog>,
+) -> Result<(), String> {
for (implementation, export, contract) in [
(IMPLEMENTATION, EXPORT, contract::contract()),
("maka.settings.setup", "Setup", contract::setup_contract()),
@@ -56,10 +59,12 @@ pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
contract::authentication_contract(),
),
] {
+ let catalog = catalog.clone();
registry.insert(
implementation.into(),
Arc::new(move |context, input| {
let contract = contract.clone();
+ let catalog = catalog.clone();
Box::pin(async move {
let opened = async {
let configuration: Configuration = serde_json::from_value(input)
@@ -76,6 +81,7 @@ pub fn provide(registry: &mut NativeRegistry) -> Result<(), String> {
.await
.map_err(|_| "settings_store_unavailable")?;
Ok::<_, String>(Arc::new(service::Settings {
+ catalog,
configuration: Arc::new(store),
log: persistence.store().clone(),
configured_locale: LanguagePreference::from_locale(
diff --git a/plugins/settings/native/src/service.rs b/plugins/settings/native/src/service.rs
index 631256c9fa..182d92d3af 100644
--- a/plugins/settings/native/src/service.rs
+++ b/plugins/settings/native/src/service.rs
@@ -24,6 +24,7 @@ use maka_host_runtime::application::{Context, Handler, NativeOwner};
use serde_json::Value;
use std::sync::Arc;
pub(crate) struct Settings {
+ pub catalog: Arc<maka_providers::catalog::Catalog>,
pub configuration: Arc<maka_config::ConfigurationStore>,
pub log: Arc<maka_event_log::EventLog>,
pub configured_locale: maka_agent_facts::language::LanguagePreference,
@@ -58,10 +59,17 @@ fn credential_result(
Err(_) => Err("settings_effect_unknown".into()),
}
}
-fn selectable(row: &ConnectionCatalogEntry) -> bool {
- row.enabled && maka_providers::definition(&row.provider).is_ok()
+fn selectable(
+ row: &ConnectionCatalogEntry,
+ snapshot: Arc<maka_providers::catalog::Snapshot>,
+) -> bool {
+ row.enabled && maka_providers::definition(&row.provider, snapshot).is_ok()
}
-async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<ModelChoice> {
+async fn model_choice(
+ row: &ConnectionCatalogEntry,
+ model: &str,
+ snapshot: Arc<maka_providers::catalog::Snapshot>,
+) -> Option<ModelChoice> {
let info = row
.models
.iter()
@@ -75,7 +83,7 @@ async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<Model
.and_then(|all| all.get(model))
.cloned();
let default_thinking_level = overrides.as_ref().and_then(|o| o.default_thinking_level);
- let resolved = maka_providers::definition(&row.provider)
+ let resolved = maka_providers::definition(&row.provider, snapshot.clone())
.ok()?
.prepare(maka_agent_ports::provider::Resolve {
connection: maka_agent_ports::provider::Connection {
@@ -97,6 +105,7 @@ async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<Model
} else {
maka_providers::api::ApiProviders::model_metadata(
&row.provider.name,
+ snapshot,
maka_agent_ports::provider::Connection {
id: row.connection_id.clone(),
revision: row.revision,
@@ -142,14 +151,15 @@ async fn model_choice(row: &ConnectionCatalogEntry, model: &str) -> Option<Model
async fn resolved_model(
catalog: &ConnectionCatalogSnapshot,
target: &ConnectionTarget,
+ snapshot: Arc<maka_providers::catalog::Snapshot>,
) -> DefaultModel {
let row = catalog.connections.iter().find(|row| {
row.connection_id == target.connection_id
- && selectable(row)
+ && selectable(row, snapshot.clone())
&& row.enabled_model_ids.contains(&target.model_id)
});
let choice = match row {
- Some(row) => model_choice(row, &target.model_id).await,
+ Some(row) => model_choice(row, &target.model_id, snapshot).await,
None => None,
};
match choice {
@@ -159,10 +169,13 @@ async fn resolved_model(
},
}
}
-async fn default_model(catalog: &ConnectionCatalogSnapshot) -> DefaultModel {
+async fn default_model(
+ catalog: &ConnectionCatalogSnapshot,
+ snapshot: Arc<maka_providers::catalog::Snapshot>,
+) -> DefaultModel {
match &catalog.default_target {
None => DefaultModel::None,
- Some(target) => resolved_model(catalog, target).await,
+ Some(target) => resolved_model(catalog, target, snapshot).await,
}
}
@@ -174,6 +187,7 @@ impl Handler for Settings {
context: Context,
) -> BoxFuture<'static, Result<Value, String>> {
let store = self.configuration.clone();
+ let public_catalog = self.catalog.clone();
let log = self.log.clone();
let configured_locale = self.configured_locale;
let native_language = self.native_language;
@@ -196,12 +210,50 @@ impl Handler for Settings {
context.operation().ok_or("settings_mutation_required")?;
super::preferences::update(&store, input).await
}
- "connections" => super::connections::page(&store, input).await,
+ "connections" => {
+ let snapshot = public_catalog
+ .snapshot
+ .read()
+ .await
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
+ super::connections::page(&store, input, snapshot).await
+ }
"connection" => super::connections::detail(&store, input).await,
- "providers" => super::connections::providers(input),
- "provider" => super::connections::provider(input),
- "discoverModels" => super::setup::discover(&store, input, context).await,
- "loginCredential" => super::credential::login(&store, input, context).await,
+ "providers" => {
+ let snapshot = public_catalog
+ .snapshot
+ .read()
+ .await
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
+ super::connections::providers(input, snapshot)
+ }
+ "provider" => {
+ let snapshot = public_catalog
+ .snapshot
+ .read()
+ .await
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
+ super::connections::provider(input, snapshot)
+ }
+ "discoverModels" => {
+ super::setup::discover(&store, &public_catalog, input, context).await
+ }
+ "loginCredential" => {
+ let snapshot = public_catalog
+ .snapshot
+ .read()
+ .await
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
+ super::credential::login(&store, snapshot, input, context).await
+ }
"create" => {
context.operation().ok_or("settings_mutation_required")?;
let Ok(mut input) =
@@ -209,7 +261,15 @@ impl Handler for Settings {
else {
return rejected(ConfigurationRejection::InvalidInput);
};
- let Ok(definition) = super::connections::definition(&input.connection.provider)
+ let snapshot = public_catalog
+ .snapshot
+ .read()
+ .await
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
+ let Ok(definition) =
+ super::connections::definition(&input.connection.provider, snapshot)
else {
return rejected(ConfigurationRejection::ProviderUnavailable);
};
@@ -235,7 +295,15 @@ impl Handler for Settings {
row.connection_id == input.expected.connection_id
&& row.revision == input.expected.revision
}) {
- let Ok(definition) = super::connections::definition(&row.provider)
+ let snapshot = public_catalog
+ .snapshot
+ .read()
+ .await
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
+ let Ok(definition) =
+ super::connections::definition(&row.provider, snapshot)
else {
return rejected(ConfigurationRejection::ProviderUnavailable);
};
@@ -272,7 +340,16 @@ impl Handler for Settings {
};
credential_result(store.delete_credential(input).await)
}
- "saveCredential" => super::credential::save(&store, input, context).await,
+ "saveCredential" => {
+ let snapshot = public_catalog
+ .snapshot
+ .read()
+ .await
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
+ super::credential::save(&store, snapshot, input, context).await
+ }
"updateModelSettings" => {
context.operation().ok_or("settings_mutation_required")?;
let input: UpdateModelSettings =
@@ -334,19 +411,31 @@ impl Handler for Settings {
"resolveModel" => {
let target: ConnectionTarget =
serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
+ let public_guard = public_catalog.snapshot.read().await;
+ let snapshot = public_guard
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
let catalog = store
.catalog()
.await
.map_err(|_| "settings_store_unavailable")?;
- super::connections::encode(resolved_model(&catalog, &target).await)
+ drop(public_guard);
+ super::connections::encode(resolved_model(&catalog, &target, snapshot).await)
}
"models" => {
let input: ModelsRead =
serde_json::from_value(input).map_err(|_| "settings_input_invalid")?;
+ let public_guard = public_catalog.snapshot.read().await;
+ let snapshot = public_guard
+ .as_ref()
+ .map_err(|_| "settings_store_unavailable")?
+ .clone();
let catalog = store
.catalog()
.await
.map_err(|_| "settings_store_unavailable")?;
+ drop(public_guard);
let start = match input {
ModelsRead::Start { query } => ModelCursor {
query,
@@ -384,7 +473,7 @@ impl Handler for Settings {
.enumerate()
.skip(start.connection)
{
- if !selectable(row) {
+ if !selectable(row, snapshot.clone()) {
continue;
}
for (at, model) in row.enabled_model_ids.iter().enumerate().skip(
@@ -402,7 +491,8 @@ impl Handler for Settings {
});
break 'rows;
}
- let Some(choice) = model_choice(row, model).await else {
+ let Some(choice) = model_choice(row, model, snapshot.clone()).await
+ else {
continue;
};
let matches = terms.is_empty() || {
@@ -422,7 +512,7 @@ impl Handler for Settings {
}
serde_json::to_value(ModelsPage::Ready {
revision: catalog.revision,
- default_model: Box::new(default_model(&catalog).await),
+ default_model: Box::new(default_model(&catalog, snapshot).await),
choices,
next,
})
diff --git a/plugins/settings/native/src/setup.rs b/plugins/settings/native/src/setup.rs
index 5ffa52dea4..cd51c1816f 100644
--- a/plugins/settings/native/src/setup.rs
+++ b/plugins/settings/native/src/setup.rs
@@ -34,13 +34,26 @@ use std::{
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")?;
- let Ok(definition) = crate::connections::definition(&expected.provider) else {
+ // 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
diff --git a/products/maka/terminal-shell/tests/real_host/oauth.rs b/products/maka/terminal-shell/tests/real_host/oauth.rs
index 145e3ef3dc..2e8d2fbb19 100644
--- a/products/maka/terminal-shell/tests/real_host/oauth.rs
+++ b/products/maka/terminal-shell/tests/real_host/oauth.rs
@@ -46,7 +46,12 @@ impl Handler for Authentication {
let identity = serde_json::from_value(input)
.map_err(|_| "invalid provider identity".to_owned());
identity.and_then(|identity| {
- let descriptor = maka_providers::definition(&identity)?.descriptor().clone();
+ let descriptor = maka_providers::definition(
+ &identity,
+ std::sync::Arc::new(maka_providers::catalog::Snapshot::bundled()?),
+ )?
+ .descriptor()
+ .clone();
serde_json::to_value(maka_settings_native::contract::Provider {
identity,
descriptor,
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org