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/// Mark a function as `act:sessions/session-provider.open-session`.
100///
101/// Inside `#[act_component]`, the component macro picks up this annotation,
102/// generates the `session-provider` Guest impl, and derives the
103/// `get-open-session-args-schema` JSON Schema from the function's argument
104/// type via `schemars::JsonSchema`.
105///
106/// Signature: `fn open(args: T) -> ActResult<String>` (sync or async). `T`
107/// must implement `serde::Deserialize` and `schemars::JsonSchema`. The
108/// returned `String` is the session-id the host will use in subsequent
109/// capability calls.
110///
111/// Outside `#[act_component]`, this attribute is a no-op pass-through.
112#[proc_macro_attribute]
113pub fn session_open(_attr: TokenStream, item: TokenStream) -> TokenStream {
114    item
115}
116
117/// Mark a function as `act:sessions/session-provider.close-session`.
118///
119/// Signature: `fn close(session_id: String)`. Synchronous, no return value
120/// (matches the WIT close-session signature).
121///
122/// Outside `#[act_component]`, this attribute is a no-op pass-through.
123#[proc_macro_attribute]
124pub fn session_close(_attr: TokenStream, item: TokenStream) -> TokenStream {
125    item
126}
127
128/// Embed an Agent Skills directory as an `act:skill` WASM custom section.
129///
130/// Reads the directory at compile time, packs it as an uncompressed tar archive,
131/// and emits a `#[link_section = "act:skill"]` static. The directory must contain
132/// at least a `SKILL.md` file.
133///
134/// The path is relative to the crate's `CARGO_MANIFEST_DIR`.
135///
136/// # Example
137///
138/// ```ignore
139/// act_sdk::embed_skill!("skill/");
140/// ```
141///
142/// See `ACT-AGENTSKILLS.md` for the full specification.
143#[proc_macro]
144pub fn embed_skill(input: TokenStream) -> TokenStream {
145    match skill::generate(input.into()) {
146        Ok(tokens) => tokens.into(),
147        Err(e) => e.to_compile_error().into(),
148    }
149}