en un clic
create-ai-tool
// Build a new AI tool end-to-end — Rust implementation, toolset wiring, infra, schema generation, and frontend UI.
// Build a new AI tool end-to-end — Rust implementation, toolset wiring, infra, schema generation, and frontend UI.
Validate Rust work after substantial Rust code changes by running `just check`, `just clippy`, then `just format`. Use before the final response after a significant Rust implementation or cleanup task; batch edits first instead of running after every small change.
Inspect Rust changes for SQLx queries. Use after modifying Rust code that adds or changes SQLx queries to ensure compile-time SQLx macros are used, run `just prepare_db` for offline query cache, and review queries for performance and security issues.
Upgrade an AI chat model (fast or good) across backend and frontend.
Create SQLx migration files with `sqlx migrate add <name>`. Use when asked to add, create, or generate a sqlx/sqlx-cli database migration.
Find all open Dependabot alerts for this repo and create a plan to resolve them using the appropriate package manager overrides (pnpm, bun, npm, cargo).
Dump clean Postgres schema to a file and copy path to clipboard.
| name | create-ai-tool |
| description | Build a new AI tool end-to-end — Rust implementation, toolset wiring, infra, schema generation, and frontend UI. |
This skill walks through building a new AI tool from scratch. Before writing any code, read the design guide at rust/cloud-storage/ai_toolset/TOOL_DESIGN.md and the framework docs/examples in rust/cloud-storage/ai_toolset/src/lib.rs.
IMPORTANT: Never modify the rust/cloud-storage/ai_toolset/ crate. It is the framework — you build tools that use it.
Decide which domain crate the tool belongs in. Tools live at rust/cloud-storage/<crate>/src/inbound/toolset/.
Study an existing tool for patterns:
rust/cloud-storage/documents/src/inbound/toolset/ — tools: read_content.rs, read_metadata.rs, create_document.rsrust/cloud-storage/email/src/inbound/toolset/ — tools: send_email.rs, get_thread.rs, update_thread_labels.rsrust/cloud-storage/soup/src/inbound/toolset/ — tool: list_entities.rsrust/cloud-storage/call/src/inbound/toolset/ — call-related toolsEach tool is a struct that derives JsonSchema and Deserialize, with #[schemars(title = "...", description = "...")] on the struct and #[schemars(description = "...")] on each field. The struct implements AsyncTool<Context> from the ai_toolset crate.
Create a new file for your tool (e.g. my_tool.rs), add it as a mod in the toolset's mod.rs, and wire it into the toolset's AsyncToolSet::new().add_tool::<...>() chain.
If your tool needs dependencies (DB connections, service clients) that aren't already in an existing context, define a new context struct in the toolset's mod.rs. See rust/cloud-storage/documents/src/inbound/toolset/mod.rs for the DocumentToolContext pattern.
The context must be Clone and derivable from the parent ToolServiceContext via FromRef.
If the tool's dependencies are already available in an existing context (e.g. it only needs Arc<ToolScribe>), you can use that context directly — no new struct needed.
all_tools in the ai_tools crateEdit rust/cloud-storage/ai_tools/src/lib.rs:
.add_tool::<YourTool, YourContext>() or .add_subtoolset::<YourToolContext>(your_toolset()) to the all_tools() functionEdit rust/cloud-storage/ai_tools/src/tool_context.rs:
Tool* naming pattern)ToolServiceContext structFromRef<ToolServiceContext> for your context if needed (or derive it — the struct uses #[derive(FromRef)])Edit rust/cloud-storage/ai_tools/src/build_context.rs:
env_var! or maybe_env_var! blocksbuild_tool_service_context_from_envToolServiceContextEdit infra/packages/shared/src/ai_tools.ts:
envVars array in getAiToolsInfra()Run from rust/cloud-storage/:
cargo fmt
cargo clippy -p ai_tools
cargo test -p <your_domain_crate>
Fix any warnings or errors before proceeding.
Run from js/app/:
bun gen-tools
This builds rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs, generates rust/cloud-storage/ai_tools/schemas/tools.json, and transpiles the schemas into TypeScript at js/app/packages/service-clients/service-cognition/generated/tools/.
Run from js/app/:
bun check
This runs tsc --noEmit and will report type errors — specifically, the toolHandlers map in js/app/packages/core/component/AI/component/tool/handler.tsx will be missing your new tool name. The errors tell you exactly what to implement.
The tool UI components live at js/app/packages/core/component/AI/component/tool/. Study existing renderers:
Search.tsx — search results renderingReadContent.tsx / ReadMetadata.tsx — document tool UISendEmail.tsx — email tool UIListEntities.tsx — list displayProperties.tsx — property get/set toolsListCallRecords.tsx / ReadCallRecord.tsx — call toolsBaseTool.tsx — shared base componentEach tool needs a handler object implementing ToolHandler (from ToolRenderer.tsx) with at minimum a render component. Use createToolRenderer to create it.
js/app/packages/core/component/AI/component/tool/YourTool.tsxcreateToolRendererjs/app/packages/core/component/AI/component/tool/handler.tsx:
toolHandlers map with the key matching your tool's schema titleEvery tool renderer must show results. Use the expandable dropdown pattern from Search.tsx / ListEntities.tsx:
BaseTool (from BaseTool.tsx) as the wrapper. It accepts a response prop for expandable content.const [isExpanded, setIsExpanded] = createSignal(false) to track open/closed state.ctx.response?.data (typed from the generated schema).CaretRight toggle button (@icon/regular/caret-right.svg?component-solid) that rotates 90deg when expanded.BaseTool's response prop, gated on isExpanded().For tools that return text/string results (not entity lists), render the response string in a <pre> or similar block inside the response prop. The key point: the response data must always be surfaced in the UI via the dropdown — never silently swallowed.
Run from js/app/:
bun format
bun check
Fix any remaining type or formatting errors.