Skip to main content

act_sdk_macros/
lib.rs

1mod component;
2mod manifest;
3mod skill;
4mod tool;
5
6use darling::FromMeta;
7use proc_macro::TokenStream;
8
9/// Attribute macro for ACT component modules.
10///
11/// Transforms a module containing `#[act_tool]` functions into a complete
12/// WIT component implementation with `wit_bindgen::generate!()`, `export!()`,
13/// and a `Guest` trait implementation.
14///
15/// # Manifest
16///
17/// Reads `act.toml` from the crate root for component metadata and capabilities.
18/// If `act.toml` is absent, all metadata comes from `Cargo.toml`.
19///
20/// Resolution order: **attribute > act.toml > Cargo.toml**.
21///
22/// # Attributes
23///
24/// - `manifest = "..."` — Path to manifest file (default: `"act.toml"`)
25/// - `name = "..."` — Override component name
26/// - `version = "..."` — Override component version
27/// - `description = "..."` — Override component description
28/// - `default_language = "..."` — Override BCP 47 language tag
29///
30/// # Examples
31///
32/// ```ignore
33/// // Reads act.toml, falls back to Cargo.toml:
34/// #[act_component]
35/// mod component {
36///     use super::*;
37///
38///     #[act_tool(description = "Say hello")]
39///     fn greet(name: String) -> ActResult<String> {
40///         Ok(format!("Hello, {name}!"))
41///     }
42/// }
43///
44/// // Feature-flag variant with attribute overrides:
45/// #[cfg_attr(not(feature = "vec"), act_component)]
46/// #[cfg_attr(feature = "vec", act_component(
47///     name = "sqlite-vec",
48///     description = "SQLite with vector search"
49/// ))]
50/// mod component { /* ... */ }
51/// ```
52#[proc_macro_attribute]
53pub fn act_component(attr: TokenStream, item: TokenStream) -> TokenStream {
54    let attr_stream: proc_macro2::TokenStream = attr.into();
55    let attr_args = if attr_stream.is_empty() {
56        Vec::new()
57    } else {
58        match darling::ast::NestedMeta::parse_meta_list(attr_stream) {
59            Ok(a) => a,
60            Err(e) => return TokenStream::from(darling::Error::from(e).write_errors()),
61        }
62    };
63    let attrs = match component::ComponentAttrs::from_list(&attr_args) {
64        Ok(a) => a,
65        Err(e) => return TokenStream::from(e.write_errors()),
66    };
67    let module = match syn::parse::<syn::ItemMod>(item) {
68        Ok(m) => m,
69        Err(e) => return e.to_compile_error().into(),
70    };
71    match component::generate(attrs, &module) {
72        Ok(tokens) => tokens.into(),
73        Err(e) => e.to_compile_error().into(),
74    }
75}
76
77/// Attribute macro for ACT tool functions.
78///
79/// When used inside an `#[act_component]` module, marks a function as a tool.
80/// The `#[act_component]` macro processes these attributes during code generation.
81///
82/// When used standalone (outside `#[act_component]`), this is a no-op pass-through.
83///
84/// # Attributes
85///
86/// - `description = "..."` (required) — Tool description
87/// - `read_only` — Mark tool as read-only
88/// - `idempotent` — Mark tool as idempotent
89/// - `destructive` — Mark tool as destructive
90/// - `streaming` — Mark tool as streaming (auto-detected if ActContext param present)
91/// - `timeout_ms = N` — Set timeout in milliseconds
92#[proc_macro_attribute]
93pub fn act_tool(_attr: TokenStream, item: TokenStream) -> TokenStream {
94    // When used standalone, pass through unchanged.
95    // When inside #[act_component], the component macro processes this.
96    item
97}
98
99/// Embed an Agent Skills directory as an `act:skill` WASM custom section.
100///
101/// Reads the directory at compile time, packs it as an uncompressed tar archive,
102/// and emits a `#[link_section = "act:skill"]` static. The directory must contain
103/// at least a `SKILL.md` file.
104///
105/// The path is relative to the crate's `CARGO_MANIFEST_DIR`.
106///
107/// # Example
108///
109/// ```ignore
110/// act_sdk::embed_skill!("skill/");
111/// ```
112///
113/// See `ACT-AGENTSKILLS.md` for the full specification.
114#[proc_macro]
115pub fn embed_skill(input: TokenStream) -> TokenStream {
116    match skill::generate(input.into()) {
117        Ok(tokens) => tokens.into(),
118        Err(e) => e.to_compile_error().into(),
119    }
120}