Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a2e1c46
test(inference): specify bounded concurrent API-key rotation and fail…
AmirrezaFarnamTaheri Oct 28, 2026
aeb3185
feat(inference): port isolated, redacted native API credential pool
AmirrezaFarnamTaheri Oct 28, 2026
108c0c5
feat(inference): register testable credential-pool foundation without…
AmirrezaFarnamTaheri Oct 28, 2026
ae5271d
ci(governance): account for scoped credential pool and per-process sa…
AmirrezaFarnamTaheri Oct 28, 2026
dfdc0be
chore(sync): adopt upstream agent-runtime ownership fix
AmirrezaFarnamTaheri Oct 28, 2026
0a9f6eb
test(inference): specify bounded credential failover across unary str…
AmirrezaFarnamTaheri Oct 28, 2026
15a0961
feat(inference): add native credential fleet adapter with safe retry …
AmirrezaFarnamTaheri Oct 28, 2026
d1e03e3
feat(inference): compile credential fleet adapter and regression test…
AmirrezaFarnamTaheri Oct 28, 2026
4dd1bec
test(tls): credentialed clients must not leak custom API headers via …
AmirrezaFarnamTaheri Oct 28, 2026
e6b17a9
fix(tls): provide credentialed HTTP clients with redirects disabled a…
AmirrezaFarnamTaheri Oct 28, 2026
5676356
fix(inference): forbid authenticated BYOK redirects while preserving …
AmirrezaFarnamTaheri Oct 28, 2026
2d94c4b
fix(inference): retain unauthenticated optional-CA route when securin…
AmirrezaFarnamTaheri Oct 28, 2026
73d315e
test(inference): prove BYOK multi-key 429 failover uses one credentia…
AmirrezaFarnamTaheri Oct 28, 2026
3ac5567
test(inference): register real BYOK rotation integration regression
AmirrezaFarnamTaheri Oct 28, 2026
a704977
test(inference): lock cloud HTTP-client behavior before rotating cred…
AmirrezaFarnamTaheri Oct 28, 2026
f90ddb5
test(inference): use correct shared TLS fixture path
AmirrezaFarnamTaheri Oct 28, 2026
8aa9826
refactor(inference): centralize secure native-cloud HTTP client const…
AmirrezaFarnamTaheri Oct 28, 2026
fa5c9e2
refactor(inference): register shared cloud HTTP security policy module
AmirrezaFarnamTaheri Oct 28, 2026
d9799e5
refactor(inference): use the shared secure BYOK HTTP client policy
AmirrezaFarnamTaheri Oct 28, 2026
48d8716
test(inference): inspection uses one key while runtime builds one mod…
AmirrezaFarnamTaheri Oct 28, 2026
7f4e062
feat(inference): use tested native fleet adapter for explicit multi-k…
AmirrezaFarnamTaheri Oct 28, 2026
5550ba6
feat(inference): register native BYOK multi-key model construction he…
AmirrezaFarnamTaheri Oct 28, 2026
88c7e05
feat(inference): enable bounded multi-key BYOK rotation for single-us…
AmirrezaFarnamTaheri Oct 28, 2026
de30c5d
test(inference): catalog probe must use one API key rather than a del…
AmirrezaFarnamTaheri Oct 28, 2026
e477ea7
fix(inference): expose one-key catalog parser using native fleet cred…
AmirrezaFarnamTaheri Oct 28, 2026
5015b1c
fix(inference): isolate catalog credentials and block authenticated r…
AmirrezaFarnamTaheri Oct 28, 2026
92de7cd
test(inference): require endpoint-scoped API-key health after BYOK re…
AmirrezaFarnamTaheri Oct 28, 2026
5a0e8aa
fix(inference): isolate API credential cooldowns by effective endpoint
AmirrezaFarnamTaheri Oct 28, 2026
07c6eef
fix(inference): isolate API credential cooldowns by effective endpoint
AmirrezaFarnamTaheri Oct 28, 2026
6e53c97
refactor(inference): own BYOK rotation pool scope in native cloud-fle…
AmirrezaFarnamTaheri Oct 28, 2026
da3c9ec
style(inference): rustfmt cloud routes and drop oversized cloud-slug …
AmirrezaFarnamTaheri Oct 28, 2026
b572a2f
style(test): apply rustfmt to credential fleet integration fixture
AmirrezaFarnamTaheri Oct 28, 2026
47e2235
style(test): maintain sorted test-module declarations
AmirrezaFarnamTaheri Oct 28, 2026
7d5b72e
style(models): rustfmt credentialed catalog checks
AmirrezaFarnamTaheri Oct 28, 2026
b941b1f
style(test): rustfmt multi-key catalog credential regression
AmirrezaFarnamTaheri Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/openhuman-core/src/config/schema/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use serde::{Deserialize, Serialize};

pub use tinyagents_harness::config::{CompactionSettings, CompactionStrategy, ContextConfig};
pub use tinyagents_harness::summarization::ToolRendering;
use tinyagents_harness::summarization::ToolRendering;

/// The whole `[context]` table: TinyAgents' [`ContextConfig`] and the
/// compaction summarizer's OpenHuman-side settings.
Expand Down
17 changes: 17 additions & 0 deletions crates/openhuman-core/src/inference/provider/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@

pub(crate) mod access_gates;
mod chat_model;
mod cloud_fleet;
mod cloud_http;
mod cloud_slug;
mod cloud_spec;
// The adapter is compiled and unit-tested before wiring any live BYOK route.
// No credential group is enabled in production until explicit factory support.
#[allow(dead_code)]
mod credential_fleet;
mod credentials;
mod inspection;
// Wave-1 scheduler foundation: remain isolated until the model-factory integration
// adopts it in the next stacked change. No inference behavior changes in this draft.
#[allow(dead_code)]
mod key_pool;
mod local_runtime;
mod managed_backend;
mod routing;
Expand All @@ -29,6 +39,13 @@ pub use chat_model::{
pub(crate) use chat_model::{resolves_to_managed_backend, OpenHumanModelFactory};
pub(crate) use cloud_slug::resolve_cloud_slug_for;
pub(crate) use cloud_spec::{cloud_provider_spec, CloudCredentialResolver};
/// Catalog discovery is a one-credential HTTP call. Never give its request
/// builder a delimited fleet: that would forward multiple secrets as a single
/// Authorization header. The native inference path owns per-call rotation.
pub(crate) fn byok_catalog_keys(raw: &str) -> Vec<String> {
key_pool::parse_api_keys(raw)
}

pub use credentials::{auth_key_for_slug, lookup_key_for_slug};
pub(crate) use credentials::{
openai_bearer_is_oauth, redact_endpoint_origin, resolve_codex_routing,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Minimal native-cloud adapter for multi-key BYOK requests.
//! The ordinary single-key model stays unchanged. Inspection constructs only
//! the first credential's model; it must not perform runtime failover.
use std::sync::Arc;

use tinyinference_llm::model::ChatModel;

use super::credential_fleet::CredentialFleetModel;
use super::key_pool::RotationStrategy;

/// Health and cooldown belong to a destination, not merely a provider alias:
/// changing the endpoint must not carry its previous 429 or invalid-key state.
pub(super) fn rotation_pool_scope(
workspace: &std::path::Path,
slug: &str,
model: &str,
endpoint: &str,
) -> String {
format!(
"{}:{}:{}:{:016x}",
workspace.display(),
slug,
model,
super::key_pool::scope_fingerprint(endpoint),
)
}

pub(super) fn build_cloud_model_with_keys(
scope: String,
keys: Vec<String>,
enable_rotation: bool,
mut build: impl FnMut(&str) -> anyhow::Result<Arc<dyn ChatModel<()>>>,
) -> anyhow::Result<Arc<dyn ChatModel<()>>> {
let keys = if keys.is_empty() {
vec![String::new()]
} else {
keys
};
if !enable_rotation || keys.len() == 1 {
return build(&keys[0]);
}

let models = keys
.iter()
.map(|key| build(key))
.collect::<anyhow::Result<Vec<_>>>()?;
Ok(Arc::new(CredentialFleetModel::new(
scope,
keys,
models,
None,
RotationStrategy::RoundRobin,
)))
}

#[cfg(test)]
#[path = "cloud_fleet_tests.rs"]
mod tests;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use super::*;
use std::cell::RefCell;

struct NoNetworkModel;

#[async_trait::async_trait]
impl ChatModel<()> for NoNetworkModel {
async fn invoke(
&self,
_: &(),
_: tinyinference_llm::model::ModelRequest,
) -> tinyinference_llm::Result<tinyinference_llm::model::ModelResponse> {
Err(tinyinference_llm::Error::Model("unused test route".into()))
}
}

#[test]
fn inspection_binds_one_credential_without_enabling_failover() {
let built = RefCell::new(Vec::new());
let result = build_cloud_model_with_keys(
"inspection-model".into(),
vec!["key-one".into(), "key-two".into()],
false,
|key| {
built.borrow_mut().push(key.to_owned());
Ok(Arc::new(NoNetworkModel) as Arc<dyn ChatModel<()>>)
},
);
assert!(result.is_ok());
assert_eq!(*built.borrow(), ["key-one"]);
}

#[test]
fn runtime_builds_one_native_model_per_distinct_credential() {
let built = RefCell::new(Vec::new());
let result = build_cloud_model_with_keys(
"runtime-model".into(),
vec!["key-one".into(), "key-two".into()],
true,
|key| {
built.borrow_mut().push(key.to_owned());
Ok(Arc::new(NoNetworkModel) as Arc<dyn ChatModel<()>>)
},
);
assert!(result.is_ok());
assert_eq!(*built.borrow(), ["key-one", "key-two"]);
}
31 changes: 31 additions & 0 deletions crates/openhuman-core/src/inference/provider/factory/cloud_http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! HTTP-client policy shared by native BYOK construction.
//! Credentialed routes never follow redirects: a custom authorization header
//! must not travel to a different host or a plaintext HTTP endpoint.
use crate::config::schema::cloud_providers::AuthStyle;

pub(super) fn build_cloud_http_client(
style: AuthStyle,
anthropic_native: bool,
ca_pem: Option<&str>,
) -> Result<Option<reqwest::Client>, String> {
let service_key = if anthropic_native {
"provider.anthropic"
} else {
"provider.compatible"
};
let pem = ca_pem.filter(|value| !value.is_empty());
if matches!(style, AuthStyle::Bearer | AuthStyle::Anthropic) {
let client = crate::util::tls::credentialed_client_builder(service_key, pem)?
.build()
.map_err(|_| "Could not build secure provider inference HTTP client".to_string())?;
return Ok(Some(client));
}
match pem {
Some(pem) => crate::util::tls::client_with_ca_bundle(pem, service_key).map(Some),
None => Ok(None),
}
}

#[cfg(test)]
#[path = "cloud_http_tests.rs"]
mod tests;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use super::*;

#[test]
fn unauthenticated_cloud_routes_keep_the_optional_http_client_behavior() {
assert!(build_cloud_http_client(AuthStyle::None, false, None)
.expect("no-auth client")
.is_none());
assert!(build_cloud_http_client(
AuthStyle::None,
false,
Some(include_str!("../../../util/tls/test-ca.pem")),
)
.expect("custom CA client")
.is_some());
}

#[test]
fn authenticated_cloud_routes_always_install_a_redirect_safe_client() {
for style in [AuthStyle::Bearer, AuthStyle::Anthropic] {
assert!(build_cloud_http_client(style, false, None)
.expect("authenticated client")
.is_some());
}
}
87 changes: 55 additions & 32 deletions crates/openhuman-core/src/inference/provider/factory/cloud_slug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub(crate) fn resolve_cloud_slug_for<'a>(
)
}

pub(super) use super::cloud_fleet::rotation_pool_scope;

/// The user-facing error for a failed credential lookup: the guidance, plus
/// the expiry signal when the lookup reported one. The doctor's
/// refresh-required check and the Codex reconnect classifier key on those
Expand Down Expand Up @@ -661,45 +663,66 @@ fn try_create_cloud_slug_chat_model_from_string_with_native_tools_and_access(
crate::security::egress::EgressDescriptor::inference(&slug, &effective_model, true),
);

let http = match config
.cloud_provider_ca_certs
.get(&slug)
.filter(|pem| !pem.is_empty())
{
Some(pem) => {
let service_key = if anthropic_native {
"provider.anthropic"
} else {
"provider.compatible"
};
match crate::util::tls::client_with_ca_bundle(pem, service_key) {
Ok(http) => Some(http),
Err(error) => return Some(Err(anyhow::anyhow!(error))),
}
}
None => None,
let http = match super::cloud_http::build_cloud_http_client(
entry.auth_style,
anthropic_native,
config
.cloud_provider_ca_certs
.get(&slug)
.map(String::as_str),
) {
Ok(client) => client,
Err(error) => return Some(Err(anyhow::anyhow!(error))),
};
let auth_style = cloud_spec::registry_auth_style(entry.auth_style)
.expect("managed JWT entries returned above");
let chat = cloud_spec::build_cloud_model(cloud_spec::CloudBuild {
spec: cloud_spec::cloud_provider_spec(entry, &effective_model),
auth_style,
key: key.as_str(),
temperature_override,
temperature_unsupported_models: config.temperature_unsupported_models.as_slice(),
native_tool_calling,
default_provider_options,
extra_headers: extra_headers.as_slice(),
extra_query_params: extra_query_params.as_slice(),
user_agent: user_agent.as_deref(),
codex: codex_route,
http,
});
// Parse only actual BYOK credentials. OAuth, hosted and unauthenticated
// routes retain their original single-key identity and provider semantics.
let eligible =
!codex.using_oauth && matches!(entry.auth_style, AuthStyle::Bearer | AuthStyle::Anthropic);
let keys = if eligible {
super::key_pool::parse_api_keys(&key)
} else {
vec![key.clone()]
};
if keys.len() > 1 && (crate::core::runtime::is_saas() || codex.account_id.is_some()) {
return Some(Err(anyhow::anyhow!(
"multi-key BYOK rotation is unavailable for shared or account-bound sessions"
)));
}
let inspection_key = keys.first().cloned().unwrap_or_default();
let scope = rotation_pool_scope(
&config.workspace_dir,
&slug,
&effective_model,
&codex.endpoint,
);
let chat = super::cloud_fleet::build_cloud_model_with_keys(
scope,
keys,
eligible && credential_access == CredentialAccess::Runtime,
|credential| {
cloud_spec::build_cloud_model(cloud_spec::CloudBuild {
spec: cloud_spec::cloud_provider_spec(entry, &effective_model),
auth_style: auth_style.clone(),
key: credential,
temperature_override,
temperature_unsupported_models: config.temperature_unsupported_models.as_slice(),
native_tool_calling,
default_provider_options: default_provider_options.clone(),
extra_headers: extra_headers.as_slice(),
extra_query_params: extra_query_params.as_slice(),
user_agent: user_agent.as_deref(),
codex: codex_route,
http: http.clone(),
})
},
);
if let Some(identity) = inspection_identity {
let credential = if entry.auth_style == AuthStyle::None {
String::new()
} else {
key.clone()
inspection_key.clone()
};
let output_budget = inspection_output_budget_capability(&codex);
// Doctor's live probe deduplicates on this identity, so it carries
Expand Down
Loading
Loading