Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions src/bin/opencompany.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,23 @@ async fn main() -> Result<()> {
"{}",
opencompany::app::journal::pin_keyring(&journal).summary()
);
// Tag every request the embedded openhuman_core makes to the
// TinyHumans backend as opencompany's, not the vendored
// runtime's own `openhuman` default (issue #376). This must run
// here — before any company runtime, agent harness or HTTP
// listener exists — because core's `IntegrationClient` reads the
// identity into its default headers AT CONSTRUCTION
// (`harness/toolbelt.rs`, `harness/composio.rs`,
// `harness/search.rs` each build one the first time a company
// needs it), so a call after the first client already exists
// would not retroactively re-tag it. Same startup-ordering
// reasoning as the keyring pin directly above: say it here, once,
// rather than leaving it implicit in which line happens to run
// first.
// The call itself lives in the library so a test can reach it —
// this arm cannot be exercised from one.
#[cfg(feature = "openhuman")]
opencompany::product::install_into_embedded_core();
// Soft disk-quota alerting. Hard enforcement is the container /
// StorageClass layer's job (EFS access point, k8s ResourceQuota);
// here we surface an operator-visible warning when a workspace
Expand Down
9 changes: 9 additions & 0 deletions src/brain/medulla/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,18 @@ impl HttpSocketTransport {
// Belt-and-suspenders: never send a body carrying a `model` field.
wire::assert_no_model(&envelope)?;

let (product_header_name, product_header_value) = crate::product::product_identity_header();
let response = self
.client
.post(self.endpoint(path))
// `HttpSocketTransport` always targets the TinyHumans-owned
// Medulla endpoint (`base_url` is `https://api.tinyhumans.ai`),
// never a third party, so tagging it is unconditional. This is a
// bespoke `reqwest::Client`, not one built through
// `openhuman_core`'s `IntegrationClient`, so it never inherits
// the header `set_product_identity` attaches elsewhere — see
// `crate::product`.
.header(product_header_name, product_header_value)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.bearer_auth(self.credential.expose())
.json(&envelope)
.send()
Expand Down
21 changes: 20 additions & 1 deletion src/feedback/tinyhumans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ use crate::feedback::types::FeedbackCategory;

/// The product discriminator the hub routes on. Feedback from this runtime is
/// always attributed to opencompany, whichever company reported it.
pub const PRODUCT: &str = "opencompany";
///
/// Re-exported from [`crate::product::PRODUCT_IDENTITY`] rather than holding
/// its own `"opencompany"` literal: that module is this crate's single
/// source of truth for the product name, and a second copy of the string
/// here would be exactly the kind of duplicate this task exists to remove
/// (issue #376). If the two ever need to differ — e.g. the hub's `product`
/// field wants a different spelling than the `x-sdk-name` header — that is a
/// deliberate divergence to introduce explicitly, not something to default
/// into by leaving a stale literal in place.
pub const PRODUCT: &str = crate::product::PRODUCT_IDENTITY;

/// One feedback report to forward.
///
Expand Down Expand Up @@ -217,9 +226,19 @@ mod http {
"origin": request.origin,
"externalRef": request.external_ref,
});
let (product_header_name, product_header_value) =
crate::product::product_identity_header();
let resp = self
.http
.post(&url)
// This client bypasses the embedded openhuman_core entirely, so
// unlike the harness's `IntegrationClient`-backed calls it must
// tag itself with our product identity directly — see
// `crate::product`. `body` already carries the same value under
// `"product"`, but that is the hub's own routing field over the
// JSON payload; this header is the transport-level marker every
// backend endpoint reads, feedback or otherwise.
.header(product_header_name, product_header_value)
// The credential rides the header and only the header.
.bearer_auth(self.credential.expose())
.json(&body)
Expand Down
80 changes: 79 additions & 1 deletion src/harness/embeddings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,19 @@ impl HostedEmbeddings {
_ => text,
};

let mut http = self.client.post(&url).json(body);
let (product_header_name, product_header_value) = crate::product::product_identity_header();
let mut http = self
.client
.post(&url)
.json(body)
// `hosted_endpoint_from_env` (the sole resolver for `base_url`
// here) always points at the managed TinyHumans endpoint, never a
// BYOK third party, so tagging every request is unconditional.
// This is a bespoke `reqwest::Client`, not one built through
// `openhuman_core`'s `IntegrationClient`, so it never inherits
// the header `set_product_identity` attaches elsewhere — see
// `crate::product`.
.header(product_header_name, product_header_value);
if let Some(bearer) = &bearer {
http = http.bearer_auth(bearer);
}
Expand Down Expand Up @@ -457,6 +469,72 @@ mod tests {
);
}

/// Wire-level proof for issue #376 (AC #1 + AC #3): a real HTTP request
/// this client sends must carry `x-sdk-name: opencompany` — not the
/// vendored `openhuman_core` crate's own `openhuman` default, and not
/// nothing. `HostedEmbeddings` was picked for this proof, of the three
/// direct backend clients this issue touches (feedback, the Medulla HTTP
/// transport, and this one): the other two live behind the `tinyhumans`
/// / `medulla` Cargo features respectively, so their tests only compile
/// under a feature set this crate's `openhuman` build does not enable by
/// default, while `harness/` — and therefore this file's tests — already
/// compiles and runs under plain `--features openhuman` (`reqwest` rides
/// in on that feature; see `Cargo.toml`).
///
/// Deliberately a small standalone server rather than reusing
/// `spawn_server`/`Mode` above: this test cares about exactly one header
/// on exactly one request, and threading header capture through the
/// shared multi-mode fixture would only add indirection for it.
#[tokio::test]
async fn embed_request_carries_the_product_identity_header() {
use axum::Json;
use axum::http::HeaderMap;
use axum::routing::post;

let captured_header: Arc<StdMutex<Option<String>>> = Arc::new(StdMutex::new(None));
let capture = Arc::clone(&captured_header);
let app = axum::Router::new().route(
"/embeddings",
post(
move |headers: HeaderMap, Json(body): Json<serde_json::Value>| {
let capture = Arc::clone(&capture);
async move {
*capture.lock().unwrap() = headers
.get(crate::product::PRODUCT_IDENTITY_HEADER)
.and_then(|value| value.to_str().ok())
.map(str::to_string);
// One 4-dim embedding per input, so this stays compatible
// with the `dim: 4` backend built below regardless of how
// many strings a future edit passes to `embed`.
let inputs = body
.get("input")
.and_then(|v| v.as_array())
.map(|a| a.len())
.unwrap_or(1);
let data: Vec<serde_json::Value> = (0..inputs)
.map(|i| serde_json::json!({ "index": i, "embedding": vec![0.0_f64; 4] }))
.collect();
Json(serde_json::json!({ "data": data }))
}
},
),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});

let be = backend(format!("http://{addr}"), 4);
be.embed(&["hello"]).await.expect("embeds against the stub");

assert_eq!(
captured_header.lock().unwrap().as_deref(),
Some("opencompany"),
"the embeddings client must attach x-sdk-name: opencompany on every request"
);
}

#[tokio::test]
async fn retries_once_after_unauthorized() {
let (url, seen) = spawn_server(Mode::UnauthorizedThenOk { dim: 4 }).await;
Expand Down
Loading
Loading