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