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}