mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(rpc): register openhuman.system_info and add legacy alias (Sentry CORE-RUST-G0, closes #2871) (#2877)
Co-authored-by: cyrus@tinyhumans.ai <cyrus@tinyhumans.ai>
This commit is contained in:
co-authored by
cyrus@tinyhumans.ai <cyrus@tinyhumans.ai>
parent
972e327381
commit
32dd0be19a
@@ -95,6 +95,24 @@ describe('rpcMethods catalog', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('health legacy alias resolution (Sentry CORE-RUST-FG / CORE-RUST-G0)', () => {
|
||||
test('health_snapshot resolves to openhuman.health_snapshot', () => {
|
||||
expect(normalizeRpcMethod('health_snapshot')).toBe(CORE_RPC_METHODS.healthSnapshot);
|
||||
});
|
||||
|
||||
test('openhuman.system_info resolves to openhuman.health_system_info (Sentry CORE-RUST-G0)', () => {
|
||||
// Older clients called openhuman.system_info before the method was
|
||||
// namespaced under health as openhuman.health_system_info.
|
||||
expect(normalizeRpcMethod('openhuman.system_info')).toBe(CORE_RPC_METHODS.healthSystemInfo);
|
||||
});
|
||||
|
||||
test('canonical health_system_info passes through unchanged', () => {
|
||||
expect(normalizeRpcMethod('openhuman.health_system_info')).toBe(
|
||||
'openhuman.health_system_info'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('catalog canonical methods exist in core schema registry (drift guard)', () => {
|
||||
const schemaSources = [
|
||||
fs.readFileSync(
|
||||
|
||||
@@ -38,6 +38,7 @@ export const CORE_RPC_METHODS = {
|
||||
mcpClientsInstalledList: 'openhuman.mcp_clients_installed_list',
|
||||
mcpClientsToolCall: 'openhuman.mcp_clients_tool_call',
|
||||
healthSnapshot: 'openhuman.health_snapshot',
|
||||
healthSystemInfo: 'openhuman.health_system_info',
|
||||
} as const;
|
||||
|
||||
export type CoreRpcMethod = (typeof CORE_RPC_METHODS)[keyof typeof CORE_RPC_METHODS];
|
||||
@@ -77,6 +78,10 @@ export const LEGACY_METHOD_ALIASES: Record<string, CoreRpcMethod> = {
|
||||
'openhuman.providers_list_models': CORE_RPC_METHODS.inferenceListModels,
|
||||
'openhuman.inference_embed': CORE_RPC_METHODS.embeddingsEmbed,
|
||||
health_snapshot: CORE_RPC_METHODS.healthSnapshot,
|
||||
// `openhuman.system_info` was used by older clients / SDK callers before the
|
||||
// method was namespaced as `openhuman.health_system_info`.
|
||||
// Sentry CORE-RUST-G0 — https://sentry.tinyhumans.ai/organizations/tinyhumans/issues/6340/
|
||||
'openhuman.system_info': CORE_RPC_METHODS.healthSystemInfo,
|
||||
};
|
||||
|
||||
export function normalizeRpcMethod(method: string): string {
|
||||
|
||||
@@ -118,6 +118,10 @@ const LEGACY_ALIASES: &[(&str, &str)] = &[
|
||||
// bare `health_snapshot` (no namespace prefix) was used by older clients
|
||||
// before the canonical `openhuman.health_snapshot` form was established.
|
||||
("health_snapshot", "openhuman.health_snapshot"),
|
||||
// `openhuman.system_info` was used by older clients / SDK callers before
|
||||
// the method was namespaced under `health` as `openhuman.health_system_info`.
|
||||
// Sentry CORE-RUST-G0 — https://sentry.tinyhumans.ai/organizations/tinyhumans/issues/6340/
|
||||
("openhuman.system_info", "openhuman.health_system_info"),
|
||||
("openhuman.inference_embed", "openhuman.embeddings_embed"),
|
||||
("openhuman.local_ai_presets", "openhuman.inference_presets"),
|
||||
(
|
||||
@@ -422,6 +426,18 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_legacy_rewrites_system_info() {
|
||||
// Sentry CORE-RUST-G0: older clients called `openhuman.system_info`
|
||||
// before the method was namespaced under `health` as
|
||||
// `openhuman.health_system_info`. The alias table must rewrite it so
|
||||
// the call resolves against the registered controller.
|
||||
assert_eq!(
|
||||
resolve_legacy("openhuman.system_info"),
|
||||
"openhuman.health_system_info",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_legacy_passes_through_unknown_methods() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,8 +1,90 @@
|
||||
//! JSON-RPC / CLI controller surface for the process health registry.
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::openhuman::health;
|
||||
use crate::rpc::RpcOutcome;
|
||||
|
||||
pub fn health_snapshot() -> RpcOutcome<serde_json::Value> {
|
||||
RpcOutcome::single_log(health::snapshot_json(), "health_snapshot requested")
|
||||
}
|
||||
|
||||
/// Static system information returned by `openhuman.health_system_info`.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SystemInfo {
|
||||
/// Cargo package version of the running core binary.
|
||||
pub version: &'static str,
|
||||
/// Target operating system name (`linux`, `macos`, `windows`, …).
|
||||
pub os: &'static str,
|
||||
/// Target CPU architecture (`x86_64`, `aarch64`, …).
|
||||
pub arch: &'static str,
|
||||
/// Current process ID.
|
||||
pub pid: u32,
|
||||
}
|
||||
|
||||
/// Returns static system information: version, OS, architecture, and PID.
|
||||
///
|
||||
/// This is the handler backing the `openhuman.health_system_info` RPC method
|
||||
/// (legacy callers may send `openhuman.system_info`, which the alias table
|
||||
/// rewrites before dispatch).
|
||||
pub fn system_info() -> RpcOutcome<SystemInfo> {
|
||||
let info = SystemInfo {
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
os: std::env::consts::OS,
|
||||
arch: std::env::consts::ARCH,
|
||||
pid: std::process::id(),
|
||||
};
|
||||
tracing::debug!(
|
||||
version = info.version,
|
||||
os = info.os,
|
||||
arch = info.arch,
|
||||
pid = info.pid,
|
||||
"[health] system_info requested"
|
||||
);
|
||||
RpcOutcome::new(info, vec![])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn system_info_returns_non_empty_version() {
|
||||
let outcome = system_info();
|
||||
let json = outcome
|
||||
.into_cli_compatible_json()
|
||||
.expect("serialization ok");
|
||||
let version = json["version"].as_str().expect("version is a string");
|
||||
assert!(!version.is_empty(), "version must be non-empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_info_returns_known_os() {
|
||||
let outcome = system_info();
|
||||
let json = outcome
|
||||
.into_cli_compatible_json()
|
||||
.expect("serialization ok");
|
||||
let os = json["os"].as_str().expect("os is a string");
|
||||
// std::env::consts::OS is always one of the compile-time Rust target OS names.
|
||||
assert!(!os.is_empty(), "os must be non-empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_info_returns_non_zero_pid() {
|
||||
let outcome = system_info();
|
||||
let json = outcome
|
||||
.into_cli_compatible_json()
|
||||
.expect("serialization ok");
|
||||
let pid = json["pid"].as_u64().expect("pid is a u64");
|
||||
assert!(pid > 0, "pid must be greater than zero");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn health_snapshot_returns_serializable_value() {
|
||||
let outcome = health_snapshot();
|
||||
let json = outcome
|
||||
.into_cli_compatible_json()
|
||||
.expect("serialization ok");
|
||||
assert!(json.is_object(), "snapshot must be a JSON object");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,20 @@ use crate::core::{ControllerSchema, FieldSchema, TypeSchema};
|
||||
use crate::rpc::RpcOutcome;
|
||||
|
||||
pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
vec![schemas("snapshot")]
|
||||
vec![schemas("snapshot"), schemas("system_info")]
|
||||
}
|
||||
|
||||
pub fn all_registered_controllers() -> Vec<RegisteredController> {
|
||||
vec![RegisteredController {
|
||||
schema: schemas("snapshot"),
|
||||
handler: handle_snapshot,
|
||||
}]
|
||||
vec![
|
||||
RegisteredController {
|
||||
schema: schemas("snapshot"),
|
||||
handler: handle_snapshot,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("system_info"),
|
||||
handler: handle_system_info,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn schemas(function: &str) -> ControllerSchema {
|
||||
@@ -29,6 +35,39 @@ pub fn schemas(function: &str) -> ControllerSchema {
|
||||
required: true,
|
||||
}],
|
||||
},
|
||||
"system_info" => ControllerSchema {
|
||||
namespace: "health",
|
||||
function: "system_info",
|
||||
description:
|
||||
"Return static system information: app version, OS, architecture, and PID.",
|
||||
inputs: vec![],
|
||||
outputs: vec![
|
||||
FieldSchema {
|
||||
name: "version",
|
||||
ty: TypeSchema::String,
|
||||
comment: "Running core binary version (CARGO_PKG_VERSION).",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "os",
|
||||
ty: TypeSchema::String,
|
||||
comment: "Host operating system name (linux, macos, windows, …).",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "arch",
|
||||
ty: TypeSchema::String,
|
||||
comment: "CPU architecture (x86_64, aarch64, …).",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "pid",
|
||||
ty: TypeSchema::String,
|
||||
comment: "Current process ID.",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
_ => ControllerSchema {
|
||||
namespace: "health",
|
||||
function: "unknown",
|
||||
@@ -48,6 +87,10 @@ fn handle_snapshot(_params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async { to_json(crate::openhuman::health::rpc::health_snapshot()) })
|
||||
}
|
||||
|
||||
fn handle_system_info(_params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async { to_json(crate::openhuman::health::rpc::system_info()) })
|
||||
}
|
||||
|
||||
fn to_json<T: serde::Serialize>(outcome: RpcOutcome<T>) -> Result<Value, String> {
|
||||
outcome.into_cli_compatible_json()
|
||||
}
|
||||
@@ -57,13 +100,13 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn all_schemas_returns_one() {
|
||||
assert_eq!(all_controller_schemas().len(), 1);
|
||||
fn all_schemas_returns_two() {
|
||||
assert_eq!(all_controller_schemas().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_controllers_returns_one() {
|
||||
assert_eq!(all_registered_controllers().len(), 1);
|
||||
fn all_controllers_returns_two() {
|
||||
assert_eq!(all_registered_controllers().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -75,6 +118,16 @@ mod tests {
|
||||
assert!(!s.outputs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_info_schema() {
|
||||
let s = schemas("system_info");
|
||||
assert_eq!(s.namespace, "health");
|
||||
assert_eq!(s.function, "system_info");
|
||||
assert!(s.inputs.is_empty());
|
||||
// version, os, arch, pid
|
||||
assert_eq!(s.outputs.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_function_returns_unknown() {
|
||||
let s = schemas("bad");
|
||||
@@ -86,7 +139,10 @@ mod tests {
|
||||
fn schemas_and_controllers_match() {
|
||||
let s = all_controller_schemas();
|
||||
let c = all_registered_controllers();
|
||||
assert_eq!(s[0].function, c[0].schema.function);
|
||||
assert_eq!(s.len(), c.len());
|
||||
for (schema, controller) in s.iter().zip(c.iter()) {
|
||||
assert_eq!(schema.function, controller.schema.function);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -96,6 +152,18 @@ mod tests {
|
||||
assert!(result.unwrap().is_object());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_system_info_returns_json_object() {
|
||||
let result = handle_system_info(Map::new()).await;
|
||||
assert!(result.is_ok());
|
||||
let json = result.unwrap();
|
||||
assert!(json.is_object());
|
||||
assert!(json["version"].as_str().is_some());
|
||||
assert!(json["os"].as_str().is_some());
|
||||
assert!(json["arch"].as_str().is_some());
|
||||
assert!(json["pid"].as_u64().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_json_helper() {
|
||||
let outcome = RpcOutcome::single_log(serde_json::json!({"ok": true}), "log");
|
||||
|
||||
Reference in New Issue
Block a user