Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3ffed3c
bump version to 3.0.2
nick1udwig Aug 9, 2025
cd6c5f0
build: dont crash on input like `.`
nick1udwig Aug 9, 2025
8e4f778
Merge pull request #355 from hyperware-ai/hf/build-dont-crash-on-dot
nick1udwig Aug 9, 2025
5c421f5
build: fix debug
nick1udwig Aug 9, 2025
a4ac566
build: fix caller-utils spoiling fresh builds of repo
nick1udwig Aug 9, 2025
895eb2d
new: update chat ui client-api dep to avoid new build cache issues
nick1udwig Aug 11, 2025
7b2ed93
build: add #[ws_client]`
nick1udwig Aug 19, 2025
188fae1
build: generate hyperapp wit files in two stages to improve type capture
nick1udwig Aug 22, 2025
168e728
build: fix and update hyperapps
nick1udwig Aug 25, 2025
3131eba
Format Rust code using rustfmt
github-actions[bot] Aug 25, 2025
56e83ad
Merge pull request #356 from hyperware-ai/hf/build-fix-caller-utils-i…
nick1udwig Aug 26, 2025
cf1f025
Merge pull request #357 from hyperware-ai/hf/add-ws-client
nick1udwig Aug 26, 2025
eed0a92
Merge pull request #358 from hyperware-ai/hf/build-two-stage-wit-gene…
nick1udwig Aug 26, 2025
f206402
Merge pull request #359 from hyperware-ai/hf/build-fix-and-update-hyp…
nick1udwig Aug 26, 2025
e06b33a
publish: use same function as app store to compute keccak256
nick1udwig Aug 26, 2025
2865796
publish: add newline if none is there
nick1udwig Aug 26, 2025
afd6d1a
Merge pull request #360 from hyperware-ai/hf/publish-fix-hash-disagre…
nick1udwig Aug 26, 2025
3d73616
build: remove rust http stubs
nick1udwig Aug 26, 2025
9a9d97c
build: use snake case for ts caller-utils for consistency with rust &…
nick1udwig Aug 30, 2025
0a8abf2
build: fix ts caller-utils gen bug
nick1udwig Aug 30, 2025
9d632a3
Merge pull request #362 from hyperware-ai/hf/build-remove-rust-http-s…
nick1udwig Sep 1, 2025
60179df
Merge pull request #363 from hyperware-ai/hf/build-hyperapp-ts-snake-…
nick1udwig Sep 1, 2025
bc77a68
Merge pull request #365 from hyperware-ai/hf/build-hyperapp-fix-ts-ne…
nick1udwig Sep 1, 2025
d73a500
ignore eth handler for WIT generation
Gohlub Sep 3, 2025
0c780be
Format Rust code using rustfmt
github-actions[bot] Sep 3, 2025
47488ac
Merge pull request #367 from hyperware-ai/feat/ignore-eth-handler
Gohlub Sep 4, 2025
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
3 changes: 2 additions & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "kit"
authors = ["Sybil Technologies AG"]
version = "3.0.1"
version = "3.0.2"
edition = "2021"
description = "Development toolkit for Hyperware"
homepage = "https://hyperware.ai"
Expand Down Expand Up @@ -52,6 +52,7 @@ semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10.8"
sha3 = "0.10.8"
syn = { version = "2.0", features = ["full", "visit", "parsing", "extra-traits"] }
thiserror = "1.0"
tokio = { version = "1.28", features = [
Expand Down
147 changes: 28 additions & 119 deletions src/build/caller_utils_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,50 +142,6 @@ fn wit_type_to_rust(wit_type: &str) -> String {
}
}

// Generate default value for Rust type - IMPROVED with additional types
fn generate_default_value(rust_type: &str) -> String {
match rust_type {
// Integer types
"i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "isize" | "usize" => {
"0".to_string()
}
// Floating point types
"f32" | "f64" => "0.0".to_string(),
// String types
"String" => "String::new()".to_string(),
"&str" => "\"\"".to_string(),
// Other primitive types
"bool" => "false".to_string(),
"char" => "'\\0'".to_string(),
"()" => "()".to_string(),
// Collection types
t if t.starts_with("Vec<") => "Vec::new()".to_string(),
t if t.starts_with("Option<") => "None".to_string(),
t if t.starts_with("Result<") => {
// For Result, default to Ok with the default value of the success type
if let Some(success_type_end) = t.find(',') {
let success_type = &t[7..success_type_end];
format!("Ok({})", generate_default_value(success_type))
} else {
"Ok(())".to_string()
}
}
//t if t.starts_with("HashMap<") => "HashMap::new()".to_string(),
t if t.starts_with("(") => {
// Generate default tuple with default values for each element
let inner_part = t.trim_start_matches('(').trim_end_matches(')');
let parts: Vec<_> = inner_part.split(", ").collect();
let default_values: Vec<_> = parts
.iter()
.map(|part| generate_default_value(part))
.collect();
format!("({})", default_values.join(", "))
}
// For custom types, assume they implement Default
_ => format!("{}::default()", rust_type),
}
}

// Structure to represent a field in a WIT signature struct
#[derive(Debug)]
struct SignatureField {
Expand Down Expand Up @@ -346,7 +302,7 @@ fn parse_wit_file(file_path: &Path) -> Result<(Vec<SignatureStruct>, Vec<String>
}

// Generate a Rust async function from a signature struct
fn generate_async_function(signature: &SignatureStruct) -> String {
fn generate_async_function(signature: &SignatureStruct) -> Option<String> {
// Convert function name from kebab-case to snake_case
let snake_function_name = to_snake_case(&signature.function_name);

Expand Down Expand Up @@ -405,62 +361,14 @@ fn generate_async_function(signature: &SignatureStruct) -> String {

// For HTTP endpoints, generate commented-out implementation
if signature.attr_type == "http" {
debug!("Generating commented-out stub for HTTP endpoint");
let default_value = generate_default_value(&return_type);

// Add underscore prefix to all parameters for HTTP stubs
let all_params_with_underscore = if target_param.is_empty() {
params
.iter()
.map(|param| {
let parts: Vec<&str> = param.split(':').collect();
if parts.len() == 2 {
format!("_{}: {}", parts[0], parts[1])
} else {
warn!(param = %param, "Could not parse parameter for underscore prefix");
format!("_{}", param)
}
})
.collect::<Vec<String>>()
.join(", ")
} else {
let target_with_underscore = format!("_target: {}", target_param);
if params.is_empty() {
target_with_underscore
} else {
let params_with_underscore = params
.iter()
.map(|param| {
let parts: Vec<&str> = param.split(':').collect();
if parts.len() == 2 {
format!("_{}: {}", parts[0], parts[1])
} else {
warn!(param = %param, "Could not parse parameter for underscore prefix");
format!("_{}", param)
}
})
.collect::<Vec<String>>()
.join(", ");
format!("{}, {}", target_with_underscore, params_with_underscore)
}
};

return format!(
"// /// Generated stub for `{}` {} RPC call\n// /// HTTP endpoint - uncomment to implement\n// pub async fn {}({}) -> {} {{\n// // TODO: Implement HTTP endpoint\n// Ok({})\n// }}",
signature.function_name,
signature.attr_type,
full_function_name,
all_params_with_underscore,
wrapped_return_type,
default_value
);
return None;
}

// Format JSON parameters correctly
let json_params = if param_names.is_empty() {
// No parameters case
debug!("Generating JSON with no parameters");
format!("json!({{\"{}\" : {{}}}})", pascal_function_name)
format!("json!({{\"{}\" : null}})", pascal_function_name)
} else if param_names.len() == 1 {
// Single parameter case
debug!(param = %param_names[0], "Generating JSON with single parameter");
Expand All @@ -480,7 +388,7 @@ fn generate_async_function(signature: &SignatureStruct) -> String {

// Generate function with implementation using send
debug!("Generating standard RPC stub implementation");
format!(
Some(format!(
"/// Generated stub for `{}` {} RPC call\npub async fn {}({}) -> {} {{\n let body = {};\n let body = serde_json::to_vec(&body).unwrap();\n let request = Request::to(target)\n .body(body);\n send::<{}>(request).await\n}}",
signature.function_name,
signature.attr_type,
Expand All @@ -489,7 +397,7 @@ fn generate_async_function(signature: &SignatureStruct) -> String {
wrapped_return_type,
json_params,
return_type
)
))
}

// Create the caller-utils crate with a single lib.rs file
Expand All @@ -507,9 +415,9 @@ fn create_caller_utils_crate(api_dir: &Path, base_dir: &Path) -> Result<()> {
fs::create_dir_all(caller_utils_dir.join("src"))?;
debug!("Created project directory structure");

// Get hyperware_app_common dependency from the process's Cargo.toml
let hyperware_dep = get_hyperware_app_common_dependency(base_dir)?;
debug!("Got hyperware_app_common dependency: {}", hyperware_dep);
// Get hyperware_process_lib dependency from the process's Cargo.toml
let hyperware_dep = get_hyperware_process_lib_dependency(base_dir)?;
debug!("Got hyperware_process_lib dependency: {}", hyperware_dep);

// Create Cargo.toml with updated dependencies
let cargo_toml = format!(
Expand All @@ -525,7 +433,7 @@ process_macros = "0.1.0"
futures-util = "0.3"
serde = {{ version = "1.0", features = ["derive"] }}
serde_json = "1.0"
hyperware_app_common = {}
hyperware_process_lib = {}
once_cell = "1.20.2"
futures = "0.3"
uuid = {{ version = "1.0" }}
Expand Down Expand Up @@ -621,9 +529,10 @@ crate-type = ["cdylib", "lib"]

// Add function implementations
for signature in &signatures {
let function_impl = generate_async_function(signature);
mod_content.push_str(&function_impl);
mod_content.push_str("\n\n");
if let Some(function_impl) = generate_async_function(signature) {
mod_content.push_str(&function_impl);
mod_content.push_str("\n\n");
}
}

// Store the module content
Expand Down Expand Up @@ -672,9 +581,8 @@ crate-type = ["cdylib", "lib"]
lib_rs.push_str("/// Generated caller utilities for RPC function stubs\n\n");

// Add global imports
lib_rs.push_str("pub use hyperware_app_common::AppSendError;\n");
lib_rs.push_str("pub use hyperware_app_common::send;\n");
lib_rs.push_str("use hyperware_app_common::hyperware_process_lib as hyperware_process_lib;\n");
lib_rs.push_str("pub use hyperware_process_lib::hyperapp::AppSendError;\n");
lib_rs.push_str("pub use hyperware_process_lib::hyperapp::send;\n");
lib_rs.push_str("pub use hyperware_process_lib::{Address, Request};\n");
lib_rs.push_str("use serde_json::json;\n\n");

Expand Down Expand Up @@ -799,9 +707,9 @@ fn read_cargo_toml(path: &Path) -> Result<Value> {
.with_context(|| format!("Failed to parse Cargo.toml: {}", path.display()))
}

// Get hyperware_app_common dependency from the process Cargo.toml files
// Get hyperware_process_lib dependency from the process Cargo.toml files
#[instrument(level = "trace", skip_all)]
fn get_hyperware_app_common_dependency(base_dir: &Path) -> Result<String> {
fn get_hyperware_process_lib_dependency(base_dir: &Path) -> Result<String> {
const DEFAULT_DEP: &str =
r#"{ git = "https://github.com/hyperware-ai/hyperprocess-macro", rev = "4c944b2" }"#;

Expand All @@ -813,7 +721,7 @@ fn get_hyperware_app_common_dependency(base_dir: &Path) -> Result<String> {
.and_then(|m| m.as_array())
.ok_or_else(|| eyre!("No workspace.members found in Cargo.toml"))?;

// Collect hyperware_app_common dependencies from all process members
// Collect hyperware_process_lib dependencies from all process members
let mut found_deps = HashMap::new();

for member in members.iter().filter_map(|m| m.as_str()) {
Expand All @@ -835,23 +743,23 @@ fn get_hyperware_app_common_dependency(base_dir: &Path) -> Result<String> {

if let Some(dep) = member_toml
.get("dependencies")
.and_then(|d| d.get("hyperware_app_common"))
.and_then(|d| d.get("hyperware_process_lib"))
.and_then(format_toml_dependency)
{
debug!("Found hyperware_app_common in {}: {}", member, dep);
debug!("Found hyperware_process_lib in {}: {}", member, dep);
found_deps.insert(member.to_string(), dep);
}
}

// Handle results
match found_deps.len() {
0 => {
warn!("No hyperware_app_common dependencies found in any process, using default");
warn!("No hyperware_process_lib dependencies found in any process, using default");
Ok(DEFAULT_DEP.to_string())
}
1 => {
let dep = found_deps.values().next().unwrap();
info!("Using hyperware_app_common dependency: {}", dep);
info!("Using hyperware_process_lib dependency: {}", dep);
Ok(dep.clone())
}
_ => {
Expand All @@ -865,13 +773,13 @@ fn get_hyperware_app_common_dependency(base_dir: &Path) -> Result<String> {
found_deps.iter().find(|(_, d)| *d == first_dep).unwrap();
let (conflict_process, _) = found_deps.iter().find(|(_, d)| *d == dep).unwrap();
bail!(
"Conflicting hyperware_app_common versions found:\n Process '{}': {}\n Process '{}': {}\nAll processes must use the same version.",
"Conflicting hyperware_process_lib versions found:\n Process '{}': {}\n Process '{}': {}\nAll processes must use the same version.",
first_process, first_dep, conflict_process, dep
);
}
}

info!("Using hyperware_app_common dependency: {}", first_dep);
info!("Using hyperware_process_lib dependency: {}", first_dep);
Ok(first_dep.clone())
}
}
Expand Down Expand Up @@ -913,10 +821,10 @@ fn update_workspace_cargo_toml(base_dir: &Path) -> Result<()> {
// Check if caller-utils is already in the members list
let caller_utils_exists = members_array
.iter()
.any(|m| m.as_str().map_or(false, |s| s == "target/caller-utils"));
.any(|m| m.as_str().map_or(false, |s| s == "target/caller-util?"));

if !caller_utils_exists {
members_array.push(Value::String("target/caller-utils".to_string()));
members_array.push(Value::String("target/caller-util?".to_string()));

// Write back the updated TOML
let updated_content = toml::to_string_pretty(&parsed_toml)
Expand All @@ -932,7 +840,7 @@ fn update_workspace_cargo_toml(base_dir: &Path) -> Result<()> {
debug!("Successfully updated workspace Cargo.toml");
} else {
debug!(
"Workspace Cargo.toml already up-to-date regarding caller-utils member."
"Workspace Cargo.toml already up-to-date regarding caller-util? member."
);
}
}
Expand Down Expand Up @@ -979,6 +887,7 @@ pub fn add_caller_utils_to_projects(projects: &[PathBuf]) -> Result<()> {
"path".to_string(),
Value::String("../target/caller-utils".to_string()),
);
t.insert("optional".to_string(), Value::Boolean(true));
t
}),
);
Expand Down
Loading