Current section
Files
Jump to
Current section
Files
lib/jido.ex
defmodule Jido do
@moduledoc """
Jido is a flexible framework for building distributed AI Agents and Workflows in Elixir. It enables intelligent automation in Elixir, with a focus on Actions, Workflows, Bots, Agents, Sensors, and Signals for creating dynamic and adaptive systems.
This module provides the main interface for interacting with Jido components, including:
- Listing and retrieving Actions, Sensors, and Domains
- Filtering and paginating results
- Generating unique slugs for components
## Examples
iex> Jido.list_actions()
[%{module: MyApp.SomeAction, name: "some_action", description: "Does something", slug: "abc123de"}]
iex> Jido.get_action_by_slug("abc123de")
%{module: MyApp.SomeAction, name: "some_action", description: "Does something", slug: "abc123de"}
"""
use Jido.Util, debug_enabled: false
@type component_metadata :: %{
module: module(),
name: String.t(),
description: String.t(),
slug: String.t(),
category: atom() | nil,
tags: [atom()] | nil
}
@doc """
Retrieves an Action by its slug.
## Parameters
- `slug`: A string representing the unique identifier of the Action.
## Returns
The Action metadata if found, otherwise `nil`.
## Examples
iex> Jido.get_action_by_slug("abc123de")
%{module: MyApp.SomeAction, name: "some_action", description: "Does something", slug: "abc123de"}
iex> Jido.get_action_by_slug("nonexistent")
nil
"""
@spec get_action_by_slug(String.t()) :: component_metadata() | nil
def get_action_by_slug(slug) do
Enum.find(list_actions(), fn action -> action.slug == slug end)
end
@doc """
Retrieves a Sensor by its slug.
## Parameters
- `slug`: A string representing the unique identifier of the Sensor.
## Returns
The Sensor metadata if found, otherwise `nil`.
## Examples
iex> Jido.get_sensor_by_slug("def456gh")
%{module: MyApp.SomeSensor, name: "some_sensor", description: "Monitors something", slug: "def456gh"}
iex> Jido.get_sensor_by_slug("nonexistent")
nil
"""
@spec get_sensor_by_slug(String.t()) :: component_metadata() | nil
def get_sensor_by_slug(slug) do
Enum.find(list_sensors(), fn sensor -> sensor.slug == slug end)
end
@doc """
Retrieves an Agent by its slug.
## Parameters
- `slug`: A string representing the unique identifier of the Agent.
## Returns
The Agent metadata if found, otherwise `nil`.
## Examples
iex> Jido.get_agent_by_slug("ghi789jk")
%{module: MyApp.SomeAgent, name: "some_agent", description: "Represents an agent", slug: "ghi789jk"}
iex> Jido.get_agent_by_slug("nonexistent")
nil
"""
@spec get_agent_by_slug(String.t()) :: component_metadata() | nil
def get_agent_by_slug(slug) do
Enum.find(list_agents(), fn agent -> agent.slug == slug end)
end
@doc """
Lists all Actions with optional filtering and pagination.
## Parameters
- `opts`: A keyword list of options for filtering and pagination. Available options:
- `:limit`: Maximum number of results to return.
- `:offset`: Number of results to skip before starting to return.
- `:name`: Filter Actions by name (partial match).
- `:description`: Filter Actions by description (partial match).
- `:category`: Filter Actions by category (exact match).
- `:tag`: Filter Actions by tag (must have the exact tag).
## Returns
A list of Action metadata.
## Examples
iex> Jido.list_actions(limit: 10, offset: 5, category: :utility)
[%{module: MyApp.SomeAction, name: "some_action", description: "Does something", slug: "abc123de", category: :utility}]
"""
@spec list_actions(keyword()) :: [component_metadata()]
def list_actions(opts \\ []) do
debug("Listing actions with options", opts: opts)
list_modules(opts, :__action_metadata__)
end
@doc """
Lists all Sensors with optional filtering and pagination.
## Parameters
- `opts`: A keyword list of options for filtering and pagination. Available options:
- `:limit`: Maximum number of results to return.
- `:offset`: Number of results to skip before starting to return.
- `:name`: Filter Sensors by name (partial match).
- `:description`: Filter Sensors by description (partial match).
- `:category`: Filter Sensors by category (exact match).
- `:tag`: Filter Sensors by tag (must have the exact tag).
## Returns
A list of Sensor metadata.
## Examples
iex> Jido.list_sensors(limit: 10, offset: 5, category: :monitoring)
[%{module: MyApp.SomeSensor, name: "some_sensor", description: "Monitors something", slug: "def456gh", category: :monitoring}]
"""
@spec list_sensors(keyword()) :: [component_metadata()]
def list_sensors(opts \\ []) do
debug("Listing sensors with options", opts: opts)
list_modules(opts, :__sensor_metadata__)
end
@doc """
Lists all Agents with optional filtering and pagination.
## Parameters
- `opts`: A keyword list of options for filtering and pagination. Available options:
- `:limit`: Maximum number of results to return.
- `:offset`: Number of results to skip before starting to return.
- `:name`: Filter Agents by name (partial match).
- `:description`: Filter Agents by description (partial match).
- `:category`: Filter Agents by category (exact match).
- `:tag`: Filter Agents by tag (must have the exact tag).
## Returns
A list of Agent metadata.
## Examples
iex> Jido.list_agents(limit: 10, offset: 5, category: :business)
[%{module: MyApp.SomeAgent, name: "some_agent", description: "Represents an agent", slug: "ghi789jk", category: :business}]
"""
@spec list_agents(keyword()) :: [component_metadata()]
def list_agents(opts \\ []) do
debug("Listing agents with options", opts: opts)
list_modules(opts, :__agent_metadata__)
end
# Private functions
@spec list_modules(keyword(), atom()) :: [component_metadata()]
defp list_modules(opts, metadata_function) do
debug("Listing modules", opts: opts, metadata_function: metadata_function)
limit = Keyword.get(opts, :limit)
offset = Keyword.get(opts, :offset, 0)
name_filter = Keyword.get(opts, :name)
description_filter = Keyword.get(opts, :description)
category_filter = Keyword.get(opts, :category)
tag_filter = Keyword.get(opts, :tag)
debug("Filters applied",
limit: limit,
offset: offset,
name: name_filter,
description: description_filter,
category: category_filter,
tag: tag_filter
)
result =
all_applications()
|> Enum.flat_map(&all_modules/1)
|> Enum.filter(&has_metadata_function?(&1, metadata_function))
|> Enum.map(fn module ->
metadata = apply(module, metadata_function, [])
module_name = to_string(module)
slug =
:sha256
|> :crypto.hash(module_name)
|> Base.url_encode64(padding: false)
|> String.slice(0, 8)
metadata
|> Map.put(:module, module)
|> Map.put(:slug, slug)
end)
|> Enum.filter(fn metadata ->
filter_metadata(metadata, name_filter, description_filter, category_filter, tag_filter)
end)
|> Enum.drop(offset)
|> maybe_limit(limit)
debug("Modules listed", count: length(result))
result
end
@spec all_applications() :: [atom()]
defp all_applications do
Application.loaded_applications()
|> Enum.map(fn {app, _description, _version} -> app end)
end
@spec all_modules(atom()) :: [module()]
defp all_modules(app) do
debug("Fetching all modules")
case :application.get_key(app, :modules) do
{:ok, modules} ->
debug("Modules fetched", count: length(modules))
modules
:undefined ->
debug("No modules found for #{app} application")
[]
end
end
@spec has_metadata_function?(module(), atom()) :: boolean()
defp has_metadata_function?(module, function) do
result = Code.ensure_loaded?(module) and function_exported?(module, function, 0)
debug("Metadata function check result", module: module, function: function, result: result)
result
end
@spec filter_metadata(map(), String.t() | nil, String.t() | nil, atom() | nil, atom() | nil) ::
boolean()
defp filter_metadata(metadata, name, description, category, tag) do
debug("Filtering metadata",
metadata: metadata,
name: name,
description: description,
category: category,
tag: tag
)
name_match = is_nil(name) or String.contains?(metadata[:name] || "", name)
description_match =
is_nil(description) or String.contains?(metadata[:description] || "", description)
category_match = is_nil(category) or metadata[:category] == category
tag_match = is_nil(tag) or (is_list(metadata[:tags]) and tag in metadata[:tags])
result = name_match and description_match and category_match and tag_match
debug("Filter result", result: result)
result
end
@spec maybe_limit(list(), non_neg_integer() | nil) :: list()
defp maybe_limit(list, nil) do
debug("No limit applied to list")
list
end
defp maybe_limit(list, limit) when is_integer(limit) and limit > 0 do
debug("Applying limit to list", limit: limit)
Enum.take(list, limit)
end
defp maybe_limit(list, _) do
debug("Invalid limit, returning original list")
list
end
end