diff --git a/Cargo.lock b/Cargo.lock index 83b5daa36..eb272758c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11375,9 +11375,9 @@ dependencies = [ [[package]] name = "wasm-rquickjs" -version = "0.0.4" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "988766af53dcca920d774678f1f3bf561d442c9285465392563acb3f2d148a39" +checksum = "88ee824d9459a4c8f707c24d81af71e9a07e7aa19b1a7f1c15eee207fe151b7e" dependencies = [ "anyhow", "camino", @@ -11389,8 +11389,9 @@ dependencies = [ "quote", "syn 2.0.104", "toml_edit", - "wit-bindgen-core 0.42.1", - "wit-parser 0.230.0", + "wit-bindgen-core 0.43.0", + "wit-encoder", + "wit-parser 0.235.0", ] [[package]] @@ -11503,17 +11504,6 @@ dependencies = [ "serde", ] -[[package]] -name = "wasmparser" -version = "0.230.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808198a69b5a0535583370a51d459baa14261dfab04800c4864ee9e1a14346ed" -dependencies = [ - "bitflags 2.9.1", - "indexmap 2.10.0", - "semver", -] - [[package]] name = "wasmparser" version = "0.235.0" @@ -12464,17 +12454,6 @@ dependencies = [ "wit-parser 0.227.1", ] -[[package]] -name = "wit-bindgen-core" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35e550f614e16db196e051d22b0d4c94dd6f52c90cb1016240f71b9db332631" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser 0.230.0", -] - [[package]] name = "wit-bindgen-core" version = "0.43.0" @@ -12727,24 +12706,6 @@ dependencies = [ "wasmparser 0.229.0", ] -[[package]] -name = "wit-parser" -version = "0.230.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "679fde5556495f98079a8e6b9ef8c887f731addaffa3d48194075c1dd5cd611b" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.10.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.230.0", -] - [[package]] name = "wit-parser" version = "0.235.0" diff --git a/Cargo.toml b/Cargo.toml index 6eea5d84f..ec9e92e04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["golem", "golem-cli", "golem-templates"] -exclude = ["desktop-app/src-tauri"] +exclude = ["desktop-app/src-tauri", "template-golem-agent-ts"] [workspace.metadata] license-file = "LICENSE" @@ -125,7 +125,7 @@ wax = "0.6.0" wasm-metadata = { version = "0.228", features = ["oci"] } wasmparser = "0.235.0" wasm-encoder = "0.235.0" -wasm-rquickjs = "0.0.4" +wasm-rquickjs = "0.0.5" wasmtime = { version = "33.0.0", features = ["async", "component-model"] } wasmtime-wasi = { version = "33.0.0" } wit-bindgen = "0.43.0" diff --git a/Makefile.toml b/Makefile.toml index e2a9e2171..c5a214758 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -259,3 +259,16 @@ description = "Serve the schema.golem.cloud directory locally to help testing th install_crate = "miniserve" command = "miniserve" args = ["--interfaces", "127.0.0.1", "--port", "41357", "schema.golem.cloud"] + +## Agent template management +[tasks.rebuild-ts-agent-template] +description = "Rebuilds the TypeScript agent template WASM" +script_runner = "@duckscript" +script = ''' +cd template-golem-agent-ts +exec --fail-on-error npm update +exec --fail-on-error npm install +exec --fail-on-error npm run generate +exec --fail-on-error npm run build +cp dist/wrapper/target/wasm32-wasip1/release/golem_agent.wasm ../golem-templates/templates/ts/ts-agent-app-common/wasm/golem_agent.wasm +''' \ No newline at end of file diff --git a/golem-cli/src/app/build/command.rs b/golem-cli/src/app/build/command.rs index c8ef10aba..f951ac17c 100644 --- a/golem-cli/src/app/build/command.rs +++ b/golem-cli/src/app/build/command.rs @@ -21,13 +21,158 @@ use crate::app::context::ApplicationContext; use crate::app::error::CustomCommandError; use crate::fs::compile_and_collect_globs; use crate::log::{log_action, log_skipping_up_to_date, LogColorize, LogIndent}; +use crate::model::app::AppComponentName; use crate::model::app_raw; +use crate::model::app_raw::{ + ComposeAgentWrapper, GenerateAgentWrapper, GenerateQuickJSCrate, GenerateQuickJSDTS, + InjectToPrebuiltQuickJs, +}; +use crate::wasm_rpc_stubgen::commands; use anyhow::{anyhow, Context}; use camino::Utf8Path; use std::collections::HashMap; use std::path::Path; use std::process::Command; use tracing::debug; +use wasm_rquickjs::{EmbeddingMode, JsModuleSpec}; + +pub async fn execute_build_command( + ctx: &mut ApplicationContext, + component_name: &AppComponentName, + command: &app_raw::BuildCommand, + additional_env_vars: HashMap, +) -> anyhow::Result<()> { + let base_build_dir = ctx + .application + .component_source_dir(component_name) + .to_path_buf(); + match command { + app_raw::BuildCommand::External(external_command) => { + execute_external_command(ctx, &base_build_dir, external_command, additional_env_vars) + } + app_raw::BuildCommand::QuickJSCrate(command) => { + execute_quickjs_create(ctx, &base_build_dir, command) + } + app_raw::BuildCommand::QuickJSDTS(command) => { + execute_quickjs_d_ts(ctx, &base_build_dir, command) + } + app_raw::BuildCommand::AgentWrapper(command) => { + execute_agent_wrapper(ctx, component_name, &base_build_dir, command).await + } + app_raw::BuildCommand::ComposeAgentWrapper(command) => { + execute_compose_agent_wrapper(&base_build_dir, command).await + } + app_raw::BuildCommand::InjectToPrebuiltQuickJs(command) => { + execute_inject_to_prebuilt_quick_js(&base_build_dir, command).await + } + } +} + +async fn execute_agent_wrapper( + ctx: &mut ApplicationContext, + component_name: &AppComponentName, + base_build_dir: &Path, + command: &GenerateAgentWrapper, +) -> anyhow::Result<()> { + let base_build_dir = Utf8Path::from_path(base_build_dir).unwrap(); + let wrapper_wasm_path = base_build_dir.join(&command.generate_agent_wrapper); + let compiled_wasm_path = base_build_dir.join(&command.based_on_compiled_wasm); + + log_action( + "Generating", + format!( + "agent wrapper for {}", + component_name.to_string().log_color_highlight() + ), + ); + let _indent = LogIndent::new(); + + let agent_types = ctx + .wit + .get_extracted_agent_types(component_name, compiled_wasm_path.as_std_path()) + .await?; + + log_action( + "Generating", + format!( + "agent WIT interface for {}", + component_name.to_string().log_color_highlight() + ), + ); + + let wrapper_context = + crate::model::agent::wit::generate_agent_wrapper_wit(component_name, &agent_types)?; + + log_action( + "Generating", + format!( + "agent WIT interface implementation to {}", + wrapper_wasm_path.to_string().log_color_highlight() + ), + ); + + crate::model::agent::moonbit::generate_moonbit_wrapper( + wrapper_context, + wrapper_wasm_path.as_std_path(), + )?; + + Ok(()) +} + +async fn execute_compose_agent_wrapper( + base_build_dir: &Path, + command: &ComposeAgentWrapper, +) -> anyhow::Result<()> { + let base_build_dir = Utf8Path::from_path(base_build_dir).unwrap(); + let wrapper_wasm_path = base_build_dir.join(&command.compose_agent_wrapper); + let user_component = base_build_dir.join(&command.with_agent); + let target_component = base_build_dir.join(&command.to); + + commands::composition::compose( + wrapper_wasm_path.as_std_path(), + &[user_component.as_std_path().to_path_buf()], + target_component.as_std_path(), + ) + .await?; + + Ok(()) +} + +async fn execute_inject_to_prebuilt_quick_js( + base_build_dir: &Path, + command: &InjectToPrebuiltQuickJs, +) -> anyhow::Result<()> { + let base_build_dir = Utf8Path::from_path(base_build_dir).unwrap(); + let base_wasm = base_build_dir.join(&command.inject_to_prebuilt_quickjs); + let js_module = base_build_dir.join(&command.module); + let js_module_contents = std::fs::read_to_string(&js_module) + .with_context(|| format!("Failed to read JS module from {js_module}"))?; + let js_module_wasm = base_build_dir.join(&command.module_wasm); + let target = base_build_dir.join(&command.into); + + log_action( + "Injecting", + format!( + "JS module {} into QuickJS WASM {}", + js_module.log_color_highlight(), + base_wasm.log_color_highlight() + ), + ); + + moonbit_component_generator::get_script::generate_get_script_component( + &js_module_contents, + &js_module_wasm, + )?; + + commands::composition::compose( + base_wasm.as_std_path(), + &[js_module_wasm.as_std_path().to_path_buf()], + target.as_std_path(), + ) + .await?; + + Ok(()) +} pub fn execute_custom_command( ctx: &ApplicationContext, @@ -94,109 +239,116 @@ pub fn execute_custom_command( Ok(()) } -pub fn execute_build_command( +fn execute_quickjs_create( ctx: &ApplicationContext, base_build_dir: &Path, - command: &app_raw::BuildCommand, - additional_env_vars: HashMap, + command: &GenerateQuickJSCrate, ) -> anyhow::Result<()> { - match command { - app_raw::BuildCommand::External(external_command) => { - execute_external_command(ctx, base_build_dir, external_command, additional_env_vars) - } - app_raw::BuildCommand::QuickJSCrate(command) => { - let base_build_dir = Utf8Path::from_path(base_build_dir).unwrap(); - let wit = base_build_dir.join(&command.wit); - let js = base_build_dir.join(&command.js); - let generate_quickjs_crate = base_build_dir.join(&command.generate_quickjs_crate); - - let task_result_marker = TaskResultMarker::new( - &ctx.application.task_result_marker_dir(), - GenerateQuickJSCrateCommandMarkerHash { - build_dir: base_build_dir.as_std_path(), - command, - }, - )?; - - let skip_up_to_date_checks = - ctx.config.skip_up_to_date_checks || !task_result_marker.is_up_to_date(); - - if is_up_to_date( - skip_up_to_date_checks, - || { - vec![ - wit.clone().into_std_path_buf(), - js.clone().into_std_path_buf(), - ] - }, - || vec![generate_quickjs_crate.clone().into_std_path_buf()], - ) { - log_skipping_up_to_date(format!( - "executing WASM RQuickJS wrapper generator in directory {}", - base_build_dir.log_color_highlight() - )); - return Ok(()); - } + let base_build_dir = Utf8Path::from_path(base_build_dir).unwrap(); + let wit = base_build_dir.join(&command.wit); + let generate_quickjs_crate = base_build_dir.join(&command.generate_quickjs_crate); + + let mut js_modules = Vec::new(); + let mut js_paths = Vec::new(); + for (name, spec) in &command.js_modules { + let mode = if spec == "@composition" { + EmbeddingMode::Composition + } else { + let js = base_build_dir.join(spec); + js_paths.push(js.clone().into_std_path_buf()); + EmbeddingMode::EmbedFile(js) + }; + js_modules.push(JsModuleSpec { + name: name.clone(), + mode, + }); + } - log_action( - "Executing", - format!( - "WASM RQuickJS wrapper generator in directory {}", - base_build_dir.log_color_highlight() - ), - ); + let task_result_marker = TaskResultMarker::new( + &ctx.application.task_result_marker_dir(), + GenerateQuickJSCrateCommandMarkerHash { + build_dir: base_build_dir.as_std_path(), + command, + }, + )?; - task_result_marker.result({ - wasm_rquickjs::generate_wrapper_crate( - &wit, - &js, - &generate_quickjs_crate, - command.world.as_deref(), - ) - }) - } - app_raw::BuildCommand::QuickJSDTS(command) => { - let base_build_dir = Utf8Path::from_path(base_build_dir).unwrap(); - let wit = &base_build_dir.join(&command.wit); - let generate_quickjs_dts = &base_build_dir.join(&command.generate_quickjs_dts); - - let task_result_marker = TaskResultMarker::new( - &ctx.application.task_result_marker_dir(), - GenerateQuickJSDTSCommandMarkerHash { - build_dir: base_build_dir.as_std_path(), - command, - }, - )?; - - let skip_up_to_date_checks = - ctx.config.skip_up_to_date_checks || !task_result_marker.is_up_to_date(); - - if is_up_to_date( - skip_up_to_date_checks, - || vec![wit.clone().into_std_path_buf()], - || vec![generate_quickjs_dts.clone().into_std_path_buf()], - ) { - log_skipping_up_to_date(format!( - "executing WASM RQuickJS d.ts generator in directory {}", - base_build_dir.log_color_highlight() - )); - return Ok(()); - } + let skip_up_to_date_checks = + ctx.config.skip_up_to_date_checks || !task_result_marker.is_up_to_date(); - log_action( - "Executing", - format!( - "WASM RQuickJS d.ts generator in directory {}", - base_build_dir.log_color_highlight() - ), - ); + if is_up_to_date( + skip_up_to_date_checks, + || [vec![wit.clone().into_std_path_buf()], js_paths].concat(), + || vec![generate_quickjs_crate.clone().into_std_path_buf()], + ) { + log_skipping_up_to_date(format!( + "executing WASM RQuickJS wrapper generator in directory {}", + base_build_dir.log_color_highlight() + )); + return Ok(()); + } - task_result_marker.result({ - wasm_rquickjs::generate_dts(wit, generate_quickjs_dts, command.world.as_deref()) - .context("Failed to generate QuickJS DTS") - }) - } + log_action( + "Executing", + format!( + "WASM RQuickJS wrapper generator in directory {}", + base_build_dir.log_color_highlight() + ), + ); + + task_result_marker.result({ + wasm_rquickjs::generate_wrapper_crate( + &wit, + &js_modules, + &generate_quickjs_crate, + command.world.as_deref(), + ) + }) +} + +fn execute_quickjs_d_ts( + ctx: &ApplicationContext, + base_build_dir: &Path, + command: &GenerateQuickJSDTS, +) -> anyhow::Result<()> { + let base_build_dir = Utf8Path::from_path(base_build_dir).unwrap(); + let wit = &base_build_dir.join(&command.wit); + let generate_quickjs_dts = &base_build_dir.join(&command.generate_quickjs_dts); + + let task_result_marker = TaskResultMarker::new( + &ctx.application.task_result_marker_dir(), + GenerateQuickJSDTSCommandMarkerHash { + build_dir: base_build_dir.as_std_path(), + command, + }, + )?; + + let skip_up_to_date_checks = + ctx.config.skip_up_to_date_checks || !task_result_marker.is_up_to_date(); + + if is_up_to_date( + skip_up_to_date_checks, + || vec![wit.clone().into_std_path_buf()], + || vec![generate_quickjs_dts.clone().into_std_path_buf()], + ) { + log_skipping_up_to_date(format!( + "executing WASM RQuickJS d.ts generator in directory {}", + base_build_dir.log_color_highlight() + )); + return Ok(()); } + + log_action( + "Executing", + format!( + "WASM RQuickJS d.ts generator in directory {}", + base_build_dir.log_color_highlight() + ), + ); + + task_result_marker.result({ + wasm_rquickjs::generate_dts(wit, generate_quickjs_dts, command.world.as_deref()) + .context("Failed to generate QuickJS DTS") + }) } pub fn execute_external_command( diff --git a/golem-cli/src/app/build/componentize.rs b/golem-cli/src/app/build/componentize.rs index a7d68a867..3558442b5 100644 --- a/golem-cli/src/app/build/componentize.rs +++ b/golem-cli/src/app/build/componentize.rs @@ -16,12 +16,9 @@ use crate::app::build::command::execute_build_command; use crate::app::context::ApplicationContext; use crate::log::{log_action, log_warn_action, LogColorize, LogIndent}; use crate::model::app::{AppComponentName, DependencyType}; -use crate::wasm_rpc_stubgen::wit_resolve::ExportedFunction; -use anyhow::{anyhow, Context}; -use heck::ToLowerCamelCase; use std::collections::{BTreeSet, HashMap}; -pub fn componentize(ctx: &mut ApplicationContext) -> anyhow::Result<()> { +pub async fn componentize(ctx: &mut ApplicationContext) -> anyhow::Result<()> { log_action("Building", "components"); let _indent = LogIndent::new(); @@ -48,16 +45,9 @@ pub fn componentize(ctx: &mut ApplicationContext) -> anyhow::Result<()> { ); let _indent = LogIndent::new(); - let env_vars = build_step_env_vars(ctx, &component_name) - .context("Failed to get env vars for build step")?; - - for build_step in &component_properties.build { - execute_build_command( - ctx, - ctx.application.component_source_dir(&component_name), - build_step, - env_vars.clone(), - )?; + let env_vars = HashMap::new(); + for build_step in component_properties.build.clone() { + execute_build_command(ctx, &component_name, &build_step, env_vars.clone()).await?; } } @@ -84,62 +74,3 @@ fn components_to_build(ctx: &ApplicationContext) -> BTreeSet { } components_to_build } - -fn build_step_env_vars( - ctx: &ApplicationContext, - component_name: &AppComponentName, -) -> anyhow::Result> { - let result = HashMap::from_iter(vec![( - "JCO_ASYNC_EXPORT_ARGS".to_string(), - jco_async_export_args(ctx, component_name)?.join(" "), - )]); - - Ok(result) -} - -fn jco_async_export_args( - ctx: &ApplicationContext, - component_name: &AppComponentName, -) -> anyhow::Result> { - let resolved = ctx - .wit - .component(component_name)? - .generated_wit_dir() - .ok_or(anyhow!("Failed to get generated wit dir"))?; - - let exported_functions = resolved.exported_functions().context(format!( - "Failed to look up exported_functions for component {component_name}" - ))?; - - let mut result = Vec::new(); - - for function in exported_functions { - match function { - ExportedFunction::Interface { - interface_name, - function_name, - } => { - // This is not a typo, it's a workaround for https://github.com/bytecodealliance/jco/issues/622 - result.push("--async-imports".to_string()); - result.push(format!("{interface_name}#{function_name}")); - } - ExportedFunction::InlineInterface { - export_name, - function_name, - } => { - // This is not a typo, it's a workaround for https://github.com/bytecodealliance/jco/issues/622 - result.push("--async-imports".to_string()); - let transformed = export_name.to_lower_camel_case(); - result.push(format!("{transformed}#{function_name}")); - } - ExportedFunction::InlineFunction { - world_name, - function_name, - } => { - result.push("--async-exports".to_string()); - result.push(format!("{world_name}#{function_name}")); - } - } - } - Ok(result) -} diff --git a/golem-cli/src/app/build/gen_rpc.rs b/golem-cli/src/app/build/gen_rpc.rs index 215b9ed9f..2c2ee2640 100644 --- a/golem-cli/src/app/build/gen_rpc.rs +++ b/golem-cli/src/app/build/gen_rpc.rs @@ -17,7 +17,7 @@ use crate::app::build::{delete_path_logged, env_var_flag, is_up_to_date}; use crate::app::context::ApplicationContext; use crate::fs; use crate::fs::PathExtra; -use crate::log::{log_action, log_skipping_up_to_date, LogColorize, LogIndent}; +use crate::log::{log_action, log_skipping_up_to_date, log_warn_action, LogColorize, LogIndent}; use crate::model::app::{ AppComponentName, BinaryComponentSource, DependencyType, DependentAppComponent, }; @@ -81,167 +81,181 @@ async fn create_generated_base_wit( let component_source_wit = ctx .application .component_source_wit(component_name, ctx.build_profile()); - let inputs = { - let mut inputs = ctx.application.wit_deps(); - inputs.push(component_source_wit.clone()); - inputs - }; - let component_generated_base_wit = ctx.application.component_generated_base_wit(component_name); - let task_result_marker = TaskResultMarker::new( - &ctx.application.task_result_marker_dir(), - ComponentGeneratorMarkerHash { - component_name, - generator_kind: "base_wit", - }, - )?; - if is_up_to_date( - ctx.config.skip_up_to_date_checks - || !task_result_marker.is_up_to_date() - || !ctx.wit.is_dep_graph_up_to_date(component_name)?, - || inputs, - || [component_generated_base_wit.clone()], - ) { - log_skipping_up_to_date(format!( - "creating generated base wit directory for {}", - component_name.as_str().log_color_highlight() - )); - Ok(false) - } else { - log_action( - "Creating", - format!( - "generated base wit directory for {}", - component_name.as_str().log_color_highlight(), - ), - ); + if component_source_wit.is_dir() { + let inputs = { + let mut inputs = ctx.application.wit_deps(); + inputs.push(component_source_wit.clone()); + inputs + }; + let component_generated_base_wit = + ctx.application.component_generated_base_wit(component_name); + let task_result_marker = TaskResultMarker::new( + &ctx.application.task_result_marker_dir(), + ComponentGeneratorMarkerHash { + component_name, + generator_kind: "base_wit", + }, + )?; - task_result_marker.result( - (async { - let _indent = LogIndent::new(); + if is_up_to_date( + ctx.config.skip_up_to_date_checks + || !task_result_marker.is_up_to_date() + || !ctx.wit.is_dep_graph_up_to_date(component_name)?, + || inputs, + || [component_generated_base_wit.clone()], + ) { + log_skipping_up_to_date(format!( + "creating generated base wit directory for {}", + component_name.as_str().log_color_highlight() + )); + Ok(false) + } else { + log_action( + "Creating", + format!( + "generated base wit directory for {}", + component_name.as_str().log_color_highlight(), + ), + ); + + task_result_marker.result( + (async { + let _indent = LogIndent::new(); - delete_path_logged( - "generated base wit directory", - &component_generated_base_wit, - )?; - copy_wit_sources(&component_source_wit, &component_generated_base_wit)?; - - let mut missing_package_deps = ctx - .wit - .missing_generic_source_package_deps(component_name)?; - let mut packages_from_lib_deps = BTreeSet::new(); - - { - let library_dependencies = ctx - .application - .component_dependencies(component_name) - .iter() - .filter(|dep| dep.dep_type == DependencyType::Wasm) - .collect::>(); - - if !library_dependencies.is_empty() { - log_action( - "Extracting", - format!( - "WIT interface of library dependencies to {}", - component_generated_base_wit.log_color_highlight() - ), - ); - let _indent = LogIndent::new(); - for library_dep in &library_dependencies { - // TODO: adding WIT packages from AppComponent wasm dependencies is not supported yet (we don't have a compiled WASM for them at this point) - if !matches!( - library_dep.source, - BinaryComponentSource::AppComponent { .. } - ) { - let path = ctx.resolve_binary_component_source(library_dep).await?; - let result = extract_wasm_interface_as_wit_dep( - ctx.common_wit_deps()?, - &library_dep.source.to_string(), - &path, - &component_generated_base_wit, - ) - .with_context(|| { - format!( - "Failed to extract WIT interface of library dependency {}", - library_dep.source.to_string().log_color_highlight() + delete_path_logged( + "generated base wit directory", + &component_generated_base_wit, + )?; + copy_wit_sources(&component_source_wit, &component_generated_base_wit)?; + + let mut missing_package_deps = ctx + .wit + .missing_generic_source_package_deps(component_name)?; + let mut packages_from_lib_deps = BTreeSet::new(); + + { + let library_dependencies = ctx + .application + .component_dependencies(component_name) + .iter() + .filter(|dep| dep.dep_type == DependencyType::Wasm) + .collect::>(); + + if !library_dependencies.is_empty() { + log_action( + "Extracting", + format!( + "WIT interface of library dependencies to {}", + component_generated_base_wit.log_color_highlight() + ), + ); + let _indent = LogIndent::new(); + for library_dep in &library_dependencies { + // TODO: adding WIT packages from AppComponent wasm dependencies is not supported yet (we don't have a compiled WASM for them at this point) + if !matches!( + library_dep.source, + BinaryComponentSource::AppComponent { .. } + ) { + let path = ctx.resolve_binary_component_source(library_dep).await?; + let result = extract_wasm_interface_as_wit_dep( + ctx.common_wit_deps()?, + &library_dep.source.to_string(), + &path, + &component_generated_base_wit, ) - })?; - packages_from_lib_deps.extend(result.new_packages); - missing_package_deps.extend(result.required_common_packages); + .with_context(|| { + format!( + "Failed to extract WIT interface of library dependency {}", + library_dep.source.to_string().log_color_highlight() + ) + })?; + packages_from_lib_deps.extend(result.new_packages); + missing_package_deps.extend(result.required_common_packages); + } } } } - } - { - missing_package_deps.retain(|name| !packages_from_lib_deps.contains(name)); + { + missing_package_deps.retain(|name| !packages_from_lib_deps.contains(name)); - if !missing_package_deps.is_empty() { - log_action("Adding", "package deps"); - let _indent = LogIndent::new(); + if !missing_package_deps.is_empty() { + log_action("Adding", "package deps"); + let _indent = LogIndent::new(); - ctx.common_wit_deps() - .with_context(|| { - format!( - "Failed to add package dependencies for {}, missing packages: {}", - component_name.as_str().log_color_highlight(), - missing_package_deps - .iter() - .map(|s| s.to_string().log_color_highlight()) - .join(", ") - ) - })? - .add_packages_with_transitive_deps_to_wit_dir( - &missing_package_deps, - &component_generated_base_wit, - ) - .with_context(|| { - format!( - "Failed to add package dependencies for {} ({})", - component_name.as_str().log_color_highlight(), - component_source_wit.log_color_highlight() + ctx.common_wit_deps() + .with_context(|| { + format!( + "Failed to add package dependencies for {}, missing packages: {}", + component_name.as_str().log_color_highlight(), + missing_package_deps + .iter() + .map(|s| s.to_string().log_color_highlight()) + .join(", ") + ) + })? + .add_packages_with_transitive_deps_to_wit_dir( + &missing_package_deps, + &component_generated_base_wit, ) - })?; + .with_context(|| { + format!( + "Failed to add package dependencies for {} ({})", + component_name.as_str().log_color_highlight(), + component_source_wit.log_color_highlight() + ) + })?; + } } - } - - { - let component_exports_package_deps = - ctx.wit.component_exports_package_deps(component_name)?; - if !component_exports_package_deps.is_empty() { - log_action("Adding", "component exports package dependencies"); - let _indent = LogIndent::new(); - for (dep_exports_package_name, dep_component_name) in - &component_exports_package_deps - { - ctx.component_base_output_wit_deps(dep_component_name)? - .add_packages_with_transitive_deps_to_wit_dir( - &[dep_exports_package_name.clone()], - &component_generated_base_wit, - )?; + { + let component_exports_package_deps = + ctx.wit.component_exports_package_deps(component_name)?; + if !component_exports_package_deps.is_empty() { + log_action("Adding", "component exports package dependencies"); + let _indent = LogIndent::new(); + + for (dep_exports_package_name, dep_component_name) in + &component_exports_package_deps + { + ctx.component_base_output_wit_deps(dep_component_name)? + .add_packages_with_transitive_deps_to_wit_dir( + &[dep_exports_package_name.clone()], + &component_generated_base_wit, + )?; + } } } - } - { - log_action( - "Extracting", - format!( - "exports package from {} to {}", - component_source_wit.log_color_highlight(), - component_generated_base_wit.log_color_highlight() - ), - ); - let _indent = LogIndent::new(); - extract_exports_as_wit_dep(&component_generated_base_wit)? - } + { + log_action( + "Extracting", + format!( + "exports package from {} to {}", + component_source_wit.log_color_highlight(), + component_generated_base_wit.log_color_highlight() + ), + ); + let _indent = LogIndent::new(); + extract_exports_as_wit_dep(&component_generated_base_wit)? + } - Ok(true) - }) - .await, - ) + Ok(true) + }) + .await, + ) + } + } else { + log_warn_action( + "Skipping", + format!( + "creating generated base wit directory for {}, {}", + component_name.as_str().log_color_highlight(), + "source WIT points to a WASM component".log_color_ok_highlight() + ), + ); + Ok(false) } } @@ -250,45 +264,57 @@ fn create_generated_wit( component_name: &AppComponentName, ) -> Result { let component_generated_base_wit = ctx.application.component_generated_base_wit(component_name); - let component_generated_wit = ctx - .application - .component_generated_wit(component_name, ctx.build_profile()); - let task_result_marker = TaskResultMarker::new( - &ctx.application.task_result_marker_dir(), - ComponentGeneratorMarkerHash { - component_name, - generator_kind: "wit", - }, - )?; + if component_generated_base_wit.exists() { + let component_generated_wit = ctx + .application + .component_generated_wit(component_name, ctx.build_profile()); + let task_result_marker = TaskResultMarker::new( + &ctx.application.task_result_marker_dir(), + ComponentGeneratorMarkerHash { + component_name, + generator_kind: "wit", + }, + )?; - if is_up_to_date( - ctx.config.skip_up_to_date_checks - || !task_result_marker.is_up_to_date() - || !ctx.wit.is_dep_graph_up_to_date(component_name)?, - || [component_generated_base_wit.clone()], - || [component_generated_wit.clone()], - ) { - log_skipping_up_to_date(format!( - "creating generated wit directory for {}", - component_name.as_str().log_color_highlight() - )); - Ok(false) + if is_up_to_date( + ctx.config.skip_up_to_date_checks + || !task_result_marker.is_up_to_date() + || !ctx.wit.is_dep_graph_up_to_date(component_name)?, + || [component_generated_base_wit.clone()], + || [component_generated_wit.clone()], + ) { + log_skipping_up_to_date(format!( + "creating generated wit directory for {}", + component_name.as_str().log_color_highlight() + )); + Ok(false) + } else { + log_action( + "Creating", + format!( + "generated wit directory for {}", + component_name.as_str().log_color_highlight(), + ), + ); + + task_result_marker.result((|| { + let _indent = LogIndent::new(); + delete_path_logged("generated wit directory", &component_generated_wit)?; + copy_wit_sources(&component_generated_base_wit, &component_generated_wit)?; + add_client_deps(ctx, component_name)?; + Ok(true) + })()) + } } else { - log_action( - "Creating", + log_warn_action( + "Skipping", format!( - "generated wit directory for {}", + "creating generated wit directory for {}, {}", component_name.as_str().log_color_highlight(), + "no base WIT directory".log_color_ok_highlight() ), ); - - task_result_marker.result((|| { - let _indent = LogIndent::new(); - delete_path_logged("generated wit directory", &component_generated_wit)?; - copy_wit_sources(&component_generated_base_wit, &component_generated_wit)?; - add_client_deps(ctx, component_name)?; - Ok(true) - })()) + Ok(false) } } diff --git a/golem-cli/src/app/build/mod.rs b/golem-cli/src/app/build/mod.rs index 62c743536..10be162da 100644 --- a/golem-cli/src/app/build/mod.rs +++ b/golem-cli/src/app/build/mod.rs @@ -44,7 +44,7 @@ pub async fn build_app(ctx: &mut ApplicationContext) -> anyhow::Result<()> { gen_rpc(ctx).await?; } if ctx.config.should_run_step(AppBuildStep::Componentize) { - componentize(ctx)?; + componentize(ctx).await?; } if ctx.config.should_run_step(AppBuildStep::Link) { link(ctx).await?; diff --git a/golem-cli/src/command_handler/component/mod.rs b/golem-cli/src/command_handler/component/mod.rs index 78e8ebe55..6bc6f674d 100644 --- a/golem-cli/src/command_handler/component/mod.rs +++ b/golem-cli/src/command_handler/component/mod.rs @@ -749,7 +749,6 @@ impl ComponentCommandHandler { } }; - // TODO: to be sent to component service let agent_types: Option> = { let mut app_ctx = self.ctx.app_context_lock_mut().await?; let app_ctx = app_ctx.some_or_err_mut()?; diff --git a/golem-cli/src/model/agent/extraction.rs b/golem-cli/src/model/agent/extraction.rs index 549c36131..ea1f0e99e 100644 --- a/golem-cli/src/model/agent/extraction.rs +++ b/golem-cli/src/model/agent/extraction.rs @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::log::{log_action, LogColorize}; use anyhow::anyhow; use golem_common::model::agent::AgentType; use rib::ParsedFunctionName; -use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, Mutex}; -use tracing::debug; +use tracing::{debug, error}; use wasmtime::component::types::{ComponentInstance, ComponentItem}; use wasmtime::component::{ Component, Func, Instance, Linker, LinkerInstance, ResourceTable, ResourceType, Type, @@ -34,18 +34,31 @@ const FUNCTION_NAME: &str = "discover-agent-types"; /// Extracts the implemented agent types from the given WASM component, assuming it implements the `golem:agent/guest` interface. /// If it does not, it fails. pub async fn extract_agent_types(wasm_path: &Path) -> anyhow::Result> { + log_action( + "Extracting", + format!( + "agent types from {}", + wasm_path + .to_string_lossy() + .to_string() + .log_color_highlight() + ), + ); + let mut config = wasmtime::Config::default(); config.async_support(true); config.wasm_component_model(true); + let engine = Engine::new(&config)?; let mut linker: Linker = Linker::new(&engine); + linker.allow_shadowing(true); wasmtime_wasi::p2::add_to_linker_with_options_async( &mut linker, &wasmtime_wasi::p2::bindings::LinkOptions::default(), )?; - let (wasi, io) = WasiCtx::builder().build(); + let (wasi, io) = WasiCtx::builder().inherit_stdout().inherit_stderr().build(); let host = Host { table: Arc::new(Mutex::new(ResourceTable::new())), wasi: Arc::new(Mutex::new(wasi)), @@ -72,6 +85,7 @@ pub async fn extract_agent_types(wasm_path: &Path) -> anyhow::Result> = HashMap::new(); let mut functions = Vec::new(); for (inner_name, inner_item) in inst.exports(engine) { @@ -216,17 +229,6 @@ fn dynamic_import( )) .map_err(|err| anyhow!(format!("Unexpected linking error: {name}.{{{inner_name}}} is not a valid function name: {err}")))?; - if let Some(resource_name) = function_name.function.resource_name() { - let methods = resources - .entry((name.clone(), resource_name.clone())) - .or_default(); - methods.push(MethodInfo { - method_name: inner_name.clone(), - params: param_types.clone(), - results: result_types.clone(), - }); - } - functions.push(FunctionInfo { name: function_name, params: param_types, @@ -239,25 +241,30 @@ fn dynamic_import( ComponentItem::ComponentInstance(_) => {} ComponentItem::Type(_) => {} ComponentItem::Resource(_resource) => { - resources.entry((name, inner_name)).or_default(); + if &inner_name != "pollable" + && &inner_name != "input-stream" + && &inner_name != "output-stream" + { + // TODO: figure out how to do this properly + instance.resource( + &inner_name, + ResourceType::host::(), + |_store, _rep| Ok(()), + )?; + } } } } - for ((_interface_name, resource_name), _methods) in resources { - instance.resource( - &resource_name, - ResourceType::host::(), - |_store, _rep| Ok(()), - )?; - } - for function in functions { instance.func_new_async( &function.name.function.function_name(), move |_store, _params, _results| { let function_name = function.name.clone(); Box::new(async move { + error!( + "External function called in get-agent-definitions: {function_name}", + ); Err(anyhow!( "External function called in get-agent-definitions: {function_name}" )) diff --git a/golem-cli/src/model/agent/moonbit.rs b/golem-cli/src/model/agent/moonbit.rs index 38964da7d..44a331773 100644 --- a/golem-cli/src/model/agent/moonbit.rs +++ b/golem-cli/src/model/agent/moonbit.rs @@ -17,7 +17,7 @@ use anyhow::{anyhow, Context}; use camino::Utf8Path; use golem_client::model::AnalysedType; use golem_common::model::agent::{DataSchema, ElementSchema, NamedElementSchemas}; -use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use heck::{ToKebabCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use moonbit_component_generator::{MoonBitComponent, MoonBitPackage, Warning, WarningControl}; use std::fmt::Write; use std::path::Path; @@ -35,8 +35,8 @@ pub fn generate_moonbit_wrapper( .context("Defining bindgen packages")?; let moonbit_root_package = component.moonbit_root_package()?; - let pkg_namespace = component.root_pkg_namespace()?; - let pkg_name = component.root_pkg_name()?; + let pkg_namespace = component.root_pkg_namespace()?.to_snake_case(); + let pkg_name = component.root_pkg_name()?.to_snake_case(); // Adding the builder and extractor packages add_builder_package(&mut component, &moonbit_root_package)?; @@ -465,7 +465,7 @@ fn generate_agent_stub(ctx: AgentWrapperGeneratorContext) -> anyhow::Result anyhow::Result { let mut ctx = AgentWrapperGeneratorContextState::new(agent_types.to_vec()); - ctx.generate_wit_source(component_name)?; - ctx.generate_single_file_wrapper_wit()?; + ctx.generate_wit_source(component_name) + .context("Generating WIT source")?; + ctx.generate_single_file_wrapper_wit() + .context("Generating single file wrapper")?; ctx.finalize() } @@ -165,14 +167,13 @@ impl AgentWrapperGeneratorContextState { agent: &AgentType, ) -> anyhow::Result<()> { let resource_name = agent.type_name.to_kebab_case(); - let constructor_name = agent.constructor.name.as_deref().unwrap_or("create"); writeln!(result, " /// {}", agent.description)?; writeln!(result, " resource {resource_name} {{")?; writeln!(result, " constructor(agent-id: string);")?; writeln!(result, " /// {}", agent.constructor.description)?; - write!(result, " {constructor_name}: static func(")?; + write!(result, " create: static func(")?; self.write_parameter_list(result, &agent.constructor.input_schema, "constructor")?; writeln!(result, ") -> result<{resource_name}, agent-error>;")?; @@ -533,7 +534,9 @@ impl ResolvedWrapper { let golem_host_package_id = add_golem_host(&mut resolve)?; let golem_agent_package_id = add_golem_agent(&mut resolve)?; - let package_id = resolve.push_str("wrapper.wit", &package_source)?; + let package_id = resolve + .push_str("wrapper.wit", &package_source) + .context(format!("Resolving generated WIT: {package_source}"))?; Ok(Self { resolve, diff --git a/golem-cli/src/model/app.rs b/golem-cli/src/model/app.rs index 0a94791a6..11eb67ddb 100644 --- a/golem-cli/src/model/app.rs +++ b/golem-cli/src/model/app.rs @@ -9,6 +9,7 @@ use crate::validation::{ValidatedResult, ValidationBuilder}; use crate::wasm_rpc_stubgen::naming; use crate::wasm_rpc_stubgen::naming::wit::package_dep_dir_name_from_parser; use crate::wasm_rpc_stubgen::stub::RustDependencyOverride; +use anyhow::anyhow; use golem_common::model::{ComponentFilePathWithPermissions, ComponentFilePermissions}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -21,7 +22,6 @@ use std::str::FromStr; use strum::IntoEnumIterator; use strum_macros::EnumIter; use url::Url; -use wit_parser::PackageName; pub const DEFAULT_CONFIG_FILE_NAME: &str = "golem.yaml"; @@ -195,6 +195,30 @@ impl AppComponentName { } } +impl AppComponentName { + pub fn to_package_name(&self) -> anyhow::Result { + let component_name_str = self.as_str(); + let package_name_re = + regex::Regex::new(r"^(?P[^:]+):(?P[^@]+)(?:@(?P.+))?$")?; + let captures = package_name_re + .captures(component_name_str) + .ok_or_else(|| anyhow!("Invalid component name format: {}", component_name_str))?; + let namespace = captures.name("namespace").unwrap().as_str().to_string(); + let name = captures.name("name").unwrap().as_str().to_string(); + let version = captures + .name("version") + .map(|m| m.as_str().to_string()) + .map(|v| semver::Version::parse(&v)) + .transpose()?; + + Ok(wit_parser::PackageName { + namespace, + name, + version, + }) + } +} + impl Display for AppComponentName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) @@ -781,7 +805,7 @@ impl Application { pub fn component_generated_base_wit_exports_package_dir( &self, component_name: &AppComponentName, - exports_package_name: &PackageName, + exports_package_name: &wit_parser::PackageName, ) -> PathBuf { self.component_generated_base_wit(component_name) .join(naming::wit::DEPS_DIR) diff --git a/golem-cli/src/model/app_raw.rs b/golem-cli/src/model/app_raw.rs index 532697017..cbe18458b 100644 --- a/golem-cli/src/model/app_raw.rs +++ b/golem-cli/src/model/app_raw.rs @@ -322,6 +322,9 @@ pub enum BuildCommand { External(ExternalCommand), QuickJSCrate(GenerateQuickJSCrate), QuickJSDTS(GenerateQuickJSDTS), + AgentWrapper(GenerateAgentWrapper), + ComposeAgentWrapper(ComposeAgentWrapper), + InjectToPrebuiltQuickJs(InjectToPrebuiltQuickJs), } impl BuildCommand { @@ -330,6 +333,9 @@ impl BuildCommand { BuildCommand::External(cmd) => cmd.dir.as_deref(), BuildCommand::QuickJSCrate(_) => None, BuildCommand::QuickJSDTS(_) => None, + BuildCommand::AgentWrapper(_) => None, + BuildCommand::ComposeAgentWrapper(_) => None, + BuildCommand::InjectToPrebuiltQuickJs(_) => None, } } @@ -338,6 +344,9 @@ impl BuildCommand { BuildCommand::External(cmd) => cmd.targets.clone(), BuildCommand::QuickJSCrate(cmd) => vec![cmd.generate_quickjs_crate.clone()], BuildCommand::QuickJSDTS(cmd) => vec![cmd.generate_quickjs_dts.clone()], + BuildCommand::AgentWrapper(cmd) => vec![cmd.generate_agent_wrapper.clone()], + BuildCommand::ComposeAgentWrapper(cmd) => vec![cmd.to.clone()], + BuildCommand::InjectToPrebuiltQuickJs(cmd) => vec![cmd.into.clone()], } } } @@ -363,7 +372,7 @@ pub struct ExternalCommand { pub struct GenerateQuickJSCrate { pub generate_quickjs_crate: String, pub wit: String, - pub js: String, + pub js_modules: HashMap, #[serde(default, skip_serializing_if = "Option::is_none")] pub world: Option, } @@ -377,6 +386,39 @@ pub struct GenerateQuickJSDTS { pub world: Option, } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct GenerateAgentWrapper { + /// The target path of the generated wrapper component + pub generate_agent_wrapper: String, + /// The path of the compiled WASM component containing the dynamic golem:agent implementation + pub based_on_compiled_wasm: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct ComposeAgentWrapper { + /// The target path of the generated wrapper component + pub compose_agent_wrapper: String, + /// The path of the compiled WASM component implementing golem:agent + pub with_agent: String, + /// The path of the resulting composed WASM component + pub to: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct InjectToPrebuiltQuickJs { + /// The path to the prebuilt QuickJS WASM file that loads a JS module through a get-script import + pub inject_to_prebuilt_quickjs: String, + /// The path to the JS module + pub module: String, + /// The path to the intermediate WASM containing the JS module + pub module_wasm: String, + /// The path to the output WASM component containing the injected JS module + pub into: String, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Dependency { diff --git a/golem-cli/src/model/template.rs b/golem-cli/src/model/template.rs index e6093791a..60a5aa759 100644 --- a/golem-cli/src/model/template.rs +++ b/golem-cli/src/model/template.rs @@ -82,6 +82,17 @@ impl Template for app_raw::BuildCommand { app_raw::BuildCommand::QuickJSDTS(generate_quickjs_dts) => Ok( app_raw::BuildCommand::QuickJSDTS(generate_quickjs_dts.render(env, ctx)?), ), + app_raw::BuildCommand::AgentWrapper(agent_wrapper) => Ok( + app_raw::BuildCommand::AgentWrapper(agent_wrapper.render(env, ctx)?), + ), + app_raw::BuildCommand::ComposeAgentWrapper(compose_agent_wrapper) => Ok( + app_raw::BuildCommand::ComposeAgentWrapper(compose_agent_wrapper.render(env, ctx)?), + ), + app_raw::BuildCommand::InjectToPrebuiltQuickJs(inject_to_prebuilt_quickjs) => { + Ok(app_raw::BuildCommand::InjectToPrebuiltQuickJs( + inject_to_prebuilt_quickjs.render(env, ctx)?, + )) + } } } } @@ -112,7 +123,15 @@ impl Template for app_raw::GenerateQuickJSCrate { Ok(app_raw::GenerateQuickJSCrate { generate_quickjs_crate: self.generate_quickjs_crate.render(env, ctx)?, wit: self.wit.render(env, ctx)?, - js: self.js.render(env, ctx)?, + js_modules: HashMap::from_iter( + self.js_modules + .iter() + .map(|(k, v)| { + k.render(env, ctx) + .and_then(|k| v.render(env, ctx).map(|v| (k, v))) + }) + .collect::, _>>()?, + ), world: self.world.render(env, ctx)?, }) } @@ -130,6 +149,42 @@ impl Template for app_raw::GenerateQuickJSDTS { } } +impl Template for app_raw::GenerateAgentWrapper { + type Rendered = app_raw::GenerateAgentWrapper; + + fn render(&self, env: &Environment, ctx: &C) -> Result { + Ok(app_raw::GenerateAgentWrapper { + generate_agent_wrapper: self.generate_agent_wrapper.render(env, ctx)?, + based_on_compiled_wasm: self.based_on_compiled_wasm.render(env, ctx)?, + }) + } +} + +impl Template for app_raw::ComposeAgentWrapper { + type Rendered = app_raw::ComposeAgentWrapper; + + fn render(&self, env: &Environment, ctx: &C) -> Result { + Ok(app_raw::ComposeAgentWrapper { + compose_agent_wrapper: self.compose_agent_wrapper.render(env, ctx)?, + with_agent: self.with_agent.render(env, ctx)?, + to: self.to.render(env, ctx)?, + }) + } +} + +impl Template for app_raw::InjectToPrebuiltQuickJs { + type Rendered = app_raw::InjectToPrebuiltQuickJs; + + fn render(&self, env: &Environment, ctx: &C) -> Result { + Ok(app_raw::InjectToPrebuiltQuickJs { + inject_to_prebuilt_quickjs: self.inject_to_prebuilt_quickjs.render(env, ctx)?, + module: self.module.render(env, ctx)?, + module_wasm: self.module_wasm.render(env, ctx)?, + into: self.into.render(env, ctx)?, + }) + } +} + impl Template for serde_json::Value { type Rendered = serde_json::Value; diff --git a/golem-cli/src/wasm_rpc_stubgen/wit_resolve.rs b/golem-cli/src/wasm_rpc_stubgen/wit_resolve.rs index 1e3d17d0a..ea81d7a4e 100644 --- a/golem-cli/src/wasm_rpc_stubgen/wit_resolve.rs +++ b/golem-cli/src/wasm_rpc_stubgen/wit_resolve.rs @@ -11,6 +11,7 @@ use indoc::formatdoc; use itertools::Itertools; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::path::{Path, PathBuf}; +use wit_parser::decoding::DecodedWasm; use wit_parser::{ InterfaceId, Package, PackageId, PackageName, PackageSourceMap, Resolve, UnresolvedPackageGroup, WorldItem, @@ -29,10 +30,19 @@ pub struct ResolvedWitDir { } impl ResolvedWitDir { - pub fn new(path: &Path) -> anyhow::Result { + pub fn new(path: &Path) -> anyhow::Result { resolve_wit_dir(path) } + pub fn from_wasm(path: &Path, wasm: &DecodedWasm) -> Self { + Self { + path: path.to_path_buf(), + resolve: wasm.resolve().clone(), + package_id: wasm.package(), + package_sources: IndexMap::new(), + } + } + pub fn package(&self, package_id: PackageId) -> anyhow::Result<&Package> { self.resolve.packages.get(package_id).with_context(|| { anyhow!( @@ -268,7 +278,7 @@ fn collect_package_sources( pub struct ResolvedWitComponent { main_package_name: PackageName, - resolved_generated_wit_dir: Option, + resolved_wit_dir: Option, app_component_deps: HashSet, source_referenced_package_deps: HashSet, source_contained_package_deps: HashSet, @@ -276,12 +286,6 @@ pub struct ResolvedWitComponent { generated_component_deps: Option>, } -impl ResolvedWitComponent { - pub fn generated_wit_dir(&self) -> Option<&ResolvedWitDir> { - self.resolved_generated_wit_dir.as_ref() - } -} - #[derive(Default)] enum ExtractedAgentTypes { #[default] @@ -427,76 +431,150 @@ impl ResolvedWitApplication { let source_wit_dir = app.component_source_wit(component_name, profile); let generated_wit_dir = app.component_generated_wit(component_name, profile); - log_action( - "Resolving", - format!( - "component wit dirs for {} ({}, {})", - component_name.as_str().log_color_highlight(), - source_wit_dir.log_color_highlight(), - generated_wit_dir.log_color_highlight(), - ), - ); + let app_component_deps = app + .component_dependencies(component_name) + .iter() + .filter(|&dep| dep.dep_type.is_wasm_rpc()) + .filter_map(|dep| dep.as_dependent_app_component()) + .map(|dep| dep.name) + .collect(); + + let resolved_component = if source_wit_dir.is_dir() { + log_action( + "Resolving", + format!( + "component wit dirs for {} ({}, {})", + component_name.as_str().log_color_highlight(), + source_wit_dir.log_color_highlight(), + generated_wit_dir.log_color_highlight(), + ), + ); + + (|| -> anyhow::Result { + let unresolved_source_package_group = + UnresolvedPackageGroup::parse_dir(&source_wit_dir).with_context(|| { + anyhow!( + "Failed to parse component {} main package in source wit dir {}", + component_name.as_str().log_color_error_highlight(), + source_wit_dir.log_color_highlight(), + ) + })?; + + let source_referenced_package_deps = unresolved_source_package_group + .main + .foreign_deps + .keys() + .cloned() + .collect(); + + let source_contained_package_deps = { + let deps_path = source_wit_dir.join("deps"); + if !deps_path.exists() { + HashSet::new() + } else { + parse_wit_deps_dir(&deps_path)? + .into_iter() + .map(|package_group| package_group.main.name) + .collect::>() + } + }; + + let main_package_name = unresolved_source_package_group.main.name.clone(); + + let resolved_generated_wit_dir = ResolvedWitDir::new(&generated_wit_dir).ok(); + let generated_has_same_main_package_name = resolved_generated_wit_dir + .as_ref() + .map(|wit| wit.main_package()) + .transpose()? + .map(|generated_main_package| { + main_package_name == generated_main_package.name + }) + .unwrap_or_default(); + let resolved_generated_wit_dir = generated_has_same_main_package_name + .then_some(resolved_generated_wit_dir) + .flatten(); + + Ok(ResolvedWitComponent { + main_package_name, + resolved_wit_dir: resolved_generated_wit_dir, + app_component_deps, + source_referenced_package_deps, + source_contained_package_deps, + source_component_deps: Default::default(), + generated_component_deps: Default::default(), + }) + })() + } else { + log_action( + "Resolving", + format!( + "component interface using base WASM for {} ({}, {})", + component_name.as_str().log_color_highlight(), + source_wit_dir.log_color_highlight(), + generated_wit_dir.log_color_highlight(), + ), + ); - let resolved_component = (|| -> anyhow::Result { - let unresolved_source_package_group = - UnresolvedPackageGroup::parse_dir(&source_wit_dir).with_context(|| { + (|| -> anyhow::Result { + let source_wasm = std::fs::read(&source_wit_dir) + .with_context(|| anyhow!("Failed to read the source WIT path as a file"))?; + let wasm = wit_parser::decoding::decode(&source_wasm).with_context(|| { anyhow!( - "Failed to parse component {} main package in source wit dir {}", - component_name.as_str().log_color_error_highlight(), - source_wit_dir.log_color_highlight(), + "Failed to decode the source WIT path as a WASM component: {}", + source_wit_dir.log_color_highlight() ) })?; - let source_referenced_package_deps = unresolved_source_package_group - .main - .foreign_deps - .keys() - .cloned() - .collect(); + // The WASM has no root package name (always root:component with world root) so + // we use the app component name as a main package name + let main_package_name = component_name.to_package_name()?; - let source_contained_package_deps = { - let deps_path = source_wit_dir.join("deps"); - if !deps_path.exists() { - HashSet::new() - } else { - parse_wit_deps_dir(&deps_path)? - .into_iter() - .map(|package_group| package_group.main.name) - .collect::>() - } - }; + // When using a WASM as a "source WIT", we are currently not supporting transforming + // that into a generated WIT dir, just treat it as a static interface definition for + // the given component. So resolved_wit_dir in this case is the resolved _source_ WIT + // parsed from the WASM. + let resolved_wit_dir = ResolvedWitDir::from_wasm(&source_wit_dir, &wasm); - let main_package_name = unresolved_source_package_group.main.name.clone(); - - let resolved_generated_wit_dir = ResolvedWitDir::new(&generated_wit_dir).ok(); - let generated_has_same_main_package_name = resolved_generated_wit_dir - .as_ref() - .map(|wit| wit.main_package()) - .transpose()? - .map(|generated_main_package| main_package_name == generated_main_package.name) - .unwrap_or_default(); - let resolved_generated_wit_dir = generated_has_same_main_package_name - .then_some(resolved_generated_wit_dir) - .flatten(); - - let app_component_deps = app - .component_dependencies(component_name) - .iter() - .filter(|&dep| dep.dep_type.is_wasm_rpc()) - .filter_map(|dep| dep.as_dependent_app_component()) - .map(|dep| dep.name) - .collect(); + let resolve = wasm.resolve(); + let root_package_id = wasm.package(); + let root_world_id = resolve.select_world(root_package_id, None)?; + let root_world = resolve.worlds.get(root_world_id).ok_or_else(|| { + anyhow!( + "Failed to get root world from the resolved component interface: {}", + source_wit_dir.log_color_highlight() + ) + })?; - Ok(ResolvedWitComponent { - main_package_name, - resolved_generated_wit_dir, - app_component_deps, - source_referenced_package_deps, - source_contained_package_deps, - source_component_deps: Default::default(), - generated_component_deps: Default::default(), - }) - })(); + let mut source_referenced_package_deps = HashSet::new(); + for import in root_world.imports.values() { + if let WorldItem::Interface { id, .. } = import { + let iface = resolve.interfaces.get(*id).ok_or_else(|| { + anyhow!( + "Failed to get interface from the resolved component interface" + ) + })?; + if let Some(package) = iface.package { + let pkg = resolve.packages.get(package).ok_or_else(|| { + anyhow!( + "Failed to get package from the resolved component interface" + ) + })?; + source_referenced_package_deps.insert(pkg.name.clone()); + } + } + } + + Ok(ResolvedWitComponent { + main_package_name, + resolved_wit_dir: Some(resolved_wit_dir), + app_component_deps, + source_referenced_package_deps, + source_contained_package_deps: Default::default(), + source_component_deps: Default::default(), + generated_component_deps: Default::default(), + }) + })() + }; match resolved_component { Ok(resolved_component) => { @@ -539,15 +617,12 @@ impl ResolvedWitApplication { &self.interface_package_to_component, &component.source_referenced_package_deps, ), - component - .resolved_generated_wit_dir - .as_ref() - .map(|wit_dir| { - component_deps( - &self.stub_package_to_component, - wit_dir.resolve.package_names.keys(), - ) - }), + component.resolved_wit_dir.as_ref().map(|wit_dir| { + component_deps( + &self.stub_package_to_component, + wit_dir.resolve.package_names.keys(), + ) + }), ), ); } @@ -721,7 +796,7 @@ impl ResolvedWitApplication { fn extract_agent_types(&mut self, validation: &mut ValidationBuilder) { for (name, component) in &self.components { - if let Some(resolved_wit_dir) = &component.resolved_generated_wit_dir { + if let Some(resolved_wit_dir) = &component.resolved_wit_dir { match resolved_wit_dir.is_agent() { Ok(true) => { log_action( diff --git a/golem-cli/tests/app/mod.rs b/golem-cli/tests/app/mod.rs index eddebd57b..da94e531c 100644 --- a/golem-cli/tests/app/mod.rs +++ b/golem-cli/tests/app/mod.rs @@ -221,7 +221,7 @@ fn basic_dependencies_build(_tracing: &Tracing) { let mut ctx = TestContext::new(); let app_name = "test-app-name"; - let outputs = ctx.cli([cmd::APP, cmd::NEW, app_name, "rust", "ts"]); + let outputs = ctx.cli([cmd::APP, cmd::NEW, app_name, "rust", "go"]); assert!(outputs.success()); ctx.cd(app_name); @@ -229,10 +229,7 @@ fn basic_dependencies_build(_tracing: &Tracing) { let outputs = ctx.cli([cmd::COMPONENT, cmd::NEW, "rust", "app:rust"]); assert!(outputs.success()); - let outputs = ctx.cli([cmd::COMPONENT, cmd::NEW, "ts", "app:ts"]); - assert!(outputs.success()); - - let outputs = ctx.cli([cmd::APP, "ts-npm-install"]); + let outputs = ctx.cli([cmd::COMPONENT, cmd::NEW, "go", "app:go"]); assert!(outputs.success()); fs::append_str( @@ -246,20 +243,20 @@ fn basic_dependencies_build(_tracing: &Tracing) { app:rust: - target: app:rust type: wasm-rpc - - target: app:ts + - target: app:go type: wasm-rpc "}, ) .unwrap(); fs::append_str( - ctx.cwd_path_join(Path::new("components-ts").join("app-ts").join("golem.yaml")), + ctx.cwd_path_join(Path::new("components-go").join("app-go").join("golem.yaml")), indoc! {" dependencies: - app:ts: + app:go: - target: app:rust type: wasm-rpc - - target: app:ts + - target: app:go type: wasm-rpc "}, ) @@ -268,7 +265,7 @@ fn basic_dependencies_build(_tracing: &Tracing) { let outputs = ctx.cli([cmd::APP]); assert!(!outputs.success()); check!(outputs.stderr_count_lines_containing("- app:rust (wasm-rpc)") == 2); - check!(outputs.stderr_count_lines_containing("- app:ts (wasm-rpc)") == 2); + check!(outputs.stderr_count_lines_containing("- app:go (wasm-rpc)") == 2); let outputs = ctx.cli([cmd::APP, cmd::BUILD]); assert!(outputs.success()); diff --git a/golem-templates/templates/js/componentizejs-app-common/common-js/golem.yaml b/golem-templates/templates/js/componentizejs-app-common/common-js/golem.yaml deleted file mode 100644 index f006def09..000000000 --- a/golem-templates/templates/js/componentizejs-app-common/common-js/golem.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# golem-app-manifest-header - -templates: - js: - build: - - command: node ../../common-js/scripts/jco-wrapper.mjs guest-types wit-generated --async-mode jspi --async-wasi-exports ${JCO_ASYNC_EXPORT_ARGS} -o src/generated - sources: - - wit-generated - targets: - - src/generated - rmdirs: - - src/generated - mkdirs: - - src/generated - - command: npx rollup -c - sources: - - src - - ../../common-ts - - rollup.config.mjs - - tsconfig.js - targets: - - dist/main.js - - command: npx jco componentize -w wit-generated -o dist/{{ component_name | to_snake_case }}.wasm dist/main.js - sources: - - dist/main.js - targets: - - dist/{{ component_name | to_snake_case }}.wasm - sourceWit: wit - generatedWit: wit-generated - componentWasm: dist/{{ component_name | to_snake_case }}.wasm - linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}.wasm - clean: - - dist -customCommands: - js-npm-install: - - command: npm install diff --git a/golem-templates/templates/js/componentizejs-app-common/common-js/rollup.config.component.mjs b/golem-templates/templates/js/componentizejs-app-common/common-js/rollup.config.component.mjs deleted file mode 100644 index 8bbe4a38b..000000000 --- a/golem-templates/templates/js/componentizejs-app-common/common-js/rollup.config.component.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import * as fs from "node:fs"; -import alias from '@rollup/plugin-alias'; -import nodeResolve from "@rollup/plugin-node-resolve"; -import path from "node:path"; -import url from "node:url"; - -export default function componentRollupConfig() { - const dir = path.dirname(url.fileURLToPath(import.meta.url)); - const moduleRegex = /declare\s+module\s+['"]([^'"]+)['"]/g; - const generated_interfaces_dir = "src/generated/interfaces"; - - const externalPackages = (() => { - if (!fs.existsSync(generated_interfaces_dir)) { - return []; - } - return fs - .readdirSync(generated_interfaces_dir, {withFileTypes: true}) - .filter(dirent => dirent.isFile() && dirent.name.endsWith(".d.ts")) - .flatMap(dirent => - [...fs.readFileSync(path.join(generated_interfaces_dir, dirent.name)) - .toString() - .matchAll(moduleRegex)] - .map((match) => { - const moduleName = match[1]; - if (moduleName === undefined) { - throw new Error(`Missing match for module name`); - } - return moduleName; - }), - ); - })(); - - console.log("External packages:", externalPackages); - - return { - input: "src/main.js", - output: { - file: "dist/main.js", - format: "esm", - }, - external: externalPackages, - plugins: [ - alias({ - entries: [ - {find: 'common', replacement: path.resolve(dir, "../common-js/src")} - ] - }), - nodeResolve({ - extensions: [".mjs", ".js", ".json", ".node", ".ts"] - }) - ], - }; -} diff --git a/golem-templates/templates/js/componentizejs-app-common/common-js/scripts/jco-wrapper.mjs b/golem-templates/templates/js/componentizejs-app-common/common-js/scripts/jco-wrapper.mjs deleted file mode 100644 index b85756090..000000000 --- a/golem-templates/templates/js/componentizejs-app-common/common-js/scripts/jco-wrapper.mjs +++ /dev/null @@ -1,53 +0,0 @@ -// Thin wrapper around jco, exposing additional options for type generation - -import { program, Option } from 'commander'; - -import { guestTypes } from '../../node_modules/@bytecodealliance/jco/src/cmd/transpile.js'; - -program - .name('typegen') - .usage(' [options]') - -program.command('guest-types') - .description('(experimental) Generate guest types for the given WIT') - .usage(' -o ') - .argument('', 'path to a WIT file or directory') - .option('--name ', 'custom output name') - .option('-n, --world-name ', 'WIT world to generate types for') - .requiredOption('-o, --out-dir ', 'output directory') - .option('-q, --quiet', 'disable output summary') - .option('--feature ', 'enable one specific WIT feature (repeatable)', collectOptions, []) - .option('--all-features', 'enable all features') - .addOption(new Option('--async-mode [mode]', 'EXPERIMENTAL: use async imports and exports').choices(['sync', 'jspi']).preset('sync')) - .option('--async-wasi-exports', 'EXPERIMENTAL: async component exports from WASI interfaces') - .option('--async-exports ', 'EXPERIMENTAL: async component exports (examples: "wasi:cli/run@#run", "handle")') - .option('--async-imports ', 'EXPERIMENTAL: async component imports (examples: "wasi:io/poll@0.2.0#poll", "wasi:io/poll#[method]pollable.block")') - .action(asyncAction(guestTypes)); - -program.showHelpAfterError(); - -program.parse(); - -function collectOptions(value, previous) { - return previous.concat([value]); -} - -function asyncAction (cmd) { - return function () { - const args = [...arguments]; - (async () => { - try { - await cmd.apply(null, args); - } - catch (e) { - process.stdout.write(`(jco ${cmd.name}) `); - if (typeof e === 'string') { - console.error(`{red.bold Error}: ${e}\n`); - } else { - console.error(e); - } - process.exit(1); - } - })(); - }; -} diff --git a/golem-templates/templates/js/componentizejs-app-common/common-js/src/lib.ts b/golem-templates/templates/js/componentizejs-app-common/common-js/src/lib.ts deleted file mode 100644 index b518d31c3..000000000 --- a/golem-templates/templates/js/componentizejs-app-common/common-js/src/lib.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function example_common_function() { - return "hello common"; -} \ No newline at end of file diff --git a/golem-templates/templates/js/componentizejs-app-common/metadata.json b/golem-templates/templates/js/componentizejs-app-common/metadata.json deleted file mode 100644 index 532f9070d..000000000 --- a/golem-templates/templates/js/componentizejs-app-common/metadata.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "Composable App Common template for JavaScript", - "appCommonGroup": "componentizejs", - "appCommonSkipIfExists": "common-js/golem.yaml", - "requiresGolemHostWIT": true, - "requiresWASI": true, - "witDepsPaths": [ - "wit/deps" - ], - "transform": false -} \ No newline at end of file diff --git a/golem-templates/templates/js/componentizejs-app-common/package.json b/golem-templates/templates/js/componentizejs-app-common/package.json deleted file mode 100644 index 9c1851d7f..000000000 --- a/golem-templates/templates/js/componentizejs-app-common/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "app", - "workspaces": [ - "common-js/*/*", - "components-js/*/*" - ], - "dependencies": { - "@golemcloud/golem-ts": "1.5.0" - }, - "devDependencies": { - "@bytecodealliance/jco": "1.10.2", - "@bytecodealliance/componentize-js": "0.18.0", - "@rollup/plugin-alias": "^5.1.1", - "@rollup/plugin-node-resolve": "^16.0.0", - "@rollup/plugin-typescript": "^12.1.2", - "@types/node": "^22.10.5", - "rollup": "^4.29.1", - "tslib": "^2.8.1", - "typescript": "^5.7.2" - } -} diff --git a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/golem.yaml b/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/golem.yaml deleted file mode 100644 index b8d74db78..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/golem.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# golem-app-manifest-header - -components: - componentname: - template: js - -# golem-app-manifest-component-hints diff --git a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/rollup.config.mjs b/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/rollup.config.mjs deleted file mode 100644 index ae7630523..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/rollup.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import componentRollupConfig from "../../common-js/rollup.config.component.mjs"; - -export default componentRollupConfig(); diff --git a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/src/main.js b/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/src/main.js deleted file mode 100644 index cffc29078..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/src/main.js +++ /dev/null @@ -1,11 +0,0 @@ -let state = BigInt(0); - -export const componentNameApi = { - add(value) { - console.log(`Adding ${value} to the counter`); - state += value; - }, - get() { - return state; - } -} diff --git a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/wit/main.wit b/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/wit/main.wit deleted file mode 100644 index 7a10d254e..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/wit/main.wit +++ /dev/null @@ -1,12 +0,0 @@ -package pack:name; - -// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax - -interface component-name-api { - add: func(value: u64); - get: func() -> u64; -} - -world component-name { - export component-name-api; -} diff --git a/golem-templates/templates/js/componentizejs-app-component-default/metadata.json b/golem-templates/templates/js/componentizejs-app-component-default/metadata.json deleted file mode 100644 index bba29b251..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-default/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "description": "The default component template for JavaScript", - "appComponentGroup": "componentizejs" -} \ No newline at end of file diff --git a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/golem.yaml b/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/golem.yaml deleted file mode 100644 index b8d74db78..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/golem.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# golem-app-manifest-header - -components: - componentname: - template: js - -# golem-app-manifest-component-hints diff --git a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/rollup.config.mjs b/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/rollup.config.mjs deleted file mode 100644 index ae7630523..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/rollup.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import componentRollupConfig from "../../common-js/rollup.config.component.mjs"; - -export default componentRollupConfig(); diff --git a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/src/main.js b/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/src/main.js deleted file mode 100644 index a55683b27..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/src/main.js +++ /dev/null @@ -1,12 +0,0 @@ - -export const componentNameApi = { - async getLastResult() { - return JSON.stringify(result); - }, - async fetchJson(url) { - const response = await fetch(url); - const responseBody = response.json(); - console.log(responseBody); - return JSON.stringify(responseBody); - }, -} diff --git a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/wit/main.wit b/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/wit/main.wit deleted file mode 100644 index 933df63eb..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/wit/main.wit +++ /dev/null @@ -1,13 +0,0 @@ -package pack:name; - -// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax - -interface component-name-api { - get-last-result: func() -> string; - fetch-json: func(url: string) -> string; -} - -world component-name { - import golem:api/host@1.1.7; - export component-name-api; -} diff --git a/golem-templates/templates/js/componentizejs-app-component-example-fetch/metadata.json b/golem-templates/templates/js/componentizejs-app-component-example-fetch/metadata.json deleted file mode 100644 index 663116b48..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-example-fetch/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "description": "A JavaScript worker making a remote request using 'fetch'", - "appComponentGroup": "componentizejs" -} \ No newline at end of file diff --git a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/.gitignore b/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/.gitignore deleted file mode 100644 index a80c710b8..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*/dist -/*/src/generated -/*/wit-generated diff --git a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/golem.yaml b/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/golem.yaml deleted file mode 100644 index b8d74db78..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/golem.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# golem-app-manifest-header - -components: - componentname: - template: js - -# golem-app-manifest-component-hints diff --git a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/package.json b/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/package.json deleted file mode 100644 index 514b91ea8..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "component-name" -} diff --git a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/rollup.config.mjs b/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/rollup.config.mjs deleted file mode 100644 index ae7630523..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/rollup.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import componentRollupConfig from "../../common-js/rollup.config.component.mjs"; - -export default componentRollupConfig(); diff --git a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/src/main.js b/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/src/main.js deleted file mode 100644 index 31a5b6f7a..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/src/main.js +++ /dev/null @@ -1,40 +0,0 @@ -import { ResponseOutparam, OutgoingBody, OutgoingResponse, Fields } from "wasi:http/types@0.2.3"; - -// For a detailed guide see https://github.com/bytecodealliance/jco/tree/main/examples/components/http-hello-world - -/** - * This export represents the `wasi:http/incoming-handler` interface, - * which describes implementing a HTTP handler in WebAssembly using WASI types. - */ -export const incomingHandler = { - /** - * This Javascript will be turned into a WebAssembly component by `jco` and turned into a - * WebAssembly binary with a single export (this `handler` function). - * - * The exported `handle` method is part of the `wasi:http/incoming-handler` interface, - * which defines how to hadle incoming web requests, turning this component into one that can - * serve web requests. - */ - handle: function (incomingRequest, responseOutparam) { - // Start building an outgoing response - const outgoingResponse = new OutgoingResponse(new Fields()); - - // Access the outgoing response body - let outgoingBody = outgoingResponse.body(); - { - // Create a stream for the response body - let outputStream = outgoingBody.write(); - // Write hello world to the response stream - outputStream.blockingWriteAndFlush(new Uint8Array(new TextEncoder().encode("Hello from Javascript!\n"))); - // @ts-ignore: This is required in order to dispose the stream before we return - outputStream[Symbol.dispose](); - } - - // Set the status code for the response - outgoingResponse.setStatusCode(200); - // Finish the response body - OutgoingBody.finish(outgoingBody, undefined); - // Set the created response to an "OK" Result value - ResponseOutparam.set(outgoingResponse, { tag: "ok", val: outgoingResponse }); - }, -}; diff --git a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/wit/main.wit b/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/wit/main.wit deleted file mode 100644 index 3e3a8104d..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-wasi-http/components-js/component-name/wit/main.wit +++ /dev/null @@ -1,8 +0,0 @@ -package pack:name; - -// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax -// See https://github.com/WebAssembly/wasi-http for an introduction to wasi-http - -world component-name { - export wasi:http/incoming-handler@0.2.3; - } diff --git a/golem-templates/templates/js/componentizejs-app-component-wasi-http/metadata.json b/golem-templates/templates/js/componentizejs-app-component-wasi-http/metadata.json deleted file mode 100644 index 1bea9b0a1..000000000 --- a/golem-templates/templates/js/componentizejs-app-component-wasi-http/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "description": "A JavaScript worker implementing wasi:http/incoming-handler. For use with the http-handler api gateway binding type", - "appComponentGroup": "componentizejs" -} \ No newline at end of file diff --git a/golem-templates/templates/js/js-app-common/common-js/golem.yaml b/golem-templates/templates/js/js-app-common/common-js/golem.yaml index fea6e9401..b0df4849f 100644 --- a/golem-templates/templates/js/js-app-common/common-js/golem.yaml +++ b/golem-templates/templates/js/js-app-common/common-js/golem.yaml @@ -14,7 +14,8 @@ templates: - dist/main.js - generateQuickjsCrate: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }} wit: wit-generated - js: dist/main.js + jsModules: + main: dist/main.js - command: cargo component build --release dir: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }} sources: diff --git a/golem-templates/templates/js/js-app-common/package.json b/golem-templates/templates/js/js-app-common/package.json index 07ce18af3..52fe45479 100644 --- a/golem-templates/templates/js/js-app-common/package.json +++ b/golem-templates/templates/js/js-app-common/package.json @@ -7,15 +7,19 @@ "components-ts/*/*" ], "dependencies": { - "@golemcloud/golem-ts": "1.5.0" + "@golemcloud/golem-ts": "1.5.0", + "@golemcloud/golem-ts-sdk": "0.0.1-dev.3", + "rttist": "^1.0.0-rc.4" }, "devDependencies": { "@rollup/plugin-alias": "^5.1.1", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-typescript": "^12.1.2", "@types/node": "^22.10.5", "rollup": "^4.29.1", "tslib": "^2.8.1", - "typescript": "^5.7.2" + "typescript": "^5.8.3", + "@rttist/typegen": "^0.2.0" } -} +} \ No newline at end of file diff --git a/golem-templates/templates/js/js-app-component-default/components-js/component-name/wit/main.wit b/golem-templates/templates/js/js-app-component-default/components-js/component-name/wit/main.wit index 7a10d254e..358fc151a 100644 --- a/golem-templates/templates/js/js-app-component-default/components-js/component-name/wit/main.wit +++ b/golem-templates/templates/js/js-app-component-default/components-js/component-name/wit/main.wit @@ -3,8 +3,8 @@ package pack:name; // See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax interface component-name-api { - add: func(value: u64); - get: func() -> u64; + add: func(value: u32); + get: func() -> u32; } world component-name { diff --git a/golem-templates/templates/ts/componentizets-app-common/.gitignore b/golem-templates/templates/ts/componentizets-app-common/.gitignore deleted file mode 100644 index 779a47485..000000000 --- a/golem-templates/templates/ts/componentizets-app-common/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/dist -/golem-temp -/node_modules diff --git a/golem-templates/templates/ts/componentizets-app-common/common-ts/golem.yaml b/golem-templates/templates/ts/componentizets-app-common/common-ts/golem.yaml deleted file mode 100644 index 093172b4a..000000000 --- a/golem-templates/templates/ts/componentizets-app-common/common-ts/golem.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# golem-app-manifest-header - -templates: - ts: - build: - - command: node ../../common-ts/scripts/jco-wrapper.mjs guest-types wit-generated --async-mode jspi --async-wasi-exports ${JCO_ASYNC_EXPORT_ARGS} -o src/generated - sources: - - wit-generated - targets: - - src/generated - rmdirs: - - src/generated - mkdirs: - - src/generated - - command: npx rollup -c - sources: - - src - - ../../common-ts - - rollup.config.mjs - - tsconfig.js - targets: - - dist/main.js - - command: npx jco componentize -w wit-generated -o dist/{{ component_name | to_snake_case }}.wasm dist/main.js - sources: - - dist/main.js - targets: - - dist/{{ component_name | to_snake_case }}.wasm - sourceWit: wit - generatedWit: wit-generated - componentWasm: dist/{{ component_name | to_snake_case }}.wasm - linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}.wasm - clean: - - dist -customCommands: - ts-npm-install: - - command: npm install diff --git a/golem-templates/templates/ts/componentizets-app-common/common-ts/scripts/jco-wrapper.mjs b/golem-templates/templates/ts/componentizets-app-common/common-ts/scripts/jco-wrapper.mjs deleted file mode 100644 index b85756090..000000000 --- a/golem-templates/templates/ts/componentizets-app-common/common-ts/scripts/jco-wrapper.mjs +++ /dev/null @@ -1,53 +0,0 @@ -// Thin wrapper around jco, exposing additional options for type generation - -import { program, Option } from 'commander'; - -import { guestTypes } from '../../node_modules/@bytecodealliance/jco/src/cmd/transpile.js'; - -program - .name('typegen') - .usage(' [options]') - -program.command('guest-types') - .description('(experimental) Generate guest types for the given WIT') - .usage(' -o ') - .argument('', 'path to a WIT file or directory') - .option('--name ', 'custom output name') - .option('-n, --world-name ', 'WIT world to generate types for') - .requiredOption('-o, --out-dir ', 'output directory') - .option('-q, --quiet', 'disable output summary') - .option('--feature ', 'enable one specific WIT feature (repeatable)', collectOptions, []) - .option('--all-features', 'enable all features') - .addOption(new Option('--async-mode [mode]', 'EXPERIMENTAL: use async imports and exports').choices(['sync', 'jspi']).preset('sync')) - .option('--async-wasi-exports', 'EXPERIMENTAL: async component exports from WASI interfaces') - .option('--async-exports ', 'EXPERIMENTAL: async component exports (examples: "wasi:cli/run@#run", "handle")') - .option('--async-imports ', 'EXPERIMENTAL: async component imports (examples: "wasi:io/poll@0.2.0#poll", "wasi:io/poll#[method]pollable.block")') - .action(asyncAction(guestTypes)); - -program.showHelpAfterError(); - -program.parse(); - -function collectOptions(value, previous) { - return previous.concat([value]); -} - -function asyncAction (cmd) { - return function () { - const args = [...arguments]; - (async () => { - try { - await cmd.apply(null, args); - } - catch (e) { - process.stdout.write(`(jco ${cmd.name}) `); - if (typeof e === 'string') { - console.error(`{red.bold Error}: ${e}\n`); - } else { - console.error(e); - } - process.exit(1); - } - })(); - }; -} diff --git a/golem-templates/templates/ts/componentizets-app-common/common-ts/src/lib.ts b/golem-templates/templates/ts/componentizets-app-common/common-ts/src/lib.ts deleted file mode 100644 index b518d31c3..000000000 --- a/golem-templates/templates/ts/componentizets-app-common/common-ts/src/lib.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function example_common_function() { - return "hello common"; -} \ No newline at end of file diff --git a/golem-templates/templates/ts/componentizets-app-common/golem.yaml b/golem-templates/templates/ts/componentizets-app-common/golem.yaml deleted file mode 100644 index 752a7ead7..000000000 --- a/golem-templates/templates/ts/componentizets-app-common/golem.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# golem-app-manifest-header - -includes: -- common-*/golem.yaml -- components-*/*/golem.yaml -witDeps: -- wit/deps diff --git a/golem-templates/templates/ts/componentizets-app-common/wit/common.wit b/golem-templates/templates/ts/componentizets-app-common/wit/common.wit deleted file mode 100644 index f74b22880..000000000 --- a/golem-templates/templates/ts/componentizets-app-common/wit/common.wit +++ /dev/null @@ -1,7 +0,0 @@ -// This file is used to make the 'wit' directory compatible with tools such as wit-bindgen. -// -// Do NOT add anything to this file, rather create a 'deps' directory, and place common WIT dependency packages there. -// Such dependencies can be referenced in the component WIT definitions, and will automatically be included when needed -// in the generated component wit directories. - -package common:root; diff --git a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/.gitignore b/golem-templates/templates/ts/componentizets-app-component-default/components-ts/.gitignore deleted file mode 100644 index a80c710b8..000000000 --- a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*/dist -/*/src/generated -/*/wit-generated diff --git a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/package.json b/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/package.json deleted file mode 100644 index 514b91ea8..000000000 --- a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "component-name" -} diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/.gitignore b/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/.gitignore deleted file mode 100644 index a80c710b8..000000000 --- a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*/dist -/*/src/generated -/*/wit-generated diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/package.json b/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/package.json deleted file mode 100644 index 514b91ea8..000000000 --- a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "component-name" -} diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/src/main.ts b/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/src/main.ts deleted file mode 100644 index 688c6c194..000000000 --- a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/src/main.ts +++ /dev/null @@ -1,16 +0,0 @@ -/// -import type * as bindings from "pack:name/component-name" - -let result: any; - -export const componentNameApi: typeof bindings.componentNameApi = { - async getLastResult(): Promise { - return JSON.stringify(result); - }, - async fetchJson(url: string): Promise { - const response = await fetch(url); - const responseBody = await response.json(); - console.log(responseBody); - return JSON.stringify(responseBody); - }, -} diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/wit/main.wit b/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/wit/main.wit deleted file mode 100644 index 847f71383..000000000 --- a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/wit/main.wit +++ /dev/null @@ -1,14 +0,0 @@ -package pack:name; - -// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax - -interface component-name-api { - get-last-result: func() -> string; - fetch-json: func(url: string) -> string; -} - -world component-name { - import golem:api/host@1.1.7; - - export component-name-api; -} diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/metadata.json b/golem-templates/templates/ts/componentizets-app-component-example-fetch/metadata.json deleted file mode 100644 index 5942c6f3f..000000000 --- a/golem-templates/templates/ts/componentizets-app-component-example-fetch/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "description": "Example: using fetch with the golem-ts library in TypeScript", - "appComponentGroup": "componentizejs" -} \ No newline at end of file diff --git a/golem-templates/templates/ts/ts-app-common/.gitignore b/golem-templates/templates/ts/ts-app-common/.gitignore index 779a47485..58e78b858 100644 --- a/golem-templates/templates/ts/ts-app-common/.gitignore +++ b/golem-templates/templates/ts/ts-app-common/.gitignore @@ -1,3 +1,4 @@ /dist /golem-temp /node_modules +.generated diff --git a/golem-templates/templates/ts/ts-app-common/common-ts/golem.yaml b/golem-templates/templates/ts/ts-app-common/common-ts/golem.yaml index e0d3a6176..0aef1ee8c 100644 --- a/golem-templates/templates/ts/ts-app-common/common-ts/golem.yaml +++ b/golem-templates/templates/ts/ts-app-common/common-ts/golem.yaml @@ -3,33 +3,38 @@ templates: ts: build: - - generateQuickjsDts: src/generated/interfaces - wit: wit-generated - - command: npx rollup -c + - command: npx --no @rttist/typegen generate -p components-ts/{{ component_name | to_kebab_case }} + sources: + - src + - ../../common-ts + targets: + - .metadata + dir: ../.. + - command: npx --no rollup -- -c sources: - src - - ../../common-js + - ../../common-ts - rollup.config.mjs - tsconfig.js targets: - dist/main.js - - generateQuickjsCrate: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }} - wit: wit-generated - js: dist/main.js - - command: cargo component build --release - dir: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }} - sources: - - src - - wit - - Cargo.toml - targets: - - target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm - sourceWit: wit - generatedWit: wit-generated - componentWasm: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }}/target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm - linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}.wasm + - injectToPrebuiltQuickjs: ../../wasm/golem_agent.wasm + module: dist/main.js + moduleWasm: ../../golem-temp/agents/{{ component_name | to_snake_case }}.module.wasm + into: ../../golem-temp/agents/{{ component_name | to_snake_case }}.dynamic.wasm + - generateAgentWrapper: ../../golem-temp/agents/{{ component_name | to_snake_case }}.wrapper.wasm + basedOnCompiledWasm: ../../golem-temp/agents/{{ component_name | to_snake_case }}.dynamic.wasm + - composeAgentWrapper: ../../golem-temp/agents/{{ component_name | to_snake_case }}.wrapper.wasm + withAgent: ../../golem-temp/agents/{{ component_name | to_snake_case }}.dynamic.wasm + to: ../../golem-temp/agents/{{ component_name | to_snake_case }}.static.wasm + + sourceWit: ../../wasm/golem_agent.wasm + generatedWit: ../../golem-temp/agents/{{ component_name | to_snake_case }}/wit-generated + componentWasm: ../../golem-temp/agents/{{ component_name | to_snake_case }}.static.wasm + linkedWasm: ../../golem-temp/agents/{{ component_name | to_snake_case }}.wasm clean: - dist + - .metadata customCommands: ts-npm-install: - command: npm install diff --git a/golem-templates/templates/ts/ts-app-common/common-ts/rollup.config.component.mjs b/golem-templates/templates/ts/ts-app-common/common-ts/rollup.config.component.mjs index 0cb6ab917..e0320bb0d 100644 --- a/golem-templates/templates/ts/ts-app-common/common-ts/rollup.config.component.mjs +++ b/golem-templates/templates/ts/ts-app-common/common-ts/rollup.config.component.mjs @@ -1,65 +1,52 @@ -import * as fs from "node:fs"; import alias from "@rollup/plugin-alias"; +import json from "@rollup/plugin-json"; import nodeResolve from "@rollup/plugin-node-resolve"; import path from "node:path"; -import url from "node:url"; import typescript from "@rollup/plugin-typescript"; +import url from "node:url"; export default function componentRollupConfig() { const dir = path.dirname(url.fileURLToPath(import.meta.url)); - const moduleRegex = /declare\s+module\s+['"]([^'"]+)['"]/g; - const generated_interfaces_dir = "src/generated/interfaces"; - - const externalPackages = (() => { - if (!fs.existsSync(generated_interfaces_dir)) { - return []; - } - return fs - .readdirSync(generated_interfaces_dir, { withFileTypes: true }) - .filter( - (dirent) => - dirent.isFile() && - dirent.name.endsWith(".d.ts") && - dirent.name !== "exports.d.ts", - ) - .flatMap((dirent) => - [ - ...fs - .readFileSync(path.join(generated_interfaces_dir, dirent.name)) - .toString() - .matchAll(moduleRegex), - ].map((match) => { - const moduleName = match[1]; - if (moduleName === undefined) { - throw new Error(`Missing match for module name`); - } - return moduleName; - }), - ); - })(); - console.log("External packages:", externalPackages); + const externalPackages = (id) => { + return ( + id === "@golemcloud/golem-ts-sdk" || + id.startsWith("golem:api") || + id.startsWith("golem:rpc") + ); + }; return { - input: "src/main.ts", + input: ".agent/main.ts", output: { file: "dist/main.js", format: "esm", + inlineDynamicImports: true, + sourcemap: false, }, external: externalPackages, plugins: [ alias({ entries: [ - {find: 'common', replacement: path.resolve(dir, "../common-ts/src")} - ] + { + find: "common", + replacement: path.resolve(dir, "../common-ts/src"), + }, + ], }), nodeResolve({ - extensions: [".mjs", ".js", ".json", ".node", ".ts"] + extensions: [".mjs", ".js", ".json", ".node", ".ts"], }), typescript({ noEmitOnError: true, - include: ["./src/**/*.ts", "../../common-ts/src/**/*.ts"] - }) + include: [ + "./src/**/*.ts", + ".agent/**/*.ts", + ".metadata/**/*.ts", + "../../common-ts/src/**/*.ts", + ], + }), + json(), ], }; } diff --git a/golem-templates/templates/ts/ts-app-common/common-ts/tsconfig.component.json b/golem-templates/templates/ts/ts-app-common/common-ts/tsconfig.component.json index e5fbabbec..216e8107a 100644 --- a/golem-templates/templates/ts/ts-app-common/common-ts/tsconfig.component.json +++ b/golem-templates/templates/ts/ts-app-common/common-ts/tsconfig.component.json @@ -4,21 +4,20 @@ "skipLibCheck": true, "target": "ES2020", "noEmit": true, - "lib": [ - "ES2020" - ], - "types": [ - "node" - ], + "lib": ["ES2020"], + "types": ["node"], "moduleResolution": "bundler", "checkJs": false, "strict": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "useDefineForClassFields": false, "paths": { - "common/*": [ - "../common-ts/src/*" - ] + "common/*": ["../common-ts/src/*"] } } -} \ No newline at end of file +} diff --git a/golem-templates/templates/ts/ts-app-common/metadata.json b/golem-templates/templates/ts/ts-app-common/metadata.json index d268eb69a..4a87eb4e5 100644 --- a/golem-templates/templates/ts/ts-app-common/metadata.json +++ b/golem-templates/templates/ts/ts-app-common/metadata.json @@ -1,5 +1,5 @@ { - "description": "Composable App Common template for TypeScript", + "description": "Composable App Common template for TypeScript Agents", "appCommonGroup": "default", "appCommonSkipIfExists": "common-ts/golem.yaml", "requiresGolemHostWIT": true, diff --git a/golem-templates/templates/ts/ts-app-common/package.json b/golem-templates/templates/ts/ts-app-common/package.json index 07ce18af3..b8aa12e84 100644 --- a/golem-templates/templates/ts/ts-app-common/package.json +++ b/golem-templates/templates/ts/ts-app-common/package.json @@ -7,15 +7,19 @@ "components-ts/*/*" ], "dependencies": { - "@golemcloud/golem-ts": "1.5.0" + "@golemcloud/golem-ts": "1.5.0", + "@golemcloud/golem-ts-sdk": "0.0.1-dev.3", + "rttist": "^1.0.0-rc.4" }, "devDependencies": { "@rollup/plugin-alias": "^5.1.1", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-typescript": "^12.1.2", "@types/node": "^22.10.5", "rollup": "^4.29.1", "tslib": "^2.8.1", - "typescript": "^5.7.2" + "typescript": "^5.8.3", + "@rttist/typegen": "^0.2.0" } } diff --git a/golem-templates/templates/ts/ts-app-common/wasm/golem_agent.wasm b/golem-templates/templates/ts/ts-app-common/wasm/golem_agent.wasm new file mode 100644 index 000000000..0fc900dc5 Binary files /dev/null and b/golem-templates/templates/ts/ts-app-common/wasm/golem_agent.wasm differ diff --git a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/.agent/main.ts b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/.agent/main.ts new file mode 100644 index 000000000..12a0edbc5 --- /dev/null +++ b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/.agent/main.ts @@ -0,0 +1,15 @@ +import '../.metadata/metadata.index'; +import { Metadata } from '@golemcloud/golem-ts-sdk'; +import { metadataCollection } from '../.metadata/metadata.index'; + +// Clear preloaded metadata +Metadata.clearMetadata("@golemcloud/golem-ts-sdk"); +// Load generated metadata +metadataCollection.forEach(mod => mod.add(Metadata, false)); + +// Import the user module after metadata is ready +// This needs to be done this way otherwise rollup ends up generating the module, +// where loading the metadata comes after the user module is loaded - resulting in errors. +export default (async () => { + return await import("../src/main"); +})(); diff --git a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/reflect.config.json b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/reflect.config.json new file mode 100644 index 000000000..b148bcb17 --- /dev/null +++ b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/reflect.config.json @@ -0,0 +1,11 @@ +{ + "$schema": "../../node_modules/@rttist/typegen/config-schema.json", + "metadata": { + "include": [ + "src/**/*.ts" + ], + "encode": false, + "exclude": [] + }, + "logLevel": "Info" +} \ No newline at end of file diff --git a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/src/main.ts b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/src/main.ts index ef6bbb612..7526999ff 100644 --- a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/src/main.ts +++ b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/src/main.ts @@ -1,18 +1,51 @@ -import type * as bindings from "component-name" - -// Use this import for using the common lib: -// import {exampleCommonFunction} from "common/lib"; - -let state = 0; - -export const componentNameApi: typeof bindings.componentNameApi = { - async add(value: number) { - // Example common lib use: - // console.log(example_common_function()); - console.log(`Adding ${value} to the counter`); - state += value; - }, - async get() { - return state; +import { + BaseAgent, + Agent, + Prompt, + Description, +} from '@golemcloud/golem-ts-sdk'; + +@Agent() +class AssistantAgent extends BaseAgent { + @Prompt("Ask your question") + @Description("This method allows the agent to answer your question") + async ask(name: string): Promise { + const customData = { data: "Sample data", value: 42 }; + + const remoteWeatherClient = WeatherAgent.createRemote(""); + const remoteWeather = await remoteWeatherClient.getWeather(name, customData); + + const localWeatherClient = WeatherAgent.createLocal("afsal"); + const localWeather = await localWeatherClient.getWeather(name, customData); + + return ( + `Hello! I'm the assistant agent (${this.getId()}) reporting on the weather in ${name}. ` + + `Here’s what the weather agent says: "\n${localWeather}\n". ` + + `Info retrieved using weather agent (${localWeatherClient.getId()}).` + ); + } +} + +@Agent() +class WeatherAgent extends BaseAgent { + private readonly userName: string; + + constructor(username: string) { + super() + this.userName = username; } -}; + + @Prompt("Get weather") + @Description("Weather forecast weather for you") + async getWeather(name: string, param2: CustomData): Promise { + return Promise.resolve( + `Hi ${this.userName} Weather in ${name} is sunny. Params passed: ${name} ${JSON.stringify(param2)}. ` + + `Computed by weather-agent ${this.getId()}. ` + ); + } +} + +interface CustomData { + data: String; + value: number; +} diff --git a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/tsconfig.json b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/tsconfig.json index 17748c83d..c827e32c5 100644 --- a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/tsconfig.json +++ b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/tsconfig.json @@ -2,6 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../common-ts/tsconfig.component.json", "include": [ - "src/**/*.ts" + "src/**/*.ts", + ".agent/**/*", + ".metadata/**/*" ] } \ No newline at end of file diff --git a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/wit/main.wit b/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/wit/main.wit deleted file mode 100644 index 7a10d254e..000000000 --- a/golem-templates/templates/ts/ts-app-component-default/components-ts/component-name/wit/main.wit +++ /dev/null @@ -1,12 +0,0 @@ -package pack:name; - -// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax - -interface component-name-api { - add: func(value: u64); - get: func() -> u64; -} - -world component-name { - export component-name-api; -} diff --git a/golem-templates/templates/ts/ts-app-component-default/metadata.json b/golem-templates/templates/ts/ts-app-component-default/metadata.json index 5e7525f69..5021ab306 100644 --- a/golem-templates/templates/ts/ts-app-component-default/metadata.json +++ b/golem-templates/templates/ts/ts-app-component-default/metadata.json @@ -1,4 +1,4 @@ { - "description": "The default component template for JavaScript", + "description": "The default agent template for TypeScript", "appComponentGroup": "default" } \ No newline at end of file diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/.gitignore b/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/.gitignore deleted file mode 100644 index a80c710b8..000000000 --- a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/*/dist -/*/src/generated -/*/wit-generated diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/golem.yaml b/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/golem.yaml deleted file mode 100644 index 417288623..000000000 --- a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/golem.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# golem-app-manifest-header - -components: - componentname: - template: ts - -# golem-app-manifest-component-hints diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/package.json b/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/package.json deleted file mode 100644 index 514b91ea8..000000000 --- a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "component-name" -} diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/rollup.config.mjs b/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/rollup.config.mjs deleted file mode 100644 index 529063f4f..000000000 --- a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/rollup.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import componentRollupConfig from "../../common-ts/rollup.config.component.mjs"; - -export default componentRollupConfig(); diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/tsconfig.json b/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/tsconfig.json deleted file mode 100644 index 17748c83d..000000000 --- a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "../../common-ts/tsconfig.component.json", - "include": [ - "src/**/*.ts" - ] -} \ No newline at end of file diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/metadata.json b/golem-templates/templates/ts/ts-app-component-example-fetch/metadata.json deleted file mode 100644 index cc43b5706..000000000 --- a/golem-templates/templates/ts/ts-app-component-example-fetch/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "description": "A JavaScript worker making a remote request using 'fetch'", - "appComponentGroup": "default" -} \ No newline at end of file diff --git a/golem-templates/templates/js/componentizejs-app-common/.gitignore b/golem-templates/templates/ts/ts-generic-app-common/.gitignore similarity index 100% rename from golem-templates/templates/js/componentizejs-app-common/.gitignore rename to golem-templates/templates/ts/ts-generic-app-common/.gitignore diff --git a/golem-templates/templates/ts/ts-generic-app-common/common-ts/golem.yaml b/golem-templates/templates/ts/ts-generic-app-common/common-ts/golem.yaml new file mode 100644 index 000000000..d7e2cf843 --- /dev/null +++ b/golem-templates/templates/ts/ts-generic-app-common/common-ts/golem.yaml @@ -0,0 +1,36 @@ +# golem-app-manifest-header + +templates: + ts: + build: + - generateQuickjsDts: src/generated/interfaces + wit: wit-generated + - command: npx rollup -c + sources: + - src + - ../../common-ts + - rollup.config.mjs + - tsconfig.js + targets: + - dist/main.js + - generateQuickjsCrate: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }} + wit: wit-generated + jsModules: + main: dist/main.js + - command: cargo component build --release + dir: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }} + sources: + - src + - wit + - Cargo.toml + targets: + - target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../golem-temp/quickjs-wrappers/{{ component_name | to_snake_case }}/target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm + linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}.wasm + clean: + - dist +customCommands: + ts-npm-install: + - command: npm install diff --git a/golem-templates/templates/ts/componentizets-app-common/common-ts/rollup.config.component.mjs b/golem-templates/templates/ts/ts-generic-app-common/common-ts/rollup.config.component.mjs similarity index 60% rename from golem-templates/templates/ts/componentizets-app-common/common-ts/rollup.config.component.mjs rename to golem-templates/templates/ts/ts-generic-app-common/common-ts/rollup.config.component.mjs index 791da16d3..0cb6ab917 100644 --- a/golem-templates/templates/ts/componentizets-app-common/common-ts/rollup.config.component.mjs +++ b/golem-templates/templates/ts/ts-generic-app-common/common-ts/rollup.config.component.mjs @@ -1,9 +1,9 @@ import * as fs from "node:fs"; -import alias from '@rollup/plugin-alias'; +import alias from "@rollup/plugin-alias"; import nodeResolve from "@rollup/plugin-node-resolve"; import path from "node:path"; -import typescript from "@rollup/plugin-typescript"; import url from "node:url"; +import typescript from "@rollup/plugin-typescript"; export default function componentRollupConfig() { const dir = path.dirname(url.fileURLToPath(import.meta.url)); @@ -15,19 +15,26 @@ export default function componentRollupConfig() { return []; } return fs - .readdirSync(generated_interfaces_dir, {withFileTypes: true}) - .filter(dirent => dirent.isFile() && dirent.name.endsWith(".d.ts")) - .flatMap(dirent => - [...fs.readFileSync(path.join(dirent.parentPath, dirent.name)) - .toString() - .matchAll(moduleRegex)] - .map((match) => { - const moduleName = match[1]; - if (moduleName === undefined) { - throw new Error(`Missing match for module name`); - } - return moduleName; - }), + .readdirSync(generated_interfaces_dir, { withFileTypes: true }) + .filter( + (dirent) => + dirent.isFile() && + dirent.name.endsWith(".d.ts") && + dirent.name !== "exports.d.ts", + ) + .flatMap((dirent) => + [ + ...fs + .readFileSync(path.join(generated_interfaces_dir, dirent.name)) + .toString() + .matchAll(moduleRegex), + ].map((match) => { + const moduleName = match[1]; + if (moduleName === undefined) { + throw new Error(`Missing match for module name`); + } + return moduleName; + }), ); })(); diff --git a/golem-templates/templates/ts/ts-generic-app-common/common-ts/src/lib.ts b/golem-templates/templates/ts/ts-generic-app-common/common-ts/src/lib.ts new file mode 100644 index 000000000..179615db5 --- /dev/null +++ b/golem-templates/templates/ts/ts-generic-app-common/common-ts/src/lib.ts @@ -0,0 +1,3 @@ +export function exampleCommonFunction() { + return "hello common"; +} diff --git a/golem-templates/templates/ts/componentizets-app-common/common-ts/tsconfig.component.json b/golem-templates/templates/ts/ts-generic-app-common/common-ts/tsconfig.component.json similarity index 100% rename from golem-templates/templates/ts/componentizets-app-common/common-ts/tsconfig.component.json rename to golem-templates/templates/ts/ts-generic-app-common/common-ts/tsconfig.component.json diff --git a/golem-templates/templates/js/componentizejs-app-common/golem.yaml b/golem-templates/templates/ts/ts-generic-app-common/golem.yaml similarity index 100% rename from golem-templates/templates/js/componentizejs-app-common/golem.yaml rename to golem-templates/templates/ts/ts-generic-app-common/golem.yaml diff --git a/golem-templates/templates/ts/componentizets-app-common/metadata.json b/golem-templates/templates/ts/ts-generic-app-common/metadata.json similarity index 86% rename from golem-templates/templates/ts/componentizets-app-common/metadata.json rename to golem-templates/templates/ts/ts-generic-app-common/metadata.json index 236135b1e..93ccf9f9e 100644 --- a/golem-templates/templates/ts/componentizets-app-common/metadata.json +++ b/golem-templates/templates/ts/ts-generic-app-common/metadata.json @@ -1,6 +1,6 @@ { "description": "Composable App Common template for TypeScript", - "appCommonGroup": "componentizejs", + "appCommonGroup": "generic", "appCommonSkipIfExists": "common-ts/golem.yaml", "requiresGolemHostWIT": true, "requiresWASI": true, diff --git a/golem-templates/templates/ts/componentizets-app-common/package.json b/golem-templates/templates/ts/ts-generic-app-common/package.json similarity index 82% rename from golem-templates/templates/ts/componentizets-app-common/package.json rename to golem-templates/templates/ts/ts-generic-app-common/package.json index 361a4da76..07ce18af3 100644 --- a/golem-templates/templates/ts/componentizets-app-common/package.json +++ b/golem-templates/templates/ts/ts-generic-app-common/package.json @@ -1,6 +1,8 @@ { "name": "app", "workspaces": [ + "common-js/*/*", + "components-js/*/*", "common-ts/*/*", "components-ts/*/*" ], @@ -8,8 +10,6 @@ "@golemcloud/golem-ts": "1.5.0" }, "devDependencies": { - "@bytecodealliance/jco": "1.10.2", - "@bytecodealliance/componentize-js": "0.18.0", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-typescript": "^12.1.2", diff --git a/golem-templates/templates/js/componentizejs-app-common/wit/common.wit b/golem-templates/templates/ts/ts-generic-app-common/wit/common.wit similarity index 100% rename from golem-templates/templates/js/componentizejs-app-common/wit/common.wit rename to golem-templates/templates/ts/ts-generic-app-common/wit/common.wit diff --git a/golem-templates/templates/js/componentizejs-app-component-default/components-js/.gitignore b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/.gitignore similarity index 100% rename from golem-templates/templates/js/componentizejs-app-component-default/components-js/.gitignore rename to golem-templates/templates/ts/ts-generic-app-component-default/components-ts/.gitignore diff --git a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/golem.yaml b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/golem.yaml similarity index 100% rename from golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/golem.yaml rename to golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/golem.yaml diff --git a/golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/package.json b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/package.json similarity index 100% rename from golem-templates/templates/js/componentizejs-app-component-default/components-js/component-name/package.json rename to golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/package.json diff --git a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/rollup.config.mjs b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/rollup.config.mjs similarity index 100% rename from golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/rollup.config.mjs rename to golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/rollup.config.mjs diff --git a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/src/main.ts b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/src/main.ts similarity index 60% rename from golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/src/main.ts rename to golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/src/main.ts index bf8adc65b..ef6bbb612 100644 --- a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/src/main.ts +++ b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/src/main.ts @@ -1,13 +1,12 @@ -/// -import type * as bindings from "pack:name/component-name" +import type * as bindings from "component-name" // Use this import for using the common lib: -// import {example_common_function} from "common/lib"; +// import {exampleCommonFunction} from "common/lib"; -let state = BigInt(0); +let state = 0; export const componentNameApi: typeof bindings.componentNameApi = { - async add(value: bigint) { + async add(value: number) { // Example common lib use: // console.log(example_common_function()); console.log(`Adding ${value} to the counter`); diff --git a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/tsconfig.json b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/tsconfig.json similarity index 100% rename from golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/tsconfig.json rename to golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/tsconfig.json diff --git a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/wit/main.wit b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/wit/main.wit similarity index 82% rename from golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/wit/main.wit rename to golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/wit/main.wit index 7a10d254e..358fc151a 100644 --- a/golem-templates/templates/ts/componentizets-app-component-default/components-ts/component-name/wit/main.wit +++ b/golem-templates/templates/ts/ts-generic-app-component-default/components-ts/component-name/wit/main.wit @@ -3,8 +3,8 @@ package pack:name; // See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax interface component-name-api { - add: func(value: u64); - get: func() -> u64; + add: func(value: u32); + get: func() -> u32; } world component-name { diff --git a/golem-templates/templates/ts/componentizets-app-component-default/metadata.json b/golem-templates/templates/ts/ts-generic-app-component-default/metadata.json similarity index 63% rename from golem-templates/templates/ts/componentizets-app-component-default/metadata.json rename to golem-templates/templates/ts/ts-generic-app-component-default/metadata.json index 33159fbf8..ffc0c5943 100644 --- a/golem-templates/templates/ts/componentizets-app-component-default/metadata.json +++ b/golem-templates/templates/ts/ts-generic-app-component-default/metadata.json @@ -1,4 +1,4 @@ { "description": "The default component template for TypeScript", - "appComponentGroup": "componentizejs" + "appComponentGroup": "generic" } \ No newline at end of file diff --git a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/.gitignore b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/.gitignore similarity index 100% rename from golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/.gitignore rename to golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/.gitignore diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/golem.yaml b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/golem.yaml similarity index 100% rename from golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/golem.yaml rename to golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/golem.yaml diff --git a/golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/package.json b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/package.json similarity index 100% rename from golem-templates/templates/js/componentizejs-app-component-example-fetch/components-js/component-name/package.json rename to golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/package.json diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/rollup.config.mjs b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/rollup.config.mjs similarity index 100% rename from golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/rollup.config.mjs rename to golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/rollup.config.mjs diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/src/main.ts b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/src/main.ts similarity index 100% rename from golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/src/main.ts rename to golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/src/main.ts diff --git a/golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/tsconfig.json b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/tsconfig.json similarity index 100% rename from golem-templates/templates/ts/componentizets-app-component-example-fetch/components-ts/component-name/tsconfig.json rename to golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/tsconfig.json diff --git a/golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/wit/main.wit b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/wit/main.wit similarity index 100% rename from golem-templates/templates/ts/ts-app-component-example-fetch/components-ts/component-name/wit/main.wit rename to golem-templates/templates/ts/ts-generic-app-component-example-fetch/components-ts/component-name/wit/main.wit diff --git a/golem-templates/templates/ts/ts-generic-app-component-example-fetch/metadata.json b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/metadata.json new file mode 100644 index 000000000..1db53ddff --- /dev/null +++ b/golem-templates/templates/ts/ts-generic-app-component-example-fetch/metadata.json @@ -0,0 +1,4 @@ +{ + "description": "A TypeScript worker making a remote request using 'fetch'", + "appComponentGroup": "generic" +} \ No newline at end of file diff --git a/template-golem-agent-ts/.gitignore b/template-golem-agent-ts/.gitignore new file mode 100644 index 000000000..4dce98c95 --- /dev/null +++ b/template-golem-agent-ts/.gitignore @@ -0,0 +1,3 @@ +src/generated +node_modules +dist \ No newline at end of file diff --git a/template-golem-agent-ts/package-lock.json b/template-golem-agent-ts/package-lock.json new file mode 100644 index 000000000..47e7dd015 --- /dev/null +++ b/template-golem-agent-ts/package-lock.json @@ -0,0 +1,19 @@ +{ + "name": "app", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "app", + "dependencies": { + "@golemcloud/golem-ts-sdk": "0.0.1-dev.3" + } + }, + "node_modules/@golemcloud/golem-ts-sdk": { + "version": "0.0.1-dev.3", + "resolved": "https://registry.npmjs.org/@golemcloud/golem-ts-sdk/-/golem-ts-sdk-0.0.1-dev.3.tgz", + "integrity": "sha512-Lviy5a9h+cOVqU/VyXZWUPQPb7c8M0ph5AyKpPXZ3hTc+2OwV0zxI/PUh5wjJvSb6SIJMjy1x72RutF3Bj/HzQ==", + "license": "ISC" + } + } +} diff --git a/template-golem-agent-ts/package.json b/template-golem-agent-ts/package.json new file mode 100644 index 000000000..28a056a7c --- /dev/null +++ b/template-golem-agent-ts/package.json @@ -0,0 +1,11 @@ +{ + "name": "app", + "dependencies": { + "@golemcloud/golem-ts-sdk": "0.0.1-dev.3" + }, + "scripts": { + "update": "wit-deps update", + "generate": "wasm-rquickjs generate-wrapper-crate --wit wit --output dist/wrapper --world golem-agent --js-modules '@golemcloud/golem-ts-sdk=node_modules/@golemcloud/golem-ts-sdk/dist/index.mjs' --js-modules 'user=@composition'", + "build": "cargo component build --manifest-path=dist/wrapper/Cargo.toml --release" + } +} diff --git a/template-golem-agent-ts/wit/deps.lock b/template-golem-agent-ts/wit/deps.lock new file mode 100644 index 000000000..e906775ae --- /dev/null +++ b/template-golem-agent-ts/wit/deps.lock @@ -0,0 +1,69 @@ +[agent] +url = "https://github.com/golemcloud/golem-agentic/archive/main.tar.gz" +sha256 = "860bbac7d96cfcdfb2c31bbb8c8d8430e55f87300042ee94228228c37d554872" +sha512 = "cb3d758b6b8c834131479ebd17e63a83ac9f376e33183bb00947e269da0ffefbeb1c945be5a51fc30497d8f6e833e20124f3f1ff6a63e9b67afeed094cfa8961" +deps = ["all", "blobstore", "cli", "clocks", "config", "filesystem", "golem-1.x", "golem-agent", "golem-durability", "golem-rpc", "http", "io", "keyvalue", "logging", "random", "sockets"] + +[all] +sha256 = "6ac4f1553003474229bb40a885b48d1eaa4bea2f9d679a115da9afede30b5ad4" +sha512 = "ced8c18a35dc04123386465d98d2aeba54c486e4e22a30e7efac0548974e176528fcba36c64942d67fa35d9135806ff0fcde259eb4af051c4a29fcb397e9af1c" + +[blobstore] +sha256 = "f7857c92a8d540ebfe10c9ac3b8cfe160d2303ca638d59aec3c18406e084d367" +sha512 = "af647a1c40672c4b2b970b2f99de54d404e35b4aed430b00395d708dcec74eb6f03543b65605a90a1dbc3db5dbf1887b2b6ae9b6c6306475d119b312b4e91868" + +[cli] +sha256 = "4dadd13d55aaf626833d1f4b9c34a17b0f04e993babd09552b785cda3b95ea76" +sha512 = "898dcc4e8c15d18acc6b88dbe232336fa4d19019430a910dbc9e7aeaace3077a164af3be9f002de6e7e65ef693df340801ac0c7e421e9a746bf1b6d698a90835" + +[clocks] +sha256 = "93a701968a7dd3c5d69031bc0601681c468972fdf7e28a93bb6150a67d6ebe8b" +sha512 = "98fca567c7a01887b0fb38981f1772169b6ea8de475b546508f8b86738d84e44ba95cae81def40ac34e8809f5f60e85224077ab8cb6d6d5d6296acc1df73c159" + +[config] +sha256 = "31fe247a242af7abe32dfadcb4edbc7720f9c47c2bff34b7e5c75df915ab5eb9" +sha512 = "ce5367f3a104843852236799a3386a44da12ee5e287eb94075fd5252c1400b6ef84438f3b3e19f75fb3d842d82fe3a1bc999385fe59fc8e1696fed69bad69eac" + +[filesystem] +sha256 = "69d42fb10a04a33545b17e055f13db9b1e10e82ba0ed5bdb52334e40dc07c679" +sha512 = "612effbac6f4804fe0c29dae20b78bbba59e52cb754c15402f5fe229c3153a221e0fbdff1d9d00ceaa3fe049c6a95523a5b99f772f1c16d972eade2c88326a30" + +["golem-1.x"] +sha256 = "d707888500487684ea753aa86547665221803d64445fcb1ef23470ded04eaab7" +sha512 = "7558b6110b7f8b43b8b9858e9236eba2569babf9a23204d186785e6693172c94856e271406b7753ed75c8e1fefb2558e7038b10f3dead448a4ea23975f3d10df" + +[golem-agent] +sha256 = "7a7bab29059c195c95878c1ad3cacdb2e82ba7a89e309684eedfe02cbe2c5bcf" +sha512 = "71c638d2f5c75627423f31ce4e8b518831636ebb32b1948384df3a47595a408e8b3a4a415f20ec571135ca2a5e1fc5684bb9c91f0b7f00fcb79bef3f00e11d8f" + +[golem-durability] +sha256 = "c5f8b9b4bda5a8d5d1d008978abed617c18da7d9c24167bf20a81a379d10d688" +sha512 = "40e09d412fd6faee904ce6f8abdcf3f83a604eca3628bf5605b7667540874ea53f9e29392059dac327ca96a0f1298372cb0626abcf66a1df4f4180731c13646a" + +[golem-rpc] +sha256 = "a73f66a61e81b237057117b14aecc6c4c20eb004a4244ae10a51a876f71cf626" +sha512 = "396a1738320f7dc34a9a066755397b132b976e81c7cd28a8f45ff32aa5ea3226eb91d1e26d73995104b397fbd063ff83090550718f805773ceb78eb4f2155acf" + +[http] +sha256 = "72d3a00dbf39eed40a134e8b1dee85834961153f9d205ee4dd56657270c084ce" +sha512 = "636150c464c0eb3d60bd212fc5d4012638c8cd4f89b583b87a38154ef99de828aac4296ac13c5cface10ee61e164fcfc43a5c104f916229dfdf49c0d11047677" + +[io] +sha256 = "1cccbfe4122686ea57a25cd368e8cdfc408cbcad089f47fb6685b6f92e96f050" +sha512 = "7a95f964c13da52611141acd89bc8876226497f128e99dd176a4270c5b5efbd8cc847b5fbd1a91258d028c646db99e0424d72590cf1caf20f9f3a3343fad5017" + +[keyvalue] +sha256 = "0b37334713ee32ddbf6c064ddc3fe8e5b730ff63b6381b45c87aa07a6ec700ca" +sha512 = "1ec372a657a2a9f602e781846984febfdc7c34c3dc8a2c83d5b282f5580edd1d556478bbbb2a34e97c618a03b05fcc1059d6f4a01dc27176f5e5702e5173507e" + +[logging] +sha256 = "7fb7216c713fd329ec91bb596127a1b95eec26290290b6de0c1ef294fba858c2" +sha512 = "ceb68410bb46176051b0eee54e1a60831ca2fe820892c5f5f739e76efd4fe6ee6dc5e307947e7118b68a2a757ed3a3b471d16eb0900bb9040de47fef4bf1b56f" + +[random] +sha256 = "dd0c91e7125172eb8fd4568e15ad9fc7305643015e6ece4396c3cc5e8c2bf79a" +sha512 = "d1ca2e7b0616a94a3b39d1b9450bb3fb595b01fd94a8626ad75433038dde40988ecb41ab93a374d569ab72163af3b30038d7bfc3499b9c07193181f4f1d9292a" + +[sockets] +sha256 = "2bc0f65a8046207ee3330ad7d63f6fafeafd4cc0ea4084f081bd5e4f7b177e74" +sha512 = "3e5490e41547dffa78d52631825d93da8d60f4af0246cbaf97e1ecb879285953a86d5f1f390b10c32f91dd7eaec6f43e625a26b1c92c32a0c86fde428aedaaab" diff --git a/template-golem-agent-ts/wit/deps.toml b/template-golem-agent-ts/wit/deps.toml new file mode 100644 index 000000000..f1ad03aa3 --- /dev/null +++ b/template-golem-agent-ts/wit/deps.toml @@ -0,0 +1 @@ +agent = "https://github.com/golemcloud/golem-agentic/archive/main.tar.gz" diff --git a/template-golem-agent-ts/wit/deps/agent/main.wit b/template-golem-agent-ts/wit/deps/agent/main.wit new file mode 100644 index 000000000..1c300786e --- /dev/null +++ b/template-golem-agent-ts/wit/deps/agent/main.wit @@ -0,0 +1 @@ +package unused:agentic; diff --git a/template-golem-agent-ts/wit/deps/all/main.wit b/template-golem-agent-ts/wit/deps/all/main.wit new file mode 100644 index 000000000..0a1f74f41 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/all/main.wit @@ -0,0 +1 @@ +package unused:main; diff --git a/template-golem-agent-ts/wit/deps/blobstore/blobstore.wit b/template-golem-agent-ts/wit/deps/blobstore/blobstore.wit new file mode 100644 index 000000000..cc52516a5 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/blobstore/blobstore.wit @@ -0,0 +1,29 @@ +package wasi:blobstore; + +// wasi-cloud Blobstore service definition +interface blobstore { + use container.{container}; + use types.{error, container-name, object-id}; + + // creates a new empty container + create-container: func(name: container-name) -> result; + + // retrieves a container by name + get-container: func(name: container-name) -> result; + + // deletes a container and all objects within it + delete-container: func(name: container-name) -> result<_, error>; + + // returns true if the container exists + container-exists: func(name: container-name) -> result; + + // copies (duplicates) an object, to the same or a different container. + // returns an error if the target container does not exist. + // overwrites destination object if it already existed. + copy-object: func(src: object-id, dest: object-id) -> result<_, error>; + + // moves or renames an object, to the same or a different container + // returns an error if the destination container does not exist. + // overwrites destination object if it already existed. + move-object: func(src:object-id, dest: object-id) -> result<_, error>; +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/blobstore/container.wit b/template-golem-agent-ts/wit/deps/blobstore/container.wit new file mode 100644 index 000000000..0652def81 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/blobstore/container.wit @@ -0,0 +1,68 @@ +package wasi:blobstore; + +// a Container is a collection of objects +interface container { + use wasi:io/streams@0.2.3.{ + input-stream, + output-stream, + }; + + use types.{ + container-metadata, + error, + incoming-value, + object-metadata, + object-name, + outgoing-value, + }; + + // this defines the `container` resource + resource container { + // returns container name + name: func() -> result; + + // returns container metadata + info: func() -> result; + + // retrieves an object or portion of an object, as a resource. + // Start and end offsets are inclusive. + // Once a data-blob resource has been created, the underlying bytes are held by the blobstore service for the lifetime + // of the data-blob resource, even if the object they came from is later deleted. + get-data: func(name: object-name, start: u64, end: u64) -> result; + + // creates or replaces an object with the data blob. + write-data: func(name: object-name, data: borrow) -> result<_, error>; + + // returns list of objects in the container. Order is undefined. + list-objects: func() -> result; + + // deletes object. + // does not return error if object did not exist. + delete-object: func(name: object-name) -> result<_, error>; + + // deletes multiple objects in the container + delete-objects: func(names: list) -> result<_, error>; + + // returns true if the object exists in this container + has-object: func(name: object-name) -> result; + + // returns metadata for the object + object-info: func(name: object-name) -> result; + + // removes all objects within the container, leaving the container empty. + clear: func() -> result<_, error>; + } + + // this defines the `stream-object-names` resource which is a representation of stream + resource stream-object-names { + // reads the next number of objects from the stream + // + // This function returns the list of objects read, and a boolean indicating if the end of the stream was reached. + read-stream-object-names: func(len: u64) -> result, bool>, error>; + + // skip the next number of objects in the stream + // + // This function returns the number of objects skipped, and a boolean indicating if the end of the stream was reached. + skip-stream-object-names: func(num: u64) -> result, error>; + } +} diff --git a/template-golem-agent-ts/wit/deps/blobstore/types.wit b/template-golem-agent-ts/wit/deps/blobstore/types.wit new file mode 100644 index 000000000..42cfc9527 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/blobstore/types.wit @@ -0,0 +1,77 @@ +package wasi:blobstore; + +// Types used by blobstore +interface types { + use wasi:io/streams@0.2.3.{input-stream, output-stream}; + + // name of a container, a collection of objects. + // The container name may be any valid UTF-8 string. + type container-name = string; + + // name of an object within a container + // The object name may be any valid UTF-8 string. + type object-name = string; + + // TODO: define timestamp to include seconds since + // Unix epoch and nanoseconds + // https://github.com/WebAssembly/wasi-blob-store/issues/7 + type timestamp = u64; + + // size of an object, in bytes + type object-size = u64; + + type error = string; + + // information about a container + record container-metadata { + // the container's name + name: container-name, + // date and time container was created + created-at: timestamp, + } + + // information about an object + record object-metadata { + // the object's name + name: object-name, + // the object's parent container + container: container-name, + // date and time the object was created + created-at: timestamp, + // size of the object, in bytes + size: object-size, + } + + // identifier for an object that includes its container name + record object-id { + container: container-name, + object: object-name + } + + /// A data is the data stored in a data blob. The value can be of any type + /// that can be represented in a byte array. It provides a way to write the value + /// to the output-stream defined in the `wasi-io` interface. + // Soon: switch to `resource value { ... }` + resource outgoing-value { + new-outgoing-value: static func() -> outgoing-value; + outgoing-value-write-body: func() -> result; + } + + /// A incoming-value is a wrapper around a value. It provides a way to read the value + /// from the input-stream defined in the `wasi-io` interface. + /// + /// The incoming-value provides two ways to consume the value: + /// 1. `incoming-value-consume-sync` consumes the value synchronously and returns the + /// value as a list of bytes. + /// 2. `incoming-value-consume-async` consumes the value asynchronously and returns the + /// value as an input-stream. + // Soon: switch to `resource incoming-value { ... }` + resource incoming-value { + incoming-value-consume-sync: func() -> result; + incoming-value-consume-async: func() -> result; + size: func() -> u64; + } + + type incoming-value-async-body = input-stream; + type incoming-value-sync-body = list; +} diff --git a/template-golem-agent-ts/wit/deps/blobstore/world.wit b/template-golem-agent-ts/wit/deps/blobstore/world.wit new file mode 100644 index 000000000..4391d68b8 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/blobstore/world.wit @@ -0,0 +1,5 @@ +package wasi:blobstore; + +world blob-store { + import blobstore; +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/cli/command.wit b/template-golem-agent-ts/wit/deps/cli/command.wit new file mode 100644 index 000000000..3a81766d6 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/cli/command.wit @@ -0,0 +1,10 @@ +package wasi:cli@0.2.3; + +@since(version = 0.2.0) +world command { + @since(version = 0.2.0) + include imports; + + @since(version = 0.2.0) + export run; +} diff --git a/template-golem-agent-ts/wit/deps/cli/environment.wit b/template-golem-agent-ts/wit/deps/cli/environment.wit new file mode 100644 index 000000000..2f449bd7c --- /dev/null +++ b/template-golem-agent-ts/wit/deps/cli/environment.wit @@ -0,0 +1,22 @@ +@since(version = 0.2.0) +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + @since(version = 0.2.0) + get-environment: func() -> list>; + + /// Get the POSIX-style arguments to the program. + @since(version = 0.2.0) + get-arguments: func() -> list; + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + @since(version = 0.2.0) + initial-cwd: func() -> option; +} diff --git a/template-golem-agent-ts/wit/deps/cli/exit.wit b/template-golem-agent-ts/wit/deps/cli/exit.wit new file mode 100644 index 000000000..427935c8d --- /dev/null +++ b/template-golem-agent-ts/wit/deps/cli/exit.wit @@ -0,0 +1,17 @@ +@since(version = 0.2.0) +interface exit { + /// Exit the current instance and any linked instances. + @since(version = 0.2.0) + exit: func(status: result); + + /// Exit the current instance and any linked instances, reporting the + /// specified status code to the host. + /// + /// The meaning of the code depends on the context, with 0 usually meaning + /// "success", and other values indicating various types of failure. + /// + /// This function does not return; the effect is analogous to a trap, but + /// without the connotation that something bad has happened. + @unstable(feature = cli-exit-with-code) + exit-with-code: func(status-code: u8); +} diff --git a/template-golem-agent-ts/wit/deps/cli/imports.wit b/template-golem-agent-ts/wit/deps/cli/imports.wit new file mode 100644 index 000000000..8b4e3975e --- /dev/null +++ b/template-golem-agent-ts/wit/deps/cli/imports.wit @@ -0,0 +1,36 @@ +package wasi:cli@0.2.3; + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + include wasi:clocks/imports@0.2.3; + @since(version = 0.2.0) + include wasi:filesystem/imports@0.2.3; + @since(version = 0.2.0) + include wasi:sockets/imports@0.2.3; + @since(version = 0.2.0) + include wasi:random/imports@0.2.3; + @since(version = 0.2.0) + include wasi:io/imports@0.2.3; + + @since(version = 0.2.0) + import environment; + @since(version = 0.2.0) + import exit; + @since(version = 0.2.0) + import stdin; + @since(version = 0.2.0) + import stdout; + @since(version = 0.2.0) + import stderr; + @since(version = 0.2.0) + import terminal-input; + @since(version = 0.2.0) + import terminal-output; + @since(version = 0.2.0) + import terminal-stdin; + @since(version = 0.2.0) + import terminal-stdout; + @since(version = 0.2.0) + import terminal-stderr; +} diff --git a/template-golem-agent-ts/wit/deps/cli/run.wit b/template-golem-agent-ts/wit/deps/cli/run.wit new file mode 100644 index 000000000..655346efb --- /dev/null +++ b/template-golem-agent-ts/wit/deps/cli/run.wit @@ -0,0 +1,6 @@ +@since(version = 0.2.0) +interface run { + /// Run the program. + @since(version = 0.2.0) + run: func() -> result; +} diff --git a/template-golem-agent-ts/wit/deps/cli/stdio.wit b/template-golem-agent-ts/wit/deps/cli/stdio.wit new file mode 100644 index 000000000..1b54f5318 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/cli/stdio.wit @@ -0,0 +1,26 @@ +@since(version = 0.2.0) +interface stdin { + @since(version = 0.2.0) + use wasi:io/streams@0.2.3.{input-stream}; + + @since(version = 0.2.0) + get-stdin: func() -> input-stream; +} + +@since(version = 0.2.0) +interface stdout { + @since(version = 0.2.0) + use wasi:io/streams@0.2.3.{output-stream}; + + @since(version = 0.2.0) + get-stdout: func() -> output-stream; +} + +@since(version = 0.2.0) +interface stderr { + @since(version = 0.2.0) + use wasi:io/streams@0.2.3.{output-stream}; + + @since(version = 0.2.0) + get-stderr: func() -> output-stream; +} diff --git a/template-golem-agent-ts/wit/deps/cli/terminal.wit b/template-golem-agent-ts/wit/deps/cli/terminal.wit new file mode 100644 index 000000000..d305498c6 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/cli/terminal.wit @@ -0,0 +1,62 @@ +/// Terminal input. +/// +/// In the future, this may include functions for disabling echoing, +/// disabling input buffering so that keyboard events are sent through +/// immediately, querying supported features, and so on. +@since(version = 0.2.0) +interface terminal-input { + /// The input side of a terminal. + @since(version = 0.2.0) + resource terminal-input; +} + +/// Terminal output. +/// +/// In the future, this may include functions for querying the terminal +/// size, being notified of terminal size changes, querying supported +/// features, and so on. +@since(version = 0.2.0) +interface terminal-output { + /// The output side of a terminal. + @since(version = 0.2.0) + resource terminal-output; +} + +/// An interface providing an optional `terminal-input` for stdin as a +/// link-time authority. +@since(version = 0.2.0) +interface terminal-stdin { + @since(version = 0.2.0) + use terminal-input.{terminal-input}; + + /// If stdin is connected to a terminal, return a `terminal-input` handle + /// allowing further interaction with it. + @since(version = 0.2.0) + get-terminal-stdin: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stdout as a +/// link-time authority. +@since(version = 0.2.0) +interface terminal-stdout { + @since(version = 0.2.0) + use terminal-output.{terminal-output}; + + /// If stdout is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + @since(version = 0.2.0) + get-terminal-stdout: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stderr as a +/// link-time authority. +@since(version = 0.2.0) +interface terminal-stderr { + @since(version = 0.2.0) + use terminal-output.{terminal-output}; + + /// If stderr is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + @since(version = 0.2.0) + get-terminal-stderr: func() -> option; +} diff --git a/template-golem-agent-ts/wit/deps/clocks/monotonic-clock.wit b/template-golem-agent-ts/wit/deps/clocks/monotonic-clock.wit new file mode 100644 index 000000000..c676fb84d --- /dev/null +++ b/template-golem-agent-ts/wit/deps/clocks/monotonic-clock.wit @@ -0,0 +1,50 @@ +package wasi:clocks@0.2.3; +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +@since(version = 0.2.0) +interface monotonic-clock { + @since(version = 0.2.0) + use wasi:io/poll@0.2.3.{pollable}; + + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + @since(version = 0.2.0) + type instant = u64; + + /// A duration of time, in nanoseconds. + @since(version = 0.2.0) + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + @since(version = 0.2.0) + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + @since(version = 0.2.0) + resolution: func() -> duration; + + /// Create a `pollable` which will resolve once the specified instant + /// has occurred. + @since(version = 0.2.0) + subscribe-instant: func( + when: instant, + ) -> pollable; + + /// Create a `pollable` that will resolve after the specified duration has + /// elapsed from the time this function is invoked. + @since(version = 0.2.0) + subscribe-duration: func( + when: duration, + ) -> pollable; +} diff --git a/template-golem-agent-ts/wit/deps/clocks/timezone.wit b/template-golem-agent-ts/wit/deps/clocks/timezone.wit new file mode 100644 index 000000000..b43e93b23 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/clocks/timezone.wit @@ -0,0 +1,55 @@ +package wasi:clocks@0.2.3; + +@unstable(feature = clocks-timezone) +interface timezone { + @unstable(feature = clocks-timezone) + use wall-clock.{datetime}; + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + @unstable(feature = clocks-timezone) + display: func(when: datetime) -> timezone-display; + + /// The same as `display`, but only return the UTC offset. + @unstable(feature = clocks-timezone) + utc-offset: func(when: datetime) -> s32; + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + @unstable(feature = clocks-timezone) + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/template-golem-agent-ts/wit/deps/clocks/wall-clock.wit b/template-golem-agent-ts/wit/deps/clocks/wall-clock.wit new file mode 100644 index 000000000..e00ce0893 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/clocks/wall-clock.wit @@ -0,0 +1,46 @@ +package wasi:clocks@0.2.3; +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +@since(version = 0.2.0) +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + @since(version = 0.2.0) + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + @since(version = 0.2.0) + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + @since(version = 0.2.0) + resolution: func() -> datetime; +} diff --git a/template-golem-agent-ts/wit/deps/clocks/world.wit b/template-golem-agent-ts/wit/deps/clocks/world.wit new file mode 100644 index 000000000..05f04f797 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/clocks/world.wit @@ -0,0 +1,11 @@ +package wasi:clocks@0.2.3; + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import monotonic-clock; + @since(version = 0.2.0) + import wall-clock; + @unstable(feature = clocks-timezone) + import timezone; +} diff --git a/template-golem-agent-ts/wit/deps/config/store.wit b/template-golem-agent-ts/wit/deps/config/store.wit new file mode 100644 index 000000000..794379a75 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/config/store.wit @@ -0,0 +1,30 @@ +interface store { + /// An error type that encapsulates the different errors that can occur fetching configuration values. + variant error { + /// This indicates an error from an "upstream" config source. + /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), + /// the error message is a string. + upstream(string), + /// This indicates an error from an I/O operation. + /// As this could be almost _anything_ (such as a file read, network connection, etc), + /// the error message is a string. + /// Depending on how this ends up being consumed, + /// we may consider moving this to use the `wasi:io/error` type instead. + /// For simplicity right now in supporting multiple implementations, it is being left as a string. + io(string), + } + + /// Gets a configuration value of type `string` associated with the `key`. + /// + /// The value is returned as an `option`. If the key is not found, + /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. + get: func( + /// A string key to fetch + key: string + ) -> result, error>; + + /// Gets a list of configuration key-value pairs of type `string`. + /// + /// If an error occurs, an `Err(error)` is returned. + get-all: func() -> result>, error>; +} diff --git a/template-golem-agent-ts/wit/deps/config/world.wit b/template-golem-agent-ts/wit/deps/config/world.wit new file mode 100644 index 000000000..f92f080a5 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/config/world.wit @@ -0,0 +1,6 @@ +package wasi:config@0.2.0-draft; + +world imports { + /// The interface for wasi:config/store + import store; +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/filesystem/preopens.wit b/template-golem-agent-ts/wit/deps/filesystem/preopens.wit new file mode 100644 index 000000000..cea97495b --- /dev/null +++ b/template-golem-agent-ts/wit/deps/filesystem/preopens.wit @@ -0,0 +1,11 @@ +package wasi:filesystem@0.2.3; + +@since(version = 0.2.0) +interface preopens { + @since(version = 0.2.0) + use types.{descriptor}; + + /// Return the set of preopened directories, and their paths. + @since(version = 0.2.0) + get-directories: func() -> list>; +} diff --git a/template-golem-agent-ts/wit/deps/filesystem/types.wit b/template-golem-agent-ts/wit/deps/filesystem/types.wit new file mode 100644 index 000000000..d229a21f4 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/filesystem/types.wit @@ -0,0 +1,672 @@ +package wasi:filesystem@0.2.3; +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +@since(version = 0.2.0) +interface types { + @since(version = 0.2.0) + use wasi:io/streams@0.2.3.{input-stream, output-stream, error}; + @since(version = 0.2.0) + use wasi:clocks/wall-clock@0.2.3.{datetime}; + + /// File size or length of a region within a file. + @since(version = 0.2.0) + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + @since(version = 0.2.0) + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + @since(version = 0.2.0) + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrity + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + @since(version = 0.2.0) + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// Flags determining the method of how paths are resolved. + @since(version = 0.2.0) + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + @since(version = 0.2.0) + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + @since(version = 0.2.0) + type link-count = u64; + + /// When setting a timestamp, this gives the value to set it to. + @since(version = 0.2.0) + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + @since(version = 0.2.0) + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + @since(version = 0.2.0) + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + @since(version = 0.2.0) + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + @since(version = 0.2.0) + read-via-stream: func( + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result; + + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + @since(version = 0.2.0) + write-via-stream: func( + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result; + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in POSIX. + @since(version = 0.2.0) + append-via-stream: func() -> result; + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + @since(version = 0.2.0) + advise: func( + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code>; + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + @since(version = 0.2.0) + sync-data: func() -> result<_, error-code>; + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.2.0) + get-flags: func() -> result; + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.2.0) + get-type: func() -> result; + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + @since(version = 0.2.0) + set-size: func(size: filesize) -> result<_, error-code>; + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + @since(version = 0.2.0) + set-times: func( + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + @since(version = 0.2.0) + read: func( + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code>; + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + @since(version = 0.2.0) + write: func( + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result; + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + @since(version = 0.2.0) + read-directory: func() -> result; + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + @since(version = 0.2.0) + sync: func() -> result<_, error-code>; + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + @since(version = 0.2.0) + create-directory-at: func( + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code>; + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + @since(version = 0.2.0) + stat: func() -> result; + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + @since(version = 0.2.0) + stat-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + @since(version = 0.2.0) + set-times-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + @since(version = 0.2.0) + link-at: func( + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code>; + + /// Open a file or directory. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + @since(version = 0.2.0) + open-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + ) -> result; + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + @since(version = 0.2.0) + readlink-at: func( + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result; + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + @since(version = 0.2.0) + remove-directory-at: func( + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code>; + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + @since(version = 0.2.0) + rename-at: func( + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code>; + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + @since(version = 0.2.0) + symlink-at: func( + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code>; + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + @since(version = 0.2.0) + unlink-file-at: func( + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code>; + + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + @since(version = 0.2.0) + is-same-object: func(other: borrow) -> bool; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encouraged to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + @since(version = 0.2.0) + metadata-hash: func() -> result; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + @since(version = 0.2.0) + metadata-hash-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + } + + /// A stream of directory entries. + @since(version = 0.2.0) + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + @since(version = 0.2.0) + read-directory-entry: func() -> result, error-code>; + } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + @since(version = 0.2.0) + filesystem-error-code: func(err: borrow) -> option; +} diff --git a/template-golem-agent-ts/wit/deps/filesystem/world.wit b/template-golem-agent-ts/wit/deps/filesystem/world.wit new file mode 100644 index 000000000..29405bc2c --- /dev/null +++ b/template-golem-agent-ts/wit/deps/filesystem/world.wit @@ -0,0 +1,9 @@ +package wasi:filesystem@0.2.3; + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import types; + @since(version = 0.2.0) + import preopens; +} diff --git a/template-golem-agent-ts/wit/deps/golem-1.x/golem-context.wit b/template-golem-agent-ts/wit/deps/golem-1.x/golem-context.wit new file mode 100644 index 000000000..54f1484c3 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-1.x/golem-context.wit @@ -0,0 +1,93 @@ +package golem:api@1.1.7; + +/// Invocation context support +interface context { + use wasi:clocks/wall-clock@0.2.3.{datetime}; + + /// Starts a new `span` with the given name, as a child of the current invocation context + start-span: func(name: string) -> span; + + /// Gets the current invocation context + /// + /// The function call captures the current context; if new spans are started, the returned `invocation-context` instance will not + /// reflect that. + current-context: func() -> invocation-context; + + /// Allows or disallows forwarding of trace context headers in outgoing HTTP requests + /// + /// Returns the previous value of the setting + allow-forwarding-trace-context-headers: func(allow: bool) -> bool; + + /// Represents a unit of work or operation + resource span { + /// Gets the starting time of the span + started-at: func() -> datetime; + + /// Set an attribute on the span + set-attribute: func(name: string, value: attribute-value); + + /// Set multiple attributes on the span + set-attributes: func(attributes: list); + + /// Early finishes the span; otherwise it will be finished when the resource is dropped + finish: func(); + } + + /// Represents an invocation context wich allows querying the stack of attributes + /// created by automatic and user-defined spans. + resource invocation-context { + /// Gets the current trace id + trace-id: func() -> trace-id; + + /// Gets the current span id + span-id: func() -> span-id; + + /// Gets the parent context, if any; allows recursive processing of the invocation context. + /// + /// Alternatively, the attribute query methods can return inherited values without having to + /// traverse the stack manually. + parent: func() -> option; + + /// Gets the value of an attribute `key`. If `inherited` is true, the value is searched in the stack of spans, + /// otherwise only in the current span. + get-attribute: func(key: string, inherited: bool) -> option; + + /// Gets all attributes of the current invocation context. If `inherited` is true, it returns the merged set of attributes, each + /// key associated with the latest value found in the stack of spans. + get-attributes: func(inherited: bool) -> list; + + /// Gets the chain of attribute values associated with the given `key`. If the key does not exist in any of the + /// spans in the invocation context, the list is empty. The chain's first element contains the most recent (innermost) value. + get-attribute-chain: func(key: string) -> list; + + /// Gets all values of all attributes of the current invocation context. + get-attribute-chains: func() -> list; + + /// Gets the W3C Trace Context headers associated with the current invocation context + trace-context-headers: func() -> list>; + } + + /// An attribute of a span + record attribute { + key: string, + value: attribute-value + } + + /// A chain of attribute values, the first element representing the most recent value + record attribute-chain { + key: string, + values: list + } + + /// Possible span attribute value types + variant attribute-value { + /// A string value + %string(string) + } + + /// The trace represented by a 16 bytes hexadecimal string + type trace-id = string; + + /// The span represented by a 8 bytes hexadecimal string + type span-id = string; +} diff --git a/template-golem-agent-ts/wit/deps/golem-1.x/golem-host.wit b/template-golem-agent-ts/wit/deps/golem-1.x/golem-host.wit new file mode 100644 index 000000000..bb932f767 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-1.x/golem-host.wit @@ -0,0 +1,298 @@ +package golem:api@1.1.7; + +/// The Golem host API provides low level access to Golem specific features such as promises and control over +/// the durability and transactional guarantees the executor provides. +interface host { + use wasi:clocks/monotonic-clock@0.2.3.{duration}; + use golem:rpc/types@0.2.2.{component-id, uuid, worker-id}; + + /// An index into the persistent log storing all performed operations of a worker + type oplog-index = u64; + + /// A promise ID is a value that can be passed to an external Golem API to complete that promise + /// from an arbitrary external source, while Golem workers can await for this completion. + record promise-id { + worker-id: worker-id, + oplog-idx: oplog-index, + } + + /// Represents a Golem component's version + type component-version = u64; + + /// Represents a Golem Cloud account + record account-id { + value: string + } + + /// Represents a Golem project + record project-id { + uuid: uuid, + } + + /// Configures how the executor retries failures + record retry-policy { + /// The maximum number of retries before the worker becomes permanently failed + max-attempts: u32, + /// The minimum delay between retries (applied to the first retry) + min-delay: duration, + /// The maximum delay between retries + max-delay: duration, + /// Multiplier applied to the delay on each retry to implement exponential backoff + multiplier: f64, + /// The maximum amount of jitter to add to the delay + max-jitter-factor: option + } + + /// Configurable persistence level for workers + variant persistence-level { + persist-nothing, + persist-remote-side-effects, + smart + } + + /// Describes how to update a worker to a different component version + enum update-mode { + /// Automatic update tries to recover the worker using the new component version + /// and may fail if there is a divergence. + automatic, + + /// Manual, snapshot-based update uses a user-defined implementation of the `save-snapshot` interface + /// to store the worker's state, and a user-defined implementation of the `load-snapshot` interface to + /// load it into the new version. + snapshot-based + } + + enum filter-comparator { + equal, + not-equal, + greater-equal, + greater, + less-equal, + less + } + + enum string-filter-comparator { + equal, + not-equal, + like, + not-like + } + + enum worker-status { + /// The worker is running an invoked function + running, + /// The worker is ready to run an invoked function + idle, + /// An invocation is active but waiting for something (sleeping, waiting for a promise) + suspended, + /// The last invocation was interrupted but will be resumed + interrupted, + /// The last invocation failed and a retry was scheduled + retrying, + /// The last invocation failed and the worker can no longer be used + failed, + /// The worker exited after a successful invocation and can no longer be invoked + exited, + } + + record worker-name-filter { + comparator: string-filter-comparator, + value: string + } + + record worker-status-filter { + comparator: filter-comparator, + value: worker-status + } + + record worker-version-filter { + comparator: filter-comparator, + value: u64 + } + + record worker-created-at-filter { + comparator: filter-comparator, + value: u64 + } + + record worker-env-filter { + name: string, + comparator: string-filter-comparator, + value: string + } + + variant worker-property-filter { + name(worker-name-filter), + status(worker-status-filter), + version(worker-version-filter), + created-at(worker-created-at-filter), + env(worker-env-filter) + } + + record worker-all-filter { + filters: list + } + + record worker-any-filter { + filters: list + } + + record worker-metadata { + worker-id: worker-id, + args: list, + env: list>, + status: worker-status, + component-version: u64, + retry-count: u64 + } + + resource get-workers { + constructor(component-id: component-id, filter: option, precise: bool); + + get-next: func() -> option>; + } + + /// Target parameter for the `revert-worker` operation + variant revert-worker-target { + /// Revert to a specific oplog index. The given index will be the last one to be kept. + revert-to-oplog-index(oplog-index), + /// Revert the last N invocations. + revert-last-invocations(u64) + } + + /// Indicates which worker the code is running on after `fork` + enum fork-result { + /// The original worker that called `fork` + original, + /// The new worker + forked + } + + /// Create a new promise + create-promise: func() -> promise-id; + + /// Suspends execution until the given promise gets completed, and returns the payload passed to + /// the promise completion. + await-promise: func(promise-id: promise-id) -> list; + + /// Checks whether the given promise is completed. If not, it returns None. If the promise is completed, + /// it returns the payload passed to the promise completion. + poll-promise: func(promise-id: promise-id) -> option>; + + /// Completes the given promise with the given payload. Returns true if the promise was completed, false + /// if the promise was already completed. The payload is passed to the worker that is awaiting the promise. + complete-promise: func(promise-id: promise-id, data: list) -> bool; + + /// Deletes the given promise + delete-promise: func(promise-id: promise-id); + + /// Returns the current position in the persistent op log + get-oplog-index: func() -> oplog-index; + + /// Makes the current worker travel back in time and continue execution from the given position in the persistent + /// op log. + set-oplog-index: func(oplog-idx: oplog-index); + + /// Blocks the execution until the oplog has been written to at least the specified number of replicas, + /// or the maximum number of replicas if the requested number is higher. + oplog-commit: func(replicas: u8); + + /// Marks the beginning of an atomic operation. + /// In case of a failure within the region selected by `mark-begin-operation` and `mark-end-operation` + /// the whole region will be reexecuted on retry. + /// The end of the region is when `mark-end-operation` is called with the returned oplog-index. + mark-begin-operation: func() -> oplog-index; + + /// Commits this atomic operation. After `mark-end-operation` is called for a given index, further calls + /// with the same parameter will do nothing. + mark-end-operation: func(begin: oplog-index); + + /// Gets the current retry policy associated with the worker + get-retry-policy: func() -> retry-policy; + + /// Overrides the current retry policy associated with the worker. Following this call, `get-retry-policy` will return the + /// new retry policy. + set-retry-policy: func(new-retry-policy: retry-policy); + + /// Gets the worker's current persistence level. + get-oplog-persistence-level: func() -> persistence-level; + + /// Sets the worker's current persistence level. This can increase the performance of execution in cases where durable + /// execution is not required. + set-oplog-persistence-level: func(new-persistence-level: persistence-level); + + /// Gets the current idempotence mode. See `set-idempotence-mode` for details. + get-idempotence-mode: func() -> bool; + + /// Sets the current idempotence mode. The default is true. + /// True means side-effects are treated idempotent and Golem guarantees at-least-once semantics. + /// In case of false the executor provides at-most-once semantics, failing the worker in case it is + /// not known if the side effect was already executed. + set-idempotence-mode: func(idempotent: bool); + + /// Generates an idempotency key. This operation will never be replayed — + /// i.e. not only is this key generated, but it is persisted and committed, such that the key can be used in third-party systems (e.g. payment processing) + /// to introduce idempotence. + generate-idempotency-key: func() -> uuid; + + /// Initiates an update attempt for the given worker. The function returns immediately once the request has been processed, + /// not waiting for the worker to get updated. + update-worker: func(worker-id: worker-id, target-version: component-version, mode: update-mode); + + /// Get current worker metadata + get-self-metadata: func() -> worker-metadata; + + /// Get worker metadata + get-worker-metadata: func(worker-id: worker-id) -> option; + + /// Fork a worker to another worker at a given oplog index + fork-worker: func(source-worker-id: worker-id, target-worker-id: worker-id, oplog-idx-cut-off: oplog-index); + + /// Revert a worker to a previous state + revert-worker: func(worker-id: worker-id, revert-target: revert-worker-target); + + /// Get the component-id for a given component reference. + /// Returns none when no component with the specified reference exists. + /// The syntax of the component reference is implementation dependent. + /// + /// Golem OSS: "{component_name}" + /// Golem Cloud: + /// 1: "{component_name}" -> will resolve in current account and project + /// 2: "{project_name}/{component_name}" -> will resolve in current account + /// 3: "{account_id}/{project_name}/{component_name}" + resolve-component-id: func(component-reference: string) -> option; + + /// Get the worker-id for a given component and worker name. + /// Returns none when no component for the specified reference exists. + resolve-worker-id: func(component-reference: string, worker-name: string) -> option; + + /// Get the worker-id for a given component and worker name. + /// Returns none when no component for the specified component-reference or no worker with the specified worker-name exists. + resolve-worker-id-strict: func(component-reference: string, worker-name: string) -> option; + + /// Forks the current worker at the current execution point. The new worker gets the `new-name` worker name, + /// and this worker continues running as well. The return value is going to be different in this worker and + /// the forked worker. + fork: func(new-name: string) -> fork-result; +} + +/// Interface providing user-defined snapshotting capability. This can be used to perform manual update of workers +/// when the new component incompatible with the old one. +interface save-snapshot { + /// Saves the component's state into a user-defined snapshot + save: func() -> list; +} + +/// Interface providing user-defined snapshotting capability. This can be used to perform manual update of workers +/// when the new component incompatible with the old one. +interface load-snapshot { + /// Tries to load a user-defined snapshot, setting up the worker's state based on it. + /// The function can return with a failure to indicate that the update is not possible. + load: func(bytes: list) -> result<_, string>; +} + +world golem-host { + import host; + import save-snapshot; + import load-snapshot; +} diff --git a/template-golem-agent-ts/wit/deps/golem-1.x/golem-oplog-processor.wit b/template-golem-agent-ts/wit/deps/golem-1.x/golem-oplog-processor.wit new file mode 100644 index 000000000..2c910fe30 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-1.x/golem-oplog-processor.wit @@ -0,0 +1,33 @@ +package golem:api@1.1.7; + +interface oplog-processor { + use wasi:clocks/wall-clock@0.2.3.{datetime}; + use golem:rpc/types@0.2.2.{wit-value}; + + use host.{account-id, oplog-index, worker-metadata}; + use oplog.{oplog-entry}; + use golem:rpc/types@0.2.2.{component-id, worker-id}; + + record account-info { + account-id: account-id + } + + /// A processor resource is instantiated for each account having activated this oplog processor plugin. + /// There are no guarantees for the number of processors running at the same time, and different entries from the same worker + /// may be sent to different processor instances. + resource processor { + /// Initializes an oplog processor for a given component where the plugin was installed to. + /// The `account-info` parameters contains details of the account the installation belongs to. + /// The `component-id` parameter contains the identifier of the component the plugin was installed to. + /// The `config` parameter contains the configuration parameters for the plugin, as specified in the plugin installation + /// for the component. + constructor(account-info: account-info, component-id: component-id, config: list>); + + /// Called when one of the workers the plugin is activated on has written new entries to its oplog. + /// The `worker-id` parameter identifies the worker. + /// The `metadata` parameter contains the latest metadata of the worker. + /// The `first-entry-index` parameter contains the index of the first entry in the list of `entries`. + /// The `entries` parameteter always contains at least one element. + process: func(worker-id: worker-id, metadata: worker-metadata, first-entry-index: oplog-index, entries: list) -> result<_, string>; + } +} diff --git a/template-golem-agent-ts/wit/deps/golem-1.x/golem-oplog.wit b/template-golem-agent-ts/wit/deps/golem-1.x/golem-oplog.wit new file mode 100644 index 000000000..fd06f8f00 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-1.x/golem-oplog.wit @@ -0,0 +1,341 @@ +package golem:api@1.1.7; + +/// Host interface for enumerating and searching for worker oplogs +interface oplog { + use wasi:clocks/wall-clock@0.2.3.{datetime}; + use golem:rpc/types@0.2.2.{wit-value}; + + use host.{account-id, component-version, oplog-index, persistence-level, project-id, retry-policy, uuid, worker-id}; + use context.{attribute, attribute-value, span-id, trace-id}; + + variant wrapped-function-type { + /// The side-effect reads from the worker's local state (for example local file system, + /// random generator, etc.) + read-local, + /// The side-effect writes to the worker's local state (for example local file system) + write-local, + /// The side-effect reads from external state (for example a key-value store) + read-remote, + /// The side-effect manipulates external state (for example an RPC call) + write-remote, + /// The side-effect manipulates external state through multiple invoked functions (for example + /// a HTTP request where reading the response involves multiple host function calls) + /// + /// On the first invocation of the batch, the parameter should be `None` - this triggers + /// writing a `BeginRemoteWrite` entry in the oplog. Followup invocations should contain + /// this entry's index as the parameter. In batched remote writes it is the caller's responsibility + /// to manually write an `EndRemoteWrite` entry (using `end_function`) when the operation is completed. + write-remote-batched(option) + } + + record plugin-installation-description { + installation-id: uuid, + name: string, + version: string, + parameters: list> + } + + record create-parameters { + timestamp: datetime, + worker-id: worker-id, + component-version: component-version, + args: list, + env: list>, + created-by: account-id, + project-id: project-id, + parent: option, + component-size: u64, + initial-total-linear-memory-size: u64, + initial-active-plugins: list + } + + record imported-function-invoked-parameters { + timestamp: datetime, + function-name: string, + request: wit-value, + response: wit-value, + wrapped-function-type: wrapped-function-type, + } + + record local-span-data { + span-id: span-id, + start: datetime, + parent: option, + /// Optionally an index of the exported-function-invoked-parameters's invocation-context field + linked-context: option, + attributes: list, + inherited: bool + } + + record external-span-data { + span-id: span-id + } + + variant span-data { + local-span(local-span-data), + external-span(external-span-data) + } + + record exported-function-invoked-parameters { + timestamp: datetime, + function-name: string, + request: list, + idempotency-key: string, + trace-id: trace-id, + trace-states: list, + /// The first one is the invocation context stack associated with the exported function invocation, + /// and further stacks can be added that are referenced by the `linked-context` field of `local-span-data` + invocation-context: list> + } + + record exported-function-completed-parameters { + timestamp: datetime, + response: option, + consumed-fuel: s64 + } + + record error-parameters { + timestamp: datetime, + error: string + } + + record jump-parameters { + timestamp: datetime, + start: oplog-index, + end: oplog-index + } + + record change-retry-policy-parameters { + timestamp: datetime, + retry-policy: retry-policy + } + + record end-atomic-region-parameters { + timestamp: datetime, + begin-index: oplog-index + } + + record end-remote-write-parameters { + timestamp: datetime, + begin-index: oplog-index + } + + record exported-function-invocation-parameters { + idempotency-key: string, + function-name: string, + input: option> + } + + variant worker-invocation { + exported-function(exported-function-invocation-parameters), + manual-update(component-version) + } + + record pending-worker-invocation-parameters { + timestamp: datetime, + invocation: worker-invocation + } + + variant update-description { + /// Automatic update by replaying the oplog on the new version + auto-update, + /// Custom update by loading a given snapshot on the new version + snapshot-based(list) + } + + record pending-update-parameters { + timestamp: datetime, + target-version: component-version, + update-description: update-description + } + + record successful-update-parameters { + timestamp: datetime, + target-version: component-version, + new-component-size: u64, + new-active-plugins: list + } + + record failed-update-parameters { + timestamp: datetime, + target-version: component-version, + details: option + } + + record grow-memory-parameters { + timestamp: datetime, + delta: u64 + } + + type worker-resource-id = u64; + + record create-resource-parameters { + timestamp: datetime, + resource-id: worker-resource-id + } + + record drop-resource-parameters { + timestamp: datetime, + resource-id: worker-resource-id + } + + record describe-resource-parameters { + timestamp: datetime, + resource-id: worker-resource-id, + resource-name: string, + resource-params: list + } + + enum log-level { + stdout, + stderr, + trace, + debug, + info, + warn, + error, + critical + } + + record log-parameters { + timestamp: datetime, + level: log-level, + context: string, + message: string + } + + record activate-plugin-parameters { + timestamp: datetime, + plugin: plugin-installation-description + } + + record deactivate-plugin-parameters { + timestamp: datetime, + plugin: plugin-installation-description + } + + record revert-parameters { + timestamp: datetime, + start: oplog-index, + end: oplog-index + } + + record cancel-invocation-parameters { + timestamp: datetime, + idempotency-key: string + } + + record start-span-parameters { + timestamp: datetime, + span-id: span-id, + parent: option, + linked-context: option, + attributes: list, + } + + record finish-span-parameters { + timestamp: datetime, + span-id: span-id + } + + record set-span-attribute-parameters { + timestamp: datetime, + span-id: span-id, + key: string, + value: attribute-value + } + + record change-persistence-level-parameters { + timestamp: datetime, + persistence-level: persistence-level + } + + variant oplog-entry { + /// The initial worker oplog entry + create(create-parameters), + /// The worker invoked a host function + imported-function-invoked(imported-function-invoked-parameters), + /// The worker has been invoked + exported-function-invoked(exported-function-invoked-parameters), + /// The worker has completed an invocation + exported-function-completed(exported-function-completed-parameters), + /// Worker suspended + suspend(datetime), + /// Worker failed + error(error-parameters), + /// Marker entry added when get-oplog-index is called from the worker, to make the jumping behavior + /// more predictable. + no-op(datetime), + /// The worker needs to recover up to the given target oplog index and continue running from + /// the source oplog index from there + /// `jump` is an oplog region representing that from the end of that region we want to go back to the start and + /// ignore all recorded operations in between. + jump(jump-parameters), + /// Indicates that the worker has been interrupted at this point. + /// Only used to recompute the worker's (cached) status, has no effect on execution. + interrupted(datetime), + /// Indicates that the worker has been exited using WASI's exit function. + exited(datetime), + /// Overrides the worker's retry policy + change-retry-policy(change-retry-policy-parameters), + /// Begins an atomic region. All oplog entries after `BeginAtomicRegion` are to be ignored during + /// recovery except if there is a corresponding `EndAtomicRegion` entry. + begin-atomic-region(datetime), + /// Ends an atomic region. All oplog entries between the corresponding `BeginAtomicRegion` and this + /// entry are to be considered during recovery, and the begin/end markers can be removed during oplog + /// compaction. + end-atomic-region(end-atomic-region-parameters), + /// Begins a remote write operation. Only used when idempotence mode is off. In this case each + /// remote write must be surrounded by a `BeginRemoteWrite` and `EndRemoteWrite` log pair and + /// unfinished remote writes cannot be recovered. + begin-remote-write(datetime), + /// Marks the end of a remote write operation. Only used when idempotence mode is off. + end-remote-write(end-remote-write-parameters), + /// An invocation request arrived while the worker was busy + pending-worker-invocation(pending-worker-invocation-parameters), + /// An update request arrived and will be applied as soon the worker restarts + pending-update(pending-update-parameters), + /// An update was successfully applied + successful-update(successful-update-parameters), + /// An update failed to be applied + failed-update(failed-update-parameters), + /// Increased total linear memory size + grow-memory(grow-memory-parameters), + /// Created a resource instance + create-resource(create-resource-parameters), + /// Dropped a resource instance + drop-resource(drop-resource-parameters), + /// Adds additional information for a created resource instance + describe-resource(describe-resource-parameters), + /// The worker emitted a log message + log(log-parameters), + /// The worker's has been restarted, forgetting all its history + restart(datetime), + /// Activates a plugin + activate-plugin(activate-plugin-parameters), + /// Deactivates a plugin + deactivate-plugin(deactivate-plugin-parameters), + /// Revert a worker to a previous state + revert(revert-parameters), + /// Cancel a pending invocation + cancel-invocation(cancel-invocation-parameters), + /// Start a new span in the invocation context + start-span(start-span-parameters), + /// Finish an open span in the invocation context + finish-span(finish-span-parameters), + /// Set an attribute on an open span in the invocation context + set-span-attribute(set-span-attribute-parameters), + /// Change the current persistence level + change-persistence-level(change-persistence-level-parameters) + } + + resource get-oplog { + constructor(worker-id: worker-id, start: oplog-index); + get-next: func() -> option>; + } + + resource search-oplog { + constructor(worker-id: worker-id, text: string); + get-next: func() -> option>>; + } +} diff --git a/template-golem-agent-ts/wit/deps/golem-agent/common.wit b/template-golem-agent-ts/wit/deps/golem-agent/common.wit new file mode 100644 index 000000000..c725c768d --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-agent/common.wit @@ -0,0 +1,108 @@ +package golem:agent; + +interface common { + use golem:rpc/types@0.2.2.{wit-type, wit-value}; + + type url = string; + + record agent-type { + type-name: string, + description: string, + %constructor: agent-constructor, + methods: list, + dependencies: list, + } + + record agent-dependency { + type-name: string, + description: option, + %constructor: agent-constructor, + methods: list, + } + + record agent-method { + name: string, + description: string, + prompt-hint: option, + input-schema: data-schema, + output-schema: data-schema, + } + + record agent-constructor { + name: option, + description: string, + prompt-hint: option, + input-schema: data-schema, + } + + variant data-schema { + /// List of named elements + %tuple(list>), + /// List of named variants that can be used 0 or more times in a multimodal `data-value` + multimodal(list>), + } + + variant data-value { + /// List of element values, each corresponding to an element of the tuple `data-schema` + %tuple(list), + /// List of element values and their schema names; each name points to one named element of the corresponding + /// multimodal `data-schema`. + multimodal(list>), + } + + variant element-schema { + component-model(wit-type), + unstructured-text(text-descriptor), + unstructured-binary(binary-descriptor), + } + + variant element-value { + component-model(wit-value), + unstructured-text(text-reference), + unstructured-binary(binary-reference), + } + + record text-type { + language-code: string, + } + + variant text-reference { + url(string), + inline(text-source), + } + + record text-source { + data: string, + text-type: option, + } + + record text-descriptor { + restrictions: option>, + } + + record binary-descriptor { + restrictions: option>, + } + + record binary-type { + mime-type: string, + } + + variant binary-reference { + url(url), + inline(binary-source), + } + + record binary-source { + data: list, + binary-type: binary-type, + } + + variant agent-error { + invalid-input(string), + invalid-method(string), + invalid-type(string), + invalid-agent-id(string), + custom-error(data-value), + } +} diff --git a/template-golem-agent-ts/wit/deps/golem-agent/guest.wit b/template-golem-agent-ts/wit/deps/golem-agent/guest.wit new file mode 100644 index 000000000..e71b8c8e7 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-agent/guest.wit @@ -0,0 +1,27 @@ +package golem:agent; + +interface guest { + use common.{agent-error, agent-type, data-value}; + + resource agent { + create: static func(agent-type: string, input: data-value) -> result; + + get-id: func() -> string; + + invoke: func(method-name: string, input: data-value) -> result; + + get-definition: func() -> agent-type; + } + + get-agent: func(agent-type: string, agent-id: string) -> agent; + + discover-agents: func() -> list; + + discover-agent-types: func() -> list; +} + +world agent-guest { + import golem:api/host@1.1.7; + import golem:rpc/types@0.2.2; + export guest; +} diff --git a/template-golem-agent-ts/wit/deps/golem-durability/golem-durability-1.2.wit b/template-golem-agent-ts/wit/deps/golem-durability/golem-durability-1.2.wit new file mode 100644 index 000000000..b4f661d9f --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-durability/golem-durability-1.2.wit @@ -0,0 +1,98 @@ +package golem:durability@1.2.1; + +interface durability { + use golem:api/host@1.1.7.{persistence-level}; + use golem:api/oplog@1.1.7.{oplog-index, wrapped-function-type}; + use wasi:clocks/wall-clock@0.2.3.{datetime}; + use wasi:io/poll@0.2.3.{pollable}; + use golem:rpc/types@0.2.2.{value-and-type}; + + type durable-function-type = wrapped-function-type; + + record durable-execution-state { + is-live: bool, + persistence-level: persistence-level, + } + + enum oplog-entry-version { + v1, + v2 + } + + record persisted-durable-function-invocation { + timestamp: datetime, + function-name: string, + response: list, + function-type: durable-function-type, + entry-version: oplog-entry-version + } + + record persisted-typed-durable-function-invocation { + timestamp: datetime, + function-name: string, + response: value-and-type, + function-type: durable-function-type, + entry-version: oplog-entry-version + } + + /// Observes a function call (produces logs and metrics) + observe-function-call: func(iface: string, function: string); + + /// Marks the beginning of a durable function. + /// + /// There must be a corresponding call to `end-durable-function` after the function has + /// performed its work (it can be ended in a different context, for example after an async + /// pollable operation has been completed) + begin-durable-function: func(function-type: durable-function-type) -> oplog-index; + + /// Marks the end of a durable function + /// + /// This is a pair of `begin-durable-function` and should be called after the durable function + /// has performed and persisted or replayed its work. The `begin-index` should be the index + /// returned by `begin-durable-function`. + /// + /// Normally commit behavior is decided by the executor based on the `function-type`. However, in special + /// cases the `forced-commit` parameter can be used to force commit the oplog in an efficient way. + end-durable-function: func(function-type: durable-function-type, begin-index: oplog-index, forced-commit: bool); + + /// Gets the current durable execution state + current-durable-execution-state: func() -> durable-execution-state; + + /// Writes a record to the worker's oplog representing a durable function invocation + persist-durable-function-invocation: func( + function-name: string, + request: list, + response: list, + function-type: durable-function-type, + ); + + /// Writes a record to the worker's oplog representing a durable function invocation + /// + /// The request and response are defined as pairs of value and type, which makes it + /// self-describing for observers of oplogs. This is the recommended way to persist + /// third-party function invocations. + persist-typed-durable-function-invocation: func( + function-name: string, + request: value-and-type, + response: value-and-type, + function-type: durable-function-type, + ); + + /// Reads the next persisted durable function invocation from the oplog during replay + read-persisted-durable-function-invocation: func() -> persisted-durable-function-invocation; + + /// Reads the next persisted durable function invocation from the oplog during replay, assuming it + /// was created with `persist-typed-durable-function-invocation` + read-persisted-typed-durable-function-invocation: func() -> persisted-typed-durable-function-invocation; + + resource lazy-initialized-pollable { + /// Creates a `pollable` that is never ready until it gets attached to a real `pollable` implementation + /// using `set-lazy-initialized-pollable`. + constructor(); + + /// Sets the underlying `pollable` for a pollable created with `create-lazy-initialized-pollable`. + set: func(pollable: pollable); + + subscribe: func() -> pollable; + } +} diff --git a/template-golem-agent-ts/wit/deps/golem-rpc/wasm-rpc.wit b/template-golem-agent-ts/wit/deps/golem-rpc/wasm-rpc.wit new file mode 100644 index 000000000..c4ed84cc3 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/golem-rpc/wasm-rpc.wit @@ -0,0 +1,148 @@ +package golem:rpc@0.2.2; + +interface types { + use wasi:clocks/wall-clock@0.2.3.{datetime}; + use wasi:io/poll@0.2.3.{pollable}; + + /// Represents a Golem worker + record worker-id { + component-id: component-id, + worker-name: string + } + + /// Represents a Golem component + record component-id { + uuid: uuid, + } + + /// UUID + record uuid { + high-bits: u64, + low-bits: u64 + } + + /// Parses a UUID from a string + parse-uuid: func(uuid: string) -> result; + + /// Converts a UUID to a string + uuid-to-string: func(uuid: uuid) -> string; + + type node-index = s32; + + record wit-value { + nodes: list, + } + + variant wit-node { + record-value(list), + variant-value(tuple>), + enum-value(u32), + flags-value(list), + tuple-value(list), + list-value(list), + option-value(option), + result-value(result, option>), + prim-u8(u8), + prim-u16(u16), + prim-u32(u32), + prim-u64(u64), + prim-s8(s8), + prim-s16(s16), + prim-s32(s32), + prim-s64(s64), + prim-float32(f32), + prim-float64(f64), + prim-char(char), + prim-bool(bool), + prim-string(string), + handle(tuple) + } + + record wit-type { + nodes: list, + } + + type resource-id = u64; + + enum resource-mode { + owned, + borrowed + } + + record named-wit-type-node { + name: option, + %type: wit-type-node + } + + variant wit-type-node { + record-type(list>), + variant-type(list>>), + enum-type(list), + flags-type(list), + tuple-type(list), + list-type(node-index), + option-type(node-index), + result-type(tuple, option>), + prim-u8-type, + prim-u16-type, + prim-u32-type, + prim-u64-type, + prim-s8-type, + prim-s16-type, + prim-s32-type, + prim-s64-type, + prim-f32-type, + prim-f64-type, + prim-char-type, + prim-bool-type, + prim-string-type, + handle-type(tuple) + } + + record value-and-type { + value: wit-value, + typ: wit-type + } + + record uri { + value: string, + } + + variant rpc-error { + protocol-error(string), + denied(string), + not-found(string), + remote-internal-error(string) + } + + resource wasm-rpc { + constructor(worker-id: worker-id); + ephemeral: static func(component-id: component-id) -> wasm-rpc; + + invoke-and-await: func(function-name: string, function-params: list) -> result; + invoke: func(function-name: string, function-params: list) -> result<_, rpc-error>; + + async-invoke-and-await: func(function-name: string, function-params: list) -> future-invoke-result; + + /// Schedule invocation for later + schedule-invocation: func(scheduled-time: datetime, function-name: string, function-params: list); + /// Schedule invocation for later. Call cancel on the returned resource to cancel the invocation before the scheduled time. + schedule-cancelable-invocation: func(scheduled-time: datetime, function-name: string, function-params: list) -> cancellation-token; + } + + resource future-invoke-result { + subscribe: func() -> pollable; + get: func() -> option>; + } + + resource cancellation-token { + cancel: func(); + } + + extract-value: func(vnt: value-and-type) -> wit-value; + extract-type: func(vnt: value-and-type) -> wit-type; +} + +world wit-value { + import types; +} diff --git a/template-golem-agent-ts/wit/deps/http/handler.wit b/template-golem-agent-ts/wit/deps/http/handler.wit new file mode 100644 index 000000000..6a6c62966 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/http/handler.wit @@ -0,0 +1,49 @@ +/// This interface defines a handler of incoming HTTP Requests. It should +/// be exported by components which can respond to HTTP Requests. +@since(version = 0.2.0) +interface incoming-handler { + @since(version = 0.2.0) + use types.{incoming-request, response-outparam}; + + /// This function is invoked with an incoming HTTP Request, and a resource + /// `response-outparam` which provides the capability to reply with an HTTP + /// Response. The response is sent by calling the `response-outparam.set` + /// method, which allows execution to continue after the response has been + /// sent. This enables both streaming to the response body, and performing other + /// work. + /// + /// The implementor of this function must write a response to the + /// `response-outparam` before returning, or else the caller will respond + /// with an error on its behalf. + @since(version = 0.2.0) + handle: func( + request: incoming-request, + response-out: response-outparam + ); +} + +/// This interface defines a handler of outgoing HTTP Requests. It should be +/// imported by components which wish to make HTTP Requests. +@since(version = 0.2.0) +interface outgoing-handler { + @since(version = 0.2.0) + use types.{ + outgoing-request, request-options, future-incoming-response, error-code + }; + + /// This function is invoked with an outgoing HTTP Request, and it returns + /// a resource `future-incoming-response` which represents an HTTP Response + /// which may arrive in the future. + /// + /// The `options` argument accepts optional parameters for the HTTP + /// protocol's transport layer. + /// + /// This function may return an error if the `outgoing-request` is invalid + /// or not allowed to be made. Otherwise, protocol errors are reported + /// through the `future-incoming-response`. + @since(version = 0.2.0) + handle: func( + request: outgoing-request, + options: option + ) -> result; +} diff --git a/template-golem-agent-ts/wit/deps/http/proxy.wit b/template-golem-agent-ts/wit/deps/http/proxy.wit new file mode 100644 index 000000000..de3bbe8ae --- /dev/null +++ b/template-golem-agent-ts/wit/deps/http/proxy.wit @@ -0,0 +1,50 @@ +package wasi:http@0.2.3; + +/// The `wasi:http/imports` world imports all the APIs for HTTP proxies. +/// It is intended to be `include`d in other worlds. +@since(version = 0.2.0) +world imports { + /// HTTP proxies have access to time and randomness. + @since(version = 0.2.0) + import wasi:clocks/monotonic-clock@0.2.3; + @since(version = 0.2.0) + import wasi:clocks/wall-clock@0.2.3; + @since(version = 0.2.0) + import wasi:random/random@0.2.3; + + /// Proxies have standard output and error streams which are expected to + /// terminate in a developer-facing console provided by the host. + @since(version = 0.2.0) + import wasi:cli/stdout@0.2.3; + @since(version = 0.2.0) + import wasi:cli/stderr@0.2.3; + + /// TODO: this is a temporary workaround until component tooling is able to + /// gracefully handle the absence of stdin. Hosts must return an eof stream + /// for this import, which is what wasi-libc + tooling will do automatically + /// when this import is properly removed. + @since(version = 0.2.0) + import wasi:cli/stdin@0.2.3; + + /// This is the default handler to use when user code simply wants to make an + /// HTTP request (e.g., via `fetch()`). + @since(version = 0.2.0) + import outgoing-handler; +} + +/// The `wasi:http/proxy` world captures a widely-implementable intersection of +/// hosts that includes HTTP forward and reverse proxies. Components targeting +/// this world may concurrently stream in and out any number of incoming and +/// outgoing HTTP requests. +@since(version = 0.2.0) +world proxy { + @since(version = 0.2.0) + include imports; + + /// The host delivers incoming HTTP requests to a component by calling the + /// `handle` function of this exported interface. A host may arbitrarily reuse + /// or not reuse component instance when delivering incoming HTTP requests and + /// thus a component must be able to handle 0..N calls to `handle`. + @since(version = 0.2.0) + export incoming-handler; +} diff --git a/template-golem-agent-ts/wit/deps/http/types.wit b/template-golem-agent-ts/wit/deps/http/types.wit new file mode 100644 index 000000000..2498f180a --- /dev/null +++ b/template-golem-agent-ts/wit/deps/http/types.wit @@ -0,0 +1,673 @@ +/// This interface defines all of the types and methods for implementing +/// HTTP Requests and Responses, both incoming and outgoing, as well as +/// their headers, trailers, and bodies. +@since(version = 0.2.0) +interface types { + @since(version = 0.2.0) + use wasi:clocks/monotonic-clock@0.2.3.{duration}; + @since(version = 0.2.0) + use wasi:io/streams@0.2.3.{input-stream, output-stream}; + @since(version = 0.2.0) + use wasi:io/error@0.2.3.{error as io-error}; + @since(version = 0.2.0) + use wasi:io/poll@0.2.3.{pollable}; + + /// This type corresponds to HTTP standard Methods. + @since(version = 0.2.0) + variant method { + get, + head, + post, + put, + delete, + connect, + options, + trace, + patch, + other(string) + } + + /// This type corresponds to HTTP standard Related Schemes. + @since(version = 0.2.0) + variant scheme { + HTTP, + HTTPS, + other(string) + } + + /// These cases are inspired by the IANA HTTP Proxy Error Types: + /// + @since(version = 0.2.0) + variant error-code { + DNS-timeout, + DNS-error(DNS-error-payload), + destination-not-found, + destination-unavailable, + destination-IP-prohibited, + destination-IP-unroutable, + connection-refused, + connection-terminated, + connection-timeout, + connection-read-timeout, + connection-write-timeout, + connection-limit-reached, + TLS-protocol-error, + TLS-certificate-error, + TLS-alert-received(TLS-alert-received-payload), + HTTP-request-denied, + HTTP-request-length-required, + HTTP-request-body-size(option), + HTTP-request-method-invalid, + HTTP-request-URI-invalid, + HTTP-request-URI-too-long, + HTTP-request-header-section-size(option), + HTTP-request-header-size(option), + HTTP-request-trailer-section-size(option), + HTTP-request-trailer-size(field-size-payload), + HTTP-response-incomplete, + HTTP-response-header-section-size(option), + HTTP-response-header-size(field-size-payload), + HTTP-response-body-size(option), + HTTP-response-trailer-section-size(option), + HTTP-response-trailer-size(field-size-payload), + HTTP-response-transfer-coding(option), + HTTP-response-content-coding(option), + HTTP-response-timeout, + HTTP-upgrade-failed, + HTTP-protocol-error, + loop-detected, + configuration-error, + /// This is a catch-all error for anything that doesn't fit cleanly into a + /// more specific case. It also includes an optional string for an + /// unstructured description of the error. Users should not depend on the + /// string for diagnosing errors, as it's not required to be consistent + /// between implementations. + internal-error(option) + } + + /// Defines the case payload type for `DNS-error` above: + @since(version = 0.2.0) + record DNS-error-payload { + rcode: option, + info-code: option + } + + /// Defines the case payload type for `TLS-alert-received` above: + @since(version = 0.2.0) + record TLS-alert-received-payload { + alert-id: option, + alert-message: option + } + + /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: + @since(version = 0.2.0) + record field-size-payload { + field-name: option, + field-size: option + } + + /// Attempts to extract a http-related `error` from the wasi:io `error` + /// provided. + /// + /// Stream operations which return + /// `wasi:io/stream/stream-error::last-operation-failed` have a payload of + /// type `wasi:io/error/error` with more information about the operation + /// that failed. This payload can be passed through to this function to see + /// if there's http-related information about the error to return. + /// + /// Note that this function is fallible because not all io-errors are + /// http-related errors. + @since(version = 0.2.0) + http-error-code: func(err: borrow) -> option; + + /// This type enumerates the different kinds of errors that may occur when + /// setting or appending to a `fields` resource. + @since(version = 0.2.0) + variant header-error { + /// This error indicates that a `field-name` or `field-value` was + /// syntactically invalid when used with an operation that sets headers in a + /// `fields`. + invalid-syntax, + + /// This error indicates that a forbidden `field-name` was used when trying + /// to set a header in a `fields`. + forbidden, + + /// This error indicates that the operation on the `fields` was not + /// permitted because the fields are immutable. + immutable, + } + + /// Field names are always strings. + /// + /// Field names should always be treated as case insensitive by the `fields` + /// resource for the purposes of equality checking. + @since(version = 0.2.1) + type field-name = field-key; + + /// Field keys are always strings. + /// + /// Field keys should always be treated as case insensitive by the `fields` + /// resource for the purposes of equality checking. + /// + /// # Deprecation + /// + /// This type has been deprecated in favor of the `field-name` type. + @since(version = 0.2.0) + @deprecated(version = 0.2.2) + type field-key = string; + + /// Field values should always be ASCII strings. However, in + /// reality, HTTP implementations often have to interpret malformed values, + /// so they are provided as a list of bytes. + @since(version = 0.2.0) + type field-value = list; + + /// This following block defines the `fields` resource which corresponds to + /// HTTP standard Fields. Fields are a common representation used for both + /// Headers and Trailers. + /// + /// A `fields` may be mutable or immutable. A `fields` created using the + /// constructor, `from-list`, or `clone` will be mutable, but a `fields` + /// resource given by other means (including, but not limited to, + /// `incoming-request.headers`, `outgoing-request.headers`) might be be + /// immutable. In an immutable fields, the `set`, `append`, and `delete` + /// operations will fail with `header-error.immutable`. + @since(version = 0.2.0) + resource fields { + + /// Construct an empty HTTP Fields. + /// + /// The resulting `fields` is mutable. + @since(version = 0.2.0) + constructor(); + + /// Construct an HTTP Fields. + /// + /// The resulting `fields` is mutable. + /// + /// The list represents each name-value pair in the Fields. Names + /// which have multiple values are represented by multiple entries in this + /// list with the same name. + /// + /// The tuple is a pair of the field name, represented as a string, and + /// Value, represented as a list of bytes. + /// + /// An error result will be returned if any `field-name` or `field-value` is + /// syntactically invalid, or if a field is forbidden. + @since(version = 0.2.0) + from-list: static func( + entries: list> + ) -> result; + + /// Get all of the values corresponding to a name. If the name is not present + /// in this `fields` or is syntactically invalid, an empty list is returned. + /// However, if the name is present but empty, this is represented by a list + /// with one or more empty field-values present. + @since(version = 0.2.0) + get: func(name: field-name) -> list; + + /// Returns `true` when the name is present in this `fields`. If the name is + /// syntactically invalid, `false` is returned. + @since(version = 0.2.0) + has: func(name: field-name) -> bool; + + /// Set all of the values for a name. Clears any existing values for that + /// name, if they have been set. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + /// + /// Fails with `header-error.invalid-syntax` if the `field-name` or any of + /// the `field-value`s are syntactically invalid. + @since(version = 0.2.0) + set: func(name: field-name, value: list) -> result<_, header-error>; + + /// Delete all values for a name. Does nothing if no values for the name + /// exist. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + /// + /// Fails with `header-error.invalid-syntax` if the `field-name` is + /// syntactically invalid. + @since(version = 0.2.0) + delete: func(name: field-name) -> result<_, header-error>; + + /// Append a value for a name. Does not change or delete any existing + /// values for that name. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + /// + /// Fails with `header-error.invalid-syntax` if the `field-name` or + /// `field-value` are syntactically invalid. + @since(version = 0.2.0) + append: func(name: field-name, value: field-value) -> result<_, header-error>; + + /// Retrieve the full set of names and values in the Fields. Like the + /// constructor, the list represents each name-value pair. + /// + /// The outer list represents each name-value pair in the Fields. Names + /// which have multiple values are represented by multiple entries in this + /// list with the same name. + /// + /// The names and values are always returned in the original casing and in + /// the order in which they will be serialized for transport. + @since(version = 0.2.0) + entries: func() -> list>; + + /// Make a deep copy of the Fields. Equivalent in behavior to calling the + /// `fields` constructor on the return value of `entries`. The resulting + /// `fields` is mutable. + @since(version = 0.2.0) + clone: func() -> fields; + } + + /// Headers is an alias for Fields. + @since(version = 0.2.0) + type headers = fields; + + /// Trailers is an alias for Fields. + @since(version = 0.2.0) + type trailers = fields; + + /// Represents an incoming HTTP Request. + @since(version = 0.2.0) + resource incoming-request { + + /// Returns the method of the incoming request. + @since(version = 0.2.0) + method: func() -> method; + + /// Returns the path with query parameters from the request, as a string. + @since(version = 0.2.0) + path-with-query: func() -> option; + + /// Returns the protocol scheme from the request. + @since(version = 0.2.0) + scheme: func() -> option; + + /// Returns the authority of the Request's target URI, if present. + @since(version = 0.2.0) + authority: func() -> option; + + /// Get the `headers` associated with the request. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// The `headers` returned are a child resource: it must be dropped before + /// the parent `incoming-request` is dropped. Dropping this + /// `incoming-request` before all children are dropped will trap. + @since(version = 0.2.0) + headers: func() -> headers; + + /// Gives the `incoming-body` associated with this request. Will only + /// return success at most once, and subsequent calls will return error. + @since(version = 0.2.0) + consume: func() -> result; + } + + /// Represents an outgoing HTTP Request. + @since(version = 0.2.0) + resource outgoing-request { + + /// Construct a new `outgoing-request` with a default `method` of `GET`, and + /// `none` values for `path-with-query`, `scheme`, and `authority`. + /// + /// * `headers` is the HTTP Headers for the Request. + /// + /// It is possible to construct, or manipulate with the accessor functions + /// below, an `outgoing-request` with an invalid combination of `scheme` + /// and `authority`, or `headers` which are not permitted to be sent. + /// It is the obligation of the `outgoing-handler.handle` implementation + /// to reject invalid constructions of `outgoing-request`. + @since(version = 0.2.0) + constructor( + headers: headers + ); + + /// Returns the resource corresponding to the outgoing Body for this + /// Request. + /// + /// Returns success on the first call: the `outgoing-body` resource for + /// this `outgoing-request` can be retrieved at most once. Subsequent + /// calls will return error. + @since(version = 0.2.0) + body: func() -> result; + + /// Get the Method for the Request. + @since(version = 0.2.0) + method: func() -> method; + /// Set the Method for the Request. Fails if the string present in a + /// `method.other` argument is not a syntactically valid method. + @since(version = 0.2.0) + set-method: func(method: method) -> result; + + /// Get the combination of the HTTP Path and Query for the Request. + /// When `none`, this represents an empty Path and empty Query. + @since(version = 0.2.0) + path-with-query: func() -> option; + /// Set the combination of the HTTP Path and Query for the Request. + /// When `none`, this represents an empty Path and empty Query. Fails is the + /// string given is not a syntactically valid path and query uri component. + @since(version = 0.2.0) + set-path-with-query: func(path-with-query: option) -> result; + + /// Get the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. + @since(version = 0.2.0) + scheme: func() -> option; + /// Set the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. Fails if the + /// string given is not a syntactically valid uri scheme. + @since(version = 0.2.0) + set-scheme: func(scheme: option) -> result; + + /// Get the authority of the Request's target URI. A value of `none` may be used + /// with Related Schemes which do not require an authority. The HTTP and + /// HTTPS schemes always require an authority. + @since(version = 0.2.0) + authority: func() -> option; + /// Set the authority of the Request's target URI. A value of `none` may be used + /// with Related Schemes which do not require an authority. The HTTP and + /// HTTPS schemes always require an authority. Fails if the string given is + /// not a syntactically valid URI authority. + @since(version = 0.2.0) + set-authority: func(authority: option) -> result; + + /// Get the headers associated with the Request. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// This headers resource is a child: it must be dropped before the parent + /// `outgoing-request` is dropped, or its ownership is transferred to + /// another component by e.g. `outgoing-handler.handle`. + @since(version = 0.2.0) + headers: func() -> headers; + } + + /// Parameters for making an HTTP Request. Each of these parameters is + /// currently an optional timeout applicable to the transport layer of the + /// HTTP protocol. + /// + /// These timeouts are separate from any the user may use to bound a + /// blocking call to `wasi:io/poll.poll`. + @since(version = 0.2.0) + resource request-options { + /// Construct a default `request-options` value. + @since(version = 0.2.0) + constructor(); + + /// The timeout for the initial connect to the HTTP Server. + @since(version = 0.2.0) + connect-timeout: func() -> option; + + /// Set the timeout for the initial connect to the HTTP Server. An error + /// return value indicates that this timeout is not supported. + @since(version = 0.2.0) + set-connect-timeout: func(duration: option) -> result; + + /// The timeout for receiving the first byte of the Response body. + @since(version = 0.2.0) + first-byte-timeout: func() -> option; + + /// Set the timeout for receiving the first byte of the Response body. An + /// error return value indicates that this timeout is not supported. + @since(version = 0.2.0) + set-first-byte-timeout: func(duration: option) -> result; + + /// The timeout for receiving subsequent chunks of bytes in the Response + /// body stream. + @since(version = 0.2.0) + between-bytes-timeout: func() -> option; + + /// Set the timeout for receiving subsequent chunks of bytes in the Response + /// body stream. An error return value indicates that this timeout is not + /// supported. + @since(version = 0.2.0) + set-between-bytes-timeout: func(duration: option) -> result; + } + + /// Represents the ability to send an HTTP Response. + /// + /// This resource is used by the `wasi:http/incoming-handler` interface to + /// allow a Response to be sent corresponding to the Request provided as the + /// other argument to `incoming-handler.handle`. + @since(version = 0.2.0) + resource response-outparam { + + /// Set the value of the `response-outparam` to either send a response, + /// or indicate an error. + /// + /// This method consumes the `response-outparam` to ensure that it is + /// called at most once. If it is never called, the implementation + /// will respond with an error. + /// + /// The user may provide an `error` to `response` to allow the + /// implementation determine how to respond with an HTTP error response. + @since(version = 0.2.0) + set: static func( + param: response-outparam, + response: result, + ); + } + + /// This type corresponds to the HTTP standard Status Code. + @since(version = 0.2.0) + type status-code = u16; + + /// Represents an incoming HTTP Response. + @since(version = 0.2.0) + resource incoming-response { + + /// Returns the status code from the incoming response. + @since(version = 0.2.0) + status: func() -> status-code; + + /// Returns the headers from the incoming response. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// This headers resource is a child: it must be dropped before the parent + /// `incoming-response` is dropped. + @since(version = 0.2.0) + headers: func() -> headers; + + /// Returns the incoming body. May be called at most once. Returns error + /// if called additional times. + @since(version = 0.2.0) + consume: func() -> result; + } + + /// Represents an incoming HTTP Request or Response's Body. + /// + /// A body has both its contents - a stream of bytes - and a (possibly + /// empty) set of trailers, indicating that the full contents of the + /// body have been received. This resource represents the contents as + /// an `input-stream` and the delivery of trailers as a `future-trailers`, + /// and ensures that the user of this interface may only be consuming either + /// the body contents or waiting on trailers at any given time. + @since(version = 0.2.0) + resource incoming-body { + + /// Returns the contents of the body, as a stream of bytes. + /// + /// Returns success on first call: the stream representing the contents + /// can be retrieved at most once. Subsequent calls will return error. + /// + /// The returned `input-stream` resource is a child: it must be dropped + /// before the parent `incoming-body` is dropped, or consumed by + /// `incoming-body.finish`. + /// + /// This invariant ensures that the implementation can determine whether + /// the user is consuming the contents of the body, waiting on the + /// `future-trailers` to be ready, or neither. This allows for network + /// backpressure is to be applied when the user is consuming the body, + /// and for that backpressure to not inhibit delivery of the trailers if + /// the user does not read the entire body. + @since(version = 0.2.0) + %stream: func() -> result; + + /// Takes ownership of `incoming-body`, and returns a `future-trailers`. + /// This function will trap if the `input-stream` child is still alive. + @since(version = 0.2.0) + finish: static func(this: incoming-body) -> future-trailers; + } + + /// Represents a future which may eventually return trailers, or an error. + /// + /// In the case that the incoming HTTP Request or Response did not have any + /// trailers, this future will resolve to the empty set of trailers once the + /// complete Request or Response body has been received. + @since(version = 0.2.0) + resource future-trailers { + + /// Returns a pollable which becomes ready when either the trailers have + /// been received, or an error has occurred. When this pollable is ready, + /// the `get` method will return `some`. + @since(version = 0.2.0) + subscribe: func() -> pollable; + + /// Returns the contents of the trailers, or an error which occurred, + /// once the future is ready. + /// + /// The outer `option` represents future readiness. Users can wait on this + /// `option` to become `some` using the `subscribe` method. + /// + /// The outer `result` is used to retrieve the trailers or error at most + /// once. It will be success on the first call in which the outer option + /// is `some`, and error on subsequent calls. + /// + /// The inner `result` represents that either the HTTP Request or Response + /// body, as well as any trailers, were received successfully, or that an + /// error occurred receiving them. The optional `trailers` indicates whether + /// or not trailers were present in the body. + /// + /// When some `trailers` are returned by this method, the `trailers` + /// resource is immutable, and a child. Use of the `set`, `append`, or + /// `delete` methods will return an error, and the resource must be + /// dropped before the parent `future-trailers` is dropped. + @since(version = 0.2.0) + get: func() -> option, error-code>>>; + } + + /// Represents an outgoing HTTP Response. + @since(version = 0.2.0) + resource outgoing-response { + + /// Construct an `outgoing-response`, with a default `status-code` of `200`. + /// If a different `status-code` is needed, it must be set via the + /// `set-status-code` method. + /// + /// * `headers` is the HTTP Headers for the Response. + @since(version = 0.2.0) + constructor(headers: headers); + + /// Get the HTTP Status Code for the Response. + @since(version = 0.2.0) + status-code: func() -> status-code; + + /// Set the HTTP Status Code for the Response. Fails if the status-code + /// given is not a valid http status code. + @since(version = 0.2.0) + set-status-code: func(status-code: status-code) -> result; + + /// Get the headers associated with the Request. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// This headers resource is a child: it must be dropped before the parent + /// `outgoing-request` is dropped, or its ownership is transferred to + /// another component by e.g. `outgoing-handler.handle`. + @since(version = 0.2.0) + headers: func() -> headers; + + /// Returns the resource corresponding to the outgoing Body for this Response. + /// + /// Returns success on the first call: the `outgoing-body` resource for + /// this `outgoing-response` can be retrieved at most once. Subsequent + /// calls will return error. + @since(version = 0.2.0) + body: func() -> result; + } + + /// Represents an outgoing HTTP Request or Response's Body. + /// + /// A body has both its contents - a stream of bytes - and a (possibly + /// empty) set of trailers, inducating the full contents of the body + /// have been sent. This resource represents the contents as an + /// `output-stream` child resource, and the completion of the body (with + /// optional trailers) with a static function that consumes the + /// `outgoing-body` resource, and ensures that the user of this interface + /// may not write to the body contents after the body has been finished. + /// + /// If the user code drops this resource, as opposed to calling the static + /// method `finish`, the implementation should treat the body as incomplete, + /// and that an error has occurred. The implementation should propagate this + /// error to the HTTP protocol by whatever means it has available, + /// including: corrupting the body on the wire, aborting the associated + /// Request, or sending a late status code for the Response. + @since(version = 0.2.0) + resource outgoing-body { + + /// Returns a stream for writing the body contents. + /// + /// The returned `output-stream` is a child resource: it must be dropped + /// before the parent `outgoing-body` resource is dropped (or finished), + /// otherwise the `outgoing-body` drop or `finish` will trap. + /// + /// Returns success on the first call: the `output-stream` resource for + /// this `outgoing-body` may be retrieved at most once. Subsequent calls + /// will return error. + @since(version = 0.2.0) + write: func() -> result; + + /// Finalize an outgoing body, optionally providing trailers. This must be + /// called to signal that the response is complete. If the `outgoing-body` + /// is dropped without calling `outgoing-body.finalize`, the implementation + /// should treat the body as corrupted. + /// + /// Fails if the body's `outgoing-request` or `outgoing-response` was + /// constructed with a Content-Length header, and the contents written + /// to the body (via `write`) does not match the value given in the + /// Content-Length. + @since(version = 0.2.0) + finish: static func( + this: outgoing-body, + trailers: option + ) -> result<_, error-code>; + } + + /// Represents a future which may eventually return an incoming HTTP + /// Response, or an error. + /// + /// This resource is returned by the `wasi:http/outgoing-handler` interface to + /// provide the HTTP Response corresponding to the sent Request. + @since(version = 0.2.0) + resource future-incoming-response { + /// Returns a pollable which becomes ready when either the Response has + /// been received, or an error has occurred. When this pollable is ready, + /// the `get` method will return `some`. + @since(version = 0.2.0) + subscribe: func() -> pollable; + + /// Returns the incoming HTTP Response, or an error, once one is ready. + /// + /// The outer `option` represents future readiness. Users can wait on this + /// `option` to become `some` using the `subscribe` method. + /// + /// The outer `result` is used to retrieve the response or error at most + /// once. It will be success on the first call in which the outer option + /// is `some`, and error on subsequent calls. + /// + /// The inner `result` represents that either the incoming HTTP Response + /// status and headers have received successfully, or that an error + /// occurred. Errors may also occur while consuming the response body, + /// but those will be reported by the `incoming-body` and its + /// `output-stream` child. + @since(version = 0.2.0) + get: func() -> option>>; + } +} diff --git a/template-golem-agent-ts/wit/deps/io/error.wit b/template-golem-agent-ts/wit/deps/io/error.wit new file mode 100644 index 000000000..97c606877 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/io/error.wit @@ -0,0 +1,34 @@ +package wasi:io@0.2.3; + +@since(version = 0.2.0) +interface error { + /// A resource which represents some error information. + /// + /// The only method provided by this resource is `to-debug-string`, + /// which provides some human-readable information about the error. + /// + /// In the `wasi:io` package, this resource is returned through the + /// `wasi:io/streams/stream-error` type. + /// + /// To provide more specific error information, other interfaces may + /// offer functions to "downcast" this error into more specific types. For example, + /// errors returned from streams derived from filesystem types can be described using + /// the filesystem's own error-code type. This is done using the function + /// `wasi:filesystem/types/filesystem-error-code`, which takes a `borrow` + /// parameter and returns an `option`. + /// + /// The set of functions which can "downcast" an `error` into a more + /// concrete type is open. + @since(version = 0.2.0) + resource error { + /// Returns a string that is suitable to assist humans in debugging + /// this error. + /// + /// WARNING: The returned string should not be consumed mechanically! + /// It may change across platforms, hosts, or other implementation + /// details. Parsing this string is a major platform-compatibility + /// hazard. + @since(version = 0.2.0) + to-debug-string: func() -> string; + } +} diff --git a/template-golem-agent-ts/wit/deps/io/poll.wit b/template-golem-agent-ts/wit/deps/io/poll.wit new file mode 100644 index 000000000..9bcbe8e03 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/io/poll.wit @@ -0,0 +1,47 @@ +package wasi:io@0.2.3; + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +@since(version = 0.2.0) +interface poll { + /// `pollable` represents a single I/O event which may be ready, or not. + @since(version = 0.2.0) + resource pollable { + + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + @since(version = 0.2.0) + ready: func() -> bool; + + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + @since(version = 0.2.0) + block: func(); + } + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// This function traps if either: + /// - the list is empty, or: + /// - the list contains more elements than can be indexed with a `u32` value. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being ready for I/O. + @since(version = 0.2.0) + poll: func(in: list>) -> list; +} diff --git a/template-golem-agent-ts/wit/deps/io/streams.wit b/template-golem-agent-ts/wit/deps/io/streams.wit new file mode 100644 index 000000000..0de084629 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/io/streams.wit @@ -0,0 +1,290 @@ +package wasi:io@0.2.3; + +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +@since(version = 0.2.0) +interface streams { + @since(version = 0.2.0) + use error.{error}; + @since(version = 0.2.0) + use poll.{pollable}; + + /// An error for input-stream and output-stream operations. + @since(version = 0.2.0) + variant stream-error { + /// The last operation (a write or flush) failed before completion. + /// + /// More information is available in the `error` payload. + /// + /// After this, the stream will be closed. All future operations return + /// `stream-error::closed`. + last-operation-failed(error), + /// The stream is closed: no more input will be accepted by the + /// stream. A closed output-stream will return this error on all + /// future operations. + closed + } + + /// An input bytestream. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + @since(version = 0.2.0) + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// When the source of a `read` is binary data, the bytes from the source + /// are returned verbatim. When the source of a `read` is known to the + /// implementation to be text, bytes containing the UTF-8 encoding of the + /// text are returned. + /// + /// This function returns a list of bytes containing the read data, + /// when successful. The returned list will contain up to `len` bytes; + /// it may return fewer than requested, but not more. The list is + /// empty when no bytes are available for reading at this time. The + /// pollable given by `subscribe` will be ready when more bytes are + /// available. + /// + /// This function fails with a `stream-error` when the operation + /// encounters an error, giving `last-operation-failed`, or when the + /// stream is closed, giving `closed`. + /// + /// When the caller gives a `len` of 0, it represents a request to + /// read 0 bytes. If the stream is still open, this call should + /// succeed and return an empty list, or otherwise fail with `closed`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + @since(version = 0.2.0) + read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, behavior is identical to `read`. + @since(version = 0.2.0) + blocking-read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Skip bytes from a stream. Returns number of bytes skipped. + /// + /// Behaves identical to `read`, except instead of returning a list + /// of bytes, returns the number of bytes consumed from the stream. + @since(version = 0.2.0) + skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + @since(version = 0.2.0) + blocking-skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + @since(version = 0.2.0) + subscribe: func() -> pollable; + } + + + /// An output bytestream. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + /// + /// Dropping an `output-stream` while there's still an active write in + /// progress may result in the data being lost. Before dropping the stream, + /// be sure to fully flush your writes. + @since(version = 0.2.0) + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + @since(version = 0.2.0) + check-write: func() -> result; + + /// Perform a write. This function never blocks. + /// + /// When the destination of a `write` is binary data, the bytes from + /// `contents` are written verbatim. When the destination of a `write` is + /// known to the implementation to be text, the bytes of `contents` are + /// transcoded from UTF-8 into the encoding of the destination and then + /// written. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + @since(version = 0.2.0) + write: func( + contents: list + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + @since(version = 0.2.0) + blocking-write-and-flush: func( + contents: list + ) -> result<_, stream-error>; + + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + @since(version = 0.2.0) + flush: func() -> result<_, stream-error>; + + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + @since(version = 0.2.0) + blocking-flush: func() -> result<_, stream-error>; + + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occurred. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + @since(version = 0.2.0) + subscribe: func() -> pollable; + + /// Write zeroes to a stream. + /// + /// This should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + @since(version = 0.2.0) + write-zeroes: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + @since(version = 0.2.0) + blocking-write-zeroes-and-flush: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Read from one stream and write to another. + /// + /// The behavior of splice is equivalent to: + /// 1. calling `check-write` on the `output-stream` + /// 2. calling `read` on the `input-stream` with the smaller of the + /// `check-write` permitted length and the `len` provided to `splice` + /// 3. calling `write` on the `output-stream` with that read data. + /// + /// Any error reported by the call to `check-write`, `read`, or + /// `write` ends the splice and reports that error. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + @since(version = 0.2.0) + splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until the + /// `output-stream` is ready for writing, and the `input-stream` + /// is ready for reading, before performing the `splice`. + @since(version = 0.2.0) + blocking-splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + } +} diff --git a/template-golem-agent-ts/wit/deps/io/world.wit b/template-golem-agent-ts/wit/deps/io/world.wit new file mode 100644 index 000000000..f1d2102dc --- /dev/null +++ b/template-golem-agent-ts/wit/deps/io/world.wit @@ -0,0 +1,10 @@ +package wasi:io@0.2.3; + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import streams; + + @since(version = 0.2.0) + import poll; +} diff --git a/template-golem-agent-ts/wit/deps/keyvalue/atomic.wit b/template-golem-agent-ts/wit/deps/keyvalue/atomic.wit new file mode 100644 index 000000000..1d32b7e32 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/atomic.wit @@ -0,0 +1,31 @@ +/// A keyvalue interface that provides atomic operations. +/// +/// Atomic operations are single, indivisible operations. When a fault causes +/// an atomic operation to fail, it will appear to the invoker of the atomic +/// operation that the action either completed successfully or did nothing +/// at all. +interface atomic { + /// A keyvalue interface that provides atomic operations. + use types.{bucket, error, key}; + + /// Atomically increment the value associated with the key in the bucket by the + /// given delta. It returns the new value. + /// + /// If the key does not exist in the bucket, it creates a new key-value pair + /// with the value set to the given delta. + /// + /// If any other error occurs, it returns an `Err(error)`. + increment: func(bucket: borrow, key: key, delta: u64) -> result; + + /// Compare-and-swap (CAS) atomically updates the value associated with the key + /// in the bucket if the value matches the old value. This operation returns + /// `Ok(true)` if the swap was successful, `Ok(false)` if the value did not match, + /// + /// A successful CAS operation means the current value matched the `old` value + /// and was replaced with the `new` value. + /// + /// If the key does not exist in the bucket, it returns `Ok(false)`. + /// + /// If any other error occurs, it returns an `Err(error)`. + compare-and-swap: func(bucket: borrow, key: key, old: u64, new: u64) -> result; +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/keyvalue/caching.wit b/template-golem-agent-ts/wit/deps/keyvalue/caching.wit new file mode 100644 index 000000000..5880362a8 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/caching.wit @@ -0,0 +1,98 @@ +// The `wasi:keyvalue/cache` interface defines the operations of a single +// instance of a "cache", which is a non-durable, weakly-consistent key-value +// store. "Non-durable" means that caches are allowed and expected to +// arbitrarily discard key-value entries. "Weakly-consistent" means that there +// are essentially no guarantees that operations will agree on their results: a +// get following a set may not observe the set value; multiple gets may observe +// different previous set values; etc. The only guarantee is that values are +// not materialized "out of thin air": if a `get` returns a value, that value +// was passed to a `set` operation at some point in time in the past. +// Additionally, caches MUST make a best effort to respect the supplied +// Time-to-Live values (within the usual limitations around time in a +// distributed setting). +interface cache { + use wasi:io/poll@0.2.3.{pollable}; + use types.{key, incoming-value, outgoing-value, error}; + + // The `get` operation returns the value passed by a previous `set` for the + // same key within the given TTL or none if there is no such value. + get: func(k: key) -> future-get-result; + + // This block defines a special resource type used by `get` to emulate + // `future,error>>`. In the return value + // of the `get` method, the outer `option` returns `none` when the pollable + // is not yet ready and the inner `option` returns `none` when the + // requested key wasn't present. + resource future-get-result { + future-get-result-get: func() -> option, error>>; + listen-to-future-get-result: func() -> pollable; + } + + // The `exists` operation returns whether a value was previously `set` for + // the given key within the TTL. + exists: func(k: key) -> future-exists-result; + + // This block defines a special resource type used by `exists` to emulate + // `future>`. + resource future-exists-result { + future-exists-result-get: func() -> option>; + listen-to-future-exists-result: func() -> pollable; + } + + // The `set` operation sets the given value for the given key for the given + // time-to-live (TTL) duration, if supplied, specified in milliseconds. If + // a TTL is not supplied, the key may be kept indefinitely (as-if a very + // large TTL were used). If the key is already present in the cache, the + // value is updated in-place. In the common case of computing and caching a + // value if the given key is not already in the cache, consider using + // `get-or-set` (below) intead of separate `get` and `set` operations. + set: func(k: key, v: borrow, TTL-ms: option) -> future-result; + + // This block defines a special resource type used by `set` and `delete` to + // emulate `future>`. + resource future-result { + future-result-get: func() -> option>; + listen-to-future-result: func() -> pollable; + } + + // The `get-or-set` operation asynchronously returns one of two cases + // enumerated by `get-or-set-entry`: in the `occupied` case, the given key + // already has a value present in the cache; in the `vacant` case, there + // was no value and the caller should write a value into the returned + // `vacancy`. This operation allows multiple concurrent `get-or-set` + // invocations to rendezvous such that only one invocation receives the + // `vacant` result while all other invocations wait until the vacancy is + // filled before receiving an `occupied` result. Implementations are not + // required to implement this rendezvous or to rendezvous in all possible + // cases. + variant get-or-set-entry { + occupied(incoming-value), + vacant(vacancy) + } + get-or-set: func(k: key) -> future-get-or-set-result; + + // This block defines a special resource type used by `get-or-set` to + // emulate `future>`. + resource future-get-or-set-result { + future-get-or-set-result-get: func() -> option>; + listen-to-future-get-or-set-result: func() -> pollable; + } + + // The following block defines the `vacancy` resource type. (When resource + // types are added, the `u32` type aliases can be replaced by proper + // `resource` types.) When the caller of `get-or-set` receives a `vacancy`, + // they must either call the `fill` method or drop the `vacancy` to + // indicate an error that prevents calling `fill`. An implementation MAY + // have a timeout that drops a vacancy that hasn't been filled in order + // to unblock other waiting `get-or-set` callers. + resource vacancy { + vacancy-fill: func(TTL-ms: option) -> outgoing-value; + } + + // The `delete` operation removes any value with the given key from the + // cache. Like all cache operations, `delete` is weakly ordered and thus + // concurrent `get` calls may still see deleted keys for a period of time. + // Additionally, due to weak ordering, concurrent `set` calls for the same + // key may or may not get deleted. + delete: func(k: key) -> future-result; +} diff --git a/template-golem-agent-ts/wit/deps/keyvalue/error.wit b/template-golem-agent-ts/wit/deps/keyvalue/error.wit new file mode 100644 index 000000000..cd244f6ff --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/error.wit @@ -0,0 +1,20 @@ +interface wasi-keyvalue-error { + /// An error resource type for keyvalue operations. + /// + /// Common errors: + /// - Connectivity errors (e.g. network errors): when the client cannot establish + /// a connection to the keyvalue service. + /// - Authentication and Authorization errors: when the client fails to authenticate + /// or does not have the required permissions to perform the operation. + /// - Data errors: when the client sends incompatible or corrupted data. + /// - Resource errors: when the system runs out of resources (e.g. memory). + /// - Internal errors: unexpected errors on the server side. + /// + /// Currently, this provides only one function to return a string representation + /// of the error. In the future, this will be extended to provide more information + /// about the error. + // Soon: switch to `resource error { ... }` + resource error { + trace: func() -> string; + } +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/keyvalue/eventual-batch.wit b/template-golem-agent-ts/wit/deps/keyvalue/eventual-batch.wit new file mode 100644 index 000000000..080999eea --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/eventual-batch.wit @@ -0,0 +1,81 @@ +/// A keyvalue interface that provides eventually consistent batch operations. +/// +/// A batch operation is an operation that operates on multiple keys at once. +/// +/// Batch operations are useful for reducing network round-trip time. For example, +/// if you want to get the values associated with 100 keys, you can either do 100 get +/// operations or you can do 1 batch get operation. The batch operation is +/// faster because it only needs to make 1 network call instead of 100. +/// +/// A batch operation does not guarantee atomicity, meaning that if the batch +/// operation fails, some of the keys may have been modified and some may not. +/// Transactional operations are being worked on and will be added in the future to +/// provide atomicity. +/// +/// Data consistency in a key value store refers to the gaurantee that once a +/// write operation completes, all subsequent read operations will return the +/// value that was written. +/// +/// The level of consistency in batch operations is **eventual consistency**, the same +/// with the readwrite interface. This interface does not guarantee strong consistency, +/// meaning that if a write operation completes, subsequent read operations may not return +/// the value that was written. +interface eventual-batch { + /// A keyvalue interface that provides batch get operations. + use types.{bucket, error, key, incoming-value, outgoing-value}; + + /// Get the values associated with the keys in the bucket. It returns a list of + /// incoming-value that can be consumed to get the value associated with the key. + /// + /// If any of the keys do not exist in the bucket, it returns a `none` value for + /// that key in the list. + /// + /// Note that the key-value pairs are guaranteed to be returned in the same order + /// + /// MAY show an out-of-date value if there are concurrent writes to the bucket. + /// + /// If any other error occurs, it returns an `Err(error)`. + get-many: func(bucket: borrow, keys: list) -> result>, error>; + + /// Get all the keys in the bucket. It returns a list of keys. + /// + /// Note that the keys are not guaranteed to be returned in any particular order. + /// + /// If the bucket is empty, it returns an empty list. + /// + /// MAY show an out-of-date list of keys if there are concurrent writes to the bucket. + /// + /// If any error occurs, it returns an `Err(error)`. + keys: func(bucket: borrow) -> result, error>; + + /// Set the values associated with the keys in the bucket. If the key already + /// exists in the bucket, it overwrites the value. + /// + /// Note that the key-value pairs are not guaranteed to be set in the order + /// they are provided. + /// + /// If any of the keys do not exist in the bucket, it creates a new key-value pair. + /// + /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it + /// does not rollback the key-value pairs that were already set. Thus, this batch operation + /// does not guarantee atomicity, implying that some key-value pairs could be + /// set while others might fail. + /// + /// Other concurrent operations may also be able to see the partial results. + set-many: func(bucket: borrow, key-values: list>>) -> result<_, error>; + + /// Delete the key-value pairs associated with the keys in the bucket. + /// + /// Note that the key-value pairs are not guaranteed to be deleted in the order + /// they are provided. + /// + /// If any of the keys do not exist in the bucket, it skips the key. + /// + /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it + /// does not rollback the key-value pairs that were already deleted. Thus, this batch operation + /// does not guarantee atomicity, implying that some key-value pairs could be + /// deleted while others might fail. + /// + /// Other concurrent operations may also be able to see the partial results. + delete-many: func(bucket: borrow, keys: list) -> result<_, error>; +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/keyvalue/eventual.wit b/template-golem-agent-ts/wit/deps/keyvalue/eventual.wit new file mode 100644 index 000000000..e6e33cfe7 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/eventual.wit @@ -0,0 +1,56 @@ +/// A keyvalue interface that provides eventually consistent CRUD operations. +/// +/// A CRUD operation is an operation that acts on a single key-value pair. +/// +/// The value in the key-value pair is defined as a `u8` byte array and the intention +/// is that it is the common denominator for all data types defined by different +/// key-value stores to handle data, ensuring compatibility between different +/// key-value stores. Note: the clients will be expecting serialization/deserialization overhead +/// to be handled by the key-value store. The value could be a serialized object from +/// JSON, HTML or vendor-specific data types like AWS S3 objects. +/// +/// Data consistency in a key value store refers to the gaurantee that once a +/// write operation completes, all subsequent read operations will return the +/// value that was written. +/// +/// The level of consistency in readwrite interfaces is **eventual consistency**, +/// which means that if a write operation completes successfully, all subsequent +/// read operations will eventually return the value that was written. In other words, +/// if we pause the updates to the system, the system eventually will return +/// the last updated value for read. +interface eventual { + /// A keyvalue interface that provides simple read and write operations. + use types.{bucket, error, incoming-value, key, outgoing-value}; + + /// Get the value associated with the key in the bucket. + /// + /// The value is returned as an option. If the key-value pair exists in the + /// bucket, it returns `Ok(value)`. If the key does not exist in the + /// bucket, it returns `Ok(none)`. + /// + /// If any other error occurs, it returns an `Err(error)`. + get: func(bucket: borrow, key: key) -> result, error>; + + /// Set the value associated with the key in the bucket. If the key already + /// exists in the bucket, it overwrites the value. + /// + /// If the key does not exist in the bucket, it creates a new key-value pair. + /// + /// If any other error occurs, it returns an `Err(error)`. + set: func(bucket: borrow, key: key, outgoing-value: borrow) -> result<_, error>; + + /// Delete the key-value pair associated with the key in the bucket. + /// + /// If the key does not exist in the bucket, it does nothing. + /// + /// If any other error occurs, it returns an `Err(error)`. + delete: func(bucket: borrow, key: key) -> result<_, error>; + + /// Check if the key exists in the bucket. + /// + /// If the key exists in the bucket, it returns `Ok(true)`. If the key does + /// not exist in the bucket, it returns `Ok(false)`. + /// + /// If any other error occurs, it returns an `Err(error)`. + exists: func(bucket: borrow, key: key) -> result; +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/keyvalue/handle-watch.wit b/template-golem-agent-ts/wit/deps/keyvalue/handle-watch.wit new file mode 100644 index 000000000..0ca7b3772 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/handle-watch.wit @@ -0,0 +1,17 @@ +/// A keyvalue interface that provides handle-watch operations. +/// +/// This interface is used to provide event-driven mechanisms to handle +/// keyvalue changes. +interface handle-watch { + /// A keyvalue interface that provides handle-watch operations. + use types.{bucket, key, incoming-value}; + + /// Handle the `set` event for the given bucket and key. + /// It returns a `incoming-value` that represents the new value being set. + /// The new value can be consumed by the handler. + on-set: func(bucket: bucket, key: key, incoming-value: borrow); + + /// Handle the `delete` event for the given bucket and key. + /// It returns a `key` that represents the key being deleted. + on-delete: func(bucket: bucket, key: key); +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/keyvalue/types.wit b/template-golem-agent-ts/wit/deps/keyvalue/types.wit new file mode 100644 index 000000000..81ee803c0 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/types.wit @@ -0,0 +1,72 @@ +// A generic keyvalue interface for WASI. +interface types { + /// A bucket is a collection of key-value pairs. Each key-value pair is stored + /// as a entry in the bucket, and the bucket itself acts as a collection of all + /// these entries. + /// + /// It is worth noting that the exact terminology for bucket in key-value stores + /// can very depending on the specific implementation. For example, + /// 1. Amazon DynamoDB calls a collection of key-value pairs a table + /// 2. Redis has hashes, sets, and sorted sets as different types of collections + /// 3. Cassandra calls a collection of key-value pairs a column family + /// 4. MongoDB calls a collection of key-value pairs a collection + /// 5. Riak calls a collection of key-value pairs a bucket + /// 6. Memcached calls a collection of key-value pairs a slab + /// 7. Azure Cosmos DB calls a collection of key-value pairs a container + /// + /// In this interface, we use the term `bucket` to refer to a collection of key-value + // Soon: switch to `resource bucket { ... }` + resource bucket { + /// Opens a bucket with the given name. + /// + /// If any error occurs, including if the bucket does not exist, it returns an `Err(error)`. + open-bucket: static func(name: string) -> result; + } + /// A key is a unique identifier for a value in a bucket. The key is used to + /// retrieve the value from the bucket. + type key = string; + + use wasi:io/streams@0.2.3.{input-stream, output-stream}; + use wasi-keyvalue-error.{ error }; + /// A value is the data stored in a key-value pair. The value can be of any type + /// that can be represented in a byte array. It provides a way to write the value + /// to the output-stream defined in the `wasi-io` interface. + // Soon: switch to `resource value { ... }` + resource outgoing-value { + new-outgoing-value: static func() -> outgoing-value; + /// Writes the value to the output-stream asynchronously. + /// If any other error occurs, it returns an `Err(error)`. + outgoing-value-write-body-async: func() -> result; + /// Writes the value to the output-stream synchronously. + /// If any other error occurs, it returns an `Err(error)`. + outgoing-value-write-body-sync: func(value: outgoing-value-body-sync) -> result<_, error>; + } + type outgoing-value-body-async = output-stream; + type outgoing-value-body-sync = list; + + /// A incoming-value is a wrapper around a value. It provides a way to read the value + /// from the `input-stream` defined in the `wasi-io` interface. + /// + /// The incoming-value provides two ways to consume the value: + /// 1. `incoming-value-consume-sync` consumes the value synchronously and returns the + /// value as a `list`. + /// 2. `incoming-value-consume-async` consumes the value asynchronously and returns the + /// value as an `input-stream`. + /// In addition, it provides a `incoming-value-size` function to get the size of the value. + /// This is useful when the value is large and the caller wants to allocate a buffer of + /// the right size to consume the value. + // Soon: switch to `resource incoming-value { ... }` + resource incoming-value { + /// Consumes the value synchronously and returns the value as a list of bytes. + /// If any other error occurs, it returns an `Err(error)`. + incoming-value-consume-sync: func() -> result; + /// Consumes the value asynchronously and returns the value as an `input-stream`. + /// If any other error occurs, it returns an `Err(error)`. + incoming-value-consume-async: func() -> result; + /// The size of the value in bytes. + /// If the size is unknown or unavailable, this function returns an `Err(error)`. + incoming-value-size: func() -> result; + } + type incoming-value-async-body = input-stream; + type incoming-value-sync-body = list; +} diff --git a/template-golem-agent-ts/wit/deps/keyvalue/world.wit b/template-golem-agent-ts/wit/deps/keyvalue/world.wit new file mode 100644 index 000000000..ea64fe5ee --- /dev/null +++ b/template-golem-agent-ts/wit/deps/keyvalue/world.wit @@ -0,0 +1,26 @@ +package wasi:keyvalue@0.1.0; + +/// The `wasi:keyvalue/imports` world provides common APIs for interacting +/// with key-value stores. Components targeting this world will be able to +/// do +/// 1. CRUD (create, read, update, delete) operations on key-value stores. +/// 2. Atomic `increment` and CAS (compare-and-swap) operations. +/// 3. Batch operations that can reduce the number of round trips to the network. +world imports { + /// The `eventual` capability allows the component to perform + /// eventually consistent CRUD operations on the key-value store. + import eventual; + + /// The `atomic` capability allows the component to perform atomic + /// `increment` and CAS (compare-and-swap) operations. + import atomic; + + /// The `eventual-batch` capability allows the component to perform eventually + /// consistent batch operations that can reduce the number of round trips to the network. + import eventual-batch; +} + +world keyvalue-handle-watch { + include imports; + export handle-watch; +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/logging/logging.wit b/template-golem-agent-ts/wit/deps/logging/logging.wit new file mode 100644 index 000000000..adb5ee5fb --- /dev/null +++ b/template-golem-agent-ts/wit/deps/logging/logging.wit @@ -0,0 +1,37 @@ +package wasi:logging; + +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +interface logging { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + + /// Describes messages indicating hazardous situations. + warn, + + /// Describes messages indicating serious errors. + error, + + /// Describes messages indicating fatal errors. + critical, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string); +} \ No newline at end of file diff --git a/template-golem-agent-ts/wit/deps/random/insecure-seed.wit b/template-golem-agent-ts/wit/deps/random/insecure-seed.wit new file mode 100644 index 000000000..67d024d5b --- /dev/null +++ b/template-golem-agent-ts/wit/deps/random/insecure-seed.wit @@ -0,0 +1,27 @@ +package wasi:random@0.2.3; +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.2.0) +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + @since(version = 0.2.0) + insecure-seed: func() -> tuple; +} diff --git a/template-golem-agent-ts/wit/deps/random/insecure.wit b/template-golem-agent-ts/wit/deps/random/insecure.wit new file mode 100644 index 000000000..a07dfab32 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/random/insecure.wit @@ -0,0 +1,25 @@ +package wasi:random@0.2.3; +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.2.0) +interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + @since(version = 0.2.0) + get-insecure-random-bytes: func(len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + @since(version = 0.2.0) + get-insecure-random-u64: func() -> u64; +} diff --git a/template-golem-agent-ts/wit/deps/random/random.wit b/template-golem-agent-ts/wit/deps/random/random.wit new file mode 100644 index 000000000..91957e633 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/random/random.wit @@ -0,0 +1,29 @@ +package wasi:random@0.2.3; +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.2.0) +interface random { + /// Return `len` cryptographically-secure random or pseudo-random bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + @since(version = 0.2.0) + get-random-bytes: func(len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + @since(version = 0.2.0) + get-random-u64: func() -> u64; +} diff --git a/template-golem-agent-ts/wit/deps/random/world.wit b/template-golem-agent-ts/wit/deps/random/world.wit new file mode 100644 index 000000000..0c1218f36 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/random/world.wit @@ -0,0 +1,13 @@ +package wasi:random@0.2.3; + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import random; + + @since(version = 0.2.0) + import insecure; + + @since(version = 0.2.0) + import insecure-seed; +} diff --git a/template-golem-agent-ts/wit/deps/sockets/instance-network.wit b/template-golem-agent-ts/wit/deps/sockets/instance-network.wit new file mode 100644 index 000000000..5f6e6c1cc --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/instance-network.wit @@ -0,0 +1,11 @@ + +/// This interface provides a value-export of the default network handle.. +@since(version = 0.2.0) +interface instance-network { + @since(version = 0.2.0) + use network.{network}; + + /// Get a handle to the default network. + @since(version = 0.2.0) + instance-network: func() -> network; +} diff --git a/template-golem-agent-ts/wit/deps/sockets/ip-name-lookup.wit b/template-golem-agent-ts/wit/deps/sockets/ip-name-lookup.wit new file mode 100644 index 000000000..c1d8a47c1 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/ip-name-lookup.wit @@ -0,0 +1,56 @@ +@since(version = 0.2.0) +interface ip-name-lookup { + @since(version = 0.2.0) + use wasi:io/poll@0.2.3.{pollable}; + @since(version = 0.2.0) + use network.{network, error-code, ip-address}; + + /// Resolve an internet host name to a list of IP addresses. + /// + /// Unicode domain names are automatically converted to ASCII using IDNA encoding. + /// If the input is an IP address string, the address is parsed and returned + /// as-is without making any external requests. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// This function never blocks. It either immediately fails or immediately + /// returns successfully with a `resolve-address-stream` that can be used + /// to (asynchronously) fetch the results. + /// + /// # Typical errors + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// + /// # References: + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + resolve-addresses: func(network: borrow, name: string) -> result; + + @since(version = 0.2.0) + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + @since(version = 0.2.0) + resolve-next-address: func() -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI 0.2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) + subscribe: func() -> pollable; + } +} diff --git a/template-golem-agent-ts/wit/deps/sockets/network.wit b/template-golem-agent-ts/wit/deps/sockets/network.wit new file mode 100644 index 000000000..f3f60a370 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/network.wit @@ -0,0 +1,169 @@ +@since(version = 0.2.0) +interface network { + @unstable(feature = network-error-code) + use wasi:io/error@0.2.3.{error}; + + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + @since(version = 0.2.0) + resource network; + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// - `concurrency-conflict` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + @since(version = 0.2.0) + enum error-code { + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + + /// The operation timed out before it could finish completely. + timeout, + + /// This operation is incompatible with another asynchronous operation that is already in progress. + /// + /// POSIX equivalent: EALREADY + concurrency-conflict, + + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + + + /// The operation is not valid in the socket's current state. + invalid-state, + + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + + /// A bind operation failed because the provided address is not an address that the `network` can bind to. + address-not-bindable, + + /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. + address-in-use, + + /// The remote address is not reachable + remote-unreachable, + + + /// The TCP connection was forcefully rejected + connection-refused, + + /// The TCP connection was reset. + connection-reset, + + /// A TCP connection was aborted. + connection-aborted, + + + /// The size of a datagram sent to a UDP socket exceeded the maximum + /// supported size. + datagram-too-large, + + + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + permanent-resolver-failure, + } + + /// Attempts to extract a network-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// network-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are network-related errors. + @unstable(feature = network-error-code) + network-error-code: func(err: borrow) -> option; + + @since(version = 0.2.0) + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + @since(version = 0.2.0) + type ipv4-address = tuple; + @since(version = 0.2.0) + type ipv6-address = tuple; + + @since(version = 0.2.0) + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + @since(version = 0.2.0) + record ipv4-socket-address { + /// sin_port + port: u16, + /// sin_addr + address: ipv4-address, + } + + @since(version = 0.2.0) + record ipv6-socket-address { + /// sin6_port + port: u16, + /// sin6_flowinfo + flow-info: u32, + /// sin6_addr + address: ipv6-address, + /// sin6_scope_id + scope-id: u32, + } + + @since(version = 0.2.0) + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } +} diff --git a/template-golem-agent-ts/wit/deps/sockets/tcp-create-socket.wit b/template-golem-agent-ts/wit/deps/sockets/tcp-create-socket.wit new file mode 100644 index 000000000..eedbd3076 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/tcp-create-socket.wit @@ -0,0 +1,30 @@ +@since(version = 0.2.0) +interface tcp-create-socket { + @since(version = 0.2.0) + use network.{network, error-code, ip-address-family}; + @since(version = 0.2.0) + use tcp.{tcp-socket}; + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + create-tcp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/template-golem-agent-ts/wit/deps/sockets/tcp.wit b/template-golem-agent-ts/wit/deps/sockets/tcp.wit new file mode 100644 index 000000000..b4cd87fce --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/tcp.wit @@ -0,0 +1,387 @@ +@since(version = 0.2.0) +interface tcp { + @since(version = 0.2.0) + use wasi:io/streams@0.2.3.{input-stream, output-stream}; + @since(version = 0.2.0) + use wasi:io/poll@0.2.3.{pollable}; + @since(version = 0.2.0) + use wasi:clocks/monotonic-clock@0.2.3.{duration}; + @since(version = 0.2.0) + use network.{network, error-code, ip-socket-address, ip-address-family}; + + @since(version = 0.2.0) + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + /// A TCP socket resource. + /// + /// The socket can be in one of the following states: + /// - `unbound` + /// - `bind-in-progress` + /// - `bound` (See note below) + /// - `listen-in-progress` + /// - `listening` + /// - `connect-in-progress` + /// - `connected` + /// - `closed` + /// See + /// for more information. + /// + /// Note: Except where explicitly mentioned, whenever this documentation uses + /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. + /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) + /// + /// In addition to the general error codes documented on the + /// `network::error-code` type, TCP socket methods may always return + /// `error(invalid-state)` when in the `closed` state. + @since(version = 0.2.0) + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// Bind can be attempted multiple times on the same socket, even with + /// different arguments on each iteration. But never concurrently and + /// only as long as the previous bind failed. Once a bind succeeds, the + /// binding can't be changed anymore. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT + /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR + /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior + /// and SO_REUSEADDR performs something different entirely. + /// + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + @since(version = 0.2.0) + finish-bind: func() -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the `connected` state. + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// After a failed connection attempt, the socket will be in the `closed` + /// state and the only valid action left is to `drop` the socket. A single + /// socket can not be used to connect more than once. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) + /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A connect operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. + /// Because all WASI sockets are non-blocking this is expected to return + /// EINPROGRESS, which should be translated to `ok()` in WASI. + /// + /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` + /// with a timeout of 0 on the socket descriptor. Followed by a check for + /// the `SO_ERROR` socket option, in case the poll signaled readiness. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + @since(version = 0.2.0) + finish-connect: func() -> result, error-code>; + + /// Start listening for new connections. + /// + /// Transitions the socket into the `listening` state. + /// + /// Unlike POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the `listening` state. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A listen operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the listen operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `listen` as part of either `start-listen` or `finish-listen`. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + start-listen: func() -> result<_, error-code>; + @since(version = 0.2.0) + finish-listen: func() -> result<_, error-code>; + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket: + /// - `address-family` + /// - `keep-alive-enabled` + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// - `hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + accept: func() -> result, error-code>; + + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + local-address: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + remote-address: func() -> result; + + /// Whether the socket is in the `listening` state. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + @since(version = 0.2.0) + is-listening: func() -> bool; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.2.0) + address-family: func() -> ip-address-family; + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` state. + @since(version = 0.2.0) + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + + /// Enables or disables keepalive. + /// + /// The keepalive behavior can be adjusted using: + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. + /// + /// Equivalent to the SO_KEEPALIVE socket option. + @since(version = 0.2.0) + keep-alive-enabled: func() -> result; + @since(version = 0.2.0) + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + + /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) + keep-alive-idle-time: func() -> result; + @since(version = 0.2.0) + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + + /// The time between keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPINTVL socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) + keep-alive-interval: func() -> result; + @since(version = 0.2.0) + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + + /// The maximum amount of keepalive packets TCP should send before aborting the connection. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPCNT socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) + keep-alive-count: func() -> result; + @since(version = 0.2.0) + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + @since(version = 0.2.0) + hop-limit: func() -> result; + @since(version = 0.2.0) + set-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) + receive-buffer-size: func() -> result; + @since(version = 0.2.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.2.0) + send-buffer-size: func() -> result; + @since(version = 0.2.0) + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which can be used to poll for, or block on, + /// completion of any of the asynchronous operations of this socket. + /// + /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` + /// return `error(would-block)`, this pollable can be used to wait for + /// their success or failure, after which the method can be retried. + /// + /// The pollable is not limited to the async operation that happens to be + /// in progress at the time of calling `subscribe` (if any). Theoretically, + /// `subscribe` only has to be called once per socket and can then be + /// (re)used for the remainder of the socket's lifetime. + /// + /// See + /// for more information. + /// + /// Note: this function is here for WASI 0.2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) + subscribe: func() -> pollable; + + /// Initiate a graceful shutdown. + /// + /// - `receive`: The socket is not expecting to receive any data from + /// the peer. The `input-stream` associated with this socket will be + /// closed. Any data still in the receive queue at time of calling + /// this method will be discarded. + /// - `send`: The socket has no more data to send to the peer. The `output-stream` + /// associated with this socket will be closed and a FIN packet will be sent. + /// - `both`: Same effect as `receive` & `send` combined. + /// + /// This function is idempotent; shutting down a direction more than once + /// has no effect and returns `ok`. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} diff --git a/template-golem-agent-ts/wit/deps/sockets/udp-create-socket.wit b/template-golem-agent-ts/wit/deps/sockets/udp-create-socket.wit new file mode 100644 index 000000000..e8eeacbfe --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/udp-create-socket.wit @@ -0,0 +1,30 @@ +@since(version = 0.2.0) +interface udp-create-socket { + @since(version = 0.2.0) + use network.{network, error-code, ip-address-family}; + @since(version = 0.2.0) + use udp.{udp-socket}; + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + create-udp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/template-golem-agent-ts/wit/deps/sockets/udp.wit b/template-golem-agent-ts/wit/deps/sockets/udp.wit new file mode 100644 index 000000000..01901ca27 --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/udp.wit @@ -0,0 +1,288 @@ +@since(version = 0.2.0) +interface udp { + @since(version = 0.2.0) + use wasi:io/poll@0.2.3.{pollable}; + @since(version = 0.2.0) + use network.{network, error-code, ip-socket-address, ip-address-family}; + + /// A received datagram. + @since(version = 0.2.0) + record incoming-datagram { + /// The payload. + /// + /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + data: list, + + /// The source address. + /// + /// This field is guaranteed to match the remote address the stream was initialized with, if any. + /// + /// Equivalent to the `src_addr` out parameter of `recvfrom`. + remote-address: ip-socket-address, + } + + /// A datagram to be sent out. + @since(version = 0.2.0) + record outgoing-datagram { + /// The payload. + data: list, + + /// The destination address. + /// + /// The requirements on this field depend on how the stream was initialized: + /// - with a remote address: this field must be None or match the stream's remote address exactly. + /// - without a remote address: this field is required. + /// + /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. + remote-address: option, + } + + /// A UDP socket handle. + @since(version = 0.2.0) + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the port is zero, the socket will be bound to a random free port. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + @since(version = 0.2.0) + finish-bind: func() -> result<_, error-code>; + + /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// + /// This function only changes the local socket configuration and does not generate any network traffic. + /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, + /// based on the best network path to `remote-address`. + /// + /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// This method may be called multiple times on the same socket to change its association, but + /// only the most recently returned pair of streams will be operational. Implementations may trap if + /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. + /// + /// The POSIX equivalent in pseudo-code is: + /// ```text + /// if (was previously connected) { + /// connect(s, AF_UNSPEC) + /// } + /// if (remote_address is Some) { + /// connect(s, remote_address) + /// } + /// ``` + /// + /// Unlike in POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-state`: The socket is not bound. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + %stream: func(remote-address: option) -> result, error-code>; + + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + local-address: func() -> result; + + /// Get the address the socket is currently streaming to. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.2.0) + address-family: func() -> ip-address-family; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + @since(version = 0.2.0) + unicast-hop-limit: func() -> result; + @since(version = 0.2.0) + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.2.0) + receive-buffer-size: func() -> result; + @since(version = 0.2.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.2.0) + send-buffer-size: func() -> result; + @since(version = 0.2.0) + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI 0.2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) + subscribe: func() -> pollable; + } + + @since(version = 0.2.0) + resource incoming-datagram-stream { + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// + /// This function returns successfully with an empty list when either: + /// - `max-results` is 0, or: + /// - `max-results` is greater than 0, but no results are immediately available. + /// This function never returns `error(would-block)`. + /// + /// # Typical errors + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + receive: func(max-results: u64) -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready to receive again. + /// + /// Note: this function is here for WASI 0.2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) + subscribe: func() -> pollable; + } + + @since(version = 0.2.0) + resource outgoing-datagram-stream { + /// Check readiness for sending. This function never blocks. + /// + /// Returns the number of datagrams permitted for the next call to `send`, + /// or an error. Calling `send` with more datagrams than this function has + /// permitted will trap. + /// + /// When this function returns ok(0), the `subscribe` pollable will + /// become ready when this function will report at least ok(1), or an + /// error. + /// + /// Never returns `would-block`. + check-send: func() -> result; + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). This function never + /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if + /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + send: func(datagrams: list) -> result; + + /// Create a `pollable` which will resolve once the stream is ready to send again. + /// + /// Note: this function is here for WASI 0.2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + @since(version = 0.2.0) + subscribe: func() -> pollable; + } +} diff --git a/template-golem-agent-ts/wit/deps/sockets/world.wit b/template-golem-agent-ts/wit/deps/sockets/world.wit new file mode 100644 index 000000000..2f0ad0d7c --- /dev/null +++ b/template-golem-agent-ts/wit/deps/sockets/world.wit @@ -0,0 +1,19 @@ +package wasi:sockets@0.2.3; + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import instance-network; + @since(version = 0.2.0) + import network; + @since(version = 0.2.0) + import udp; + @since(version = 0.2.0) + import udp-create-socket; + @since(version = 0.2.0) + import tcp; + @since(version = 0.2.0) + import tcp-create-socket; + @since(version = 0.2.0) + import ip-name-lookup; +} diff --git a/template-golem-agent-ts/wit/main.wit b/template-golem-agent-ts/wit/main.wit new file mode 100644 index 000000000..2ebb71344 --- /dev/null +++ b/template-golem-agent-ts/wit/main.wit @@ -0,0 +1,7 @@ +package golem:agent-ts; + +world golem-agent { + import golem:api/host@1.1.7; + import golem:rpc/types@0.2.2; + export golem:agent/guest; +}