refactor: Agents that depend on global tools now have all binaries compiled and stored in the agent's bin directory so multiple agents can run at once
This commit is contained in:
@@ -3,9 +3,8 @@ set -e
|
|||||||
|
|
||||||
# @env LLM_OUTPUT=/dev/stdout The output path
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
PROMPT_UTILS="${LLM_ROOT_DIR:-$(dirname "${BASH_SOURCE[0]}")/..}/functions/utils/prompt-utils.sh"
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$PROMPT_UTILS"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
# @cmd Create a new file at the specified path with the given contents.
|
# @cmd Create a new file at the specified path with the given contents.
|
||||||
# @option --path! The path where the file should be created
|
# @option --path! The path where the file should be created
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ set -e
|
|||||||
# @env LLM_OUTPUT=/dev/stdout The output path
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
# @env LLM_AGENT_VAR_DSN! The database connection url. e.g. pgsql://user:pass@host:port
|
# @env LLM_AGENT_VAR_DSN! The database connection url. e.g. pgsql://user:pass@host:port
|
||||||
|
|
||||||
PROMPT_UTILS="${LLM_ROOT_DIR:-$(dirname "${BASH_SOURCE[0]}")/..}/functions/utils/prompt-utils.sh"
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$PROMPT_UTILS"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
# @cmd Execute a SELECT query
|
# @cmd Execute a SELECT query
|
||||||
# @option --query! SELECT SQL query to execute
|
# @option --query! SELECT SQL query to execute
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ setup_env() {
|
|||||||
export LLM_AGENT_FUNC="$agent_func"
|
export LLM_AGENT_FUNC="$agent_func"
|
||||||
export LLM_AGENT_ROOT_DIR="$LLM_ROOT_DIR/agents/{agent_name}"
|
export LLM_AGENT_ROOT_DIR="$LLM_ROOT_DIR/agents/{agent_name}"
|
||||||
export LLM_AGENT_CACHE_DIR="$LLM_ROOT_DIR/cache/{agent_name}"
|
export LLM_AGENT_CACHE_DIR="$LLM_ROOT_DIR/cache/{agent_name}"
|
||||||
|
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
|
||||||
}
|
}
|
||||||
|
|
||||||
load_env() {
|
load_env() {
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ def main():
|
|||||||
raw_data = parse_argv()
|
raw_data = parse_argv()
|
||||||
tool_data = parse_raw_data(raw_data)
|
tool_data = parse_raw_data(raw_data)
|
||||||
|
|
||||||
root_dir = "{config_dir}/functions"
|
root_dir = "{root_dir}"
|
||||||
setup_env(root_dir)
|
setup_env(root_dir)
|
||||||
|
|
||||||
tool_path = os.path.join(root_dir, "tools/{function_name}.py")
|
tool_path = "{tool_path}.py"
|
||||||
run(tool_path, "run", tool_data)
|
run(tool_path, "run", tool_data)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
root_dir="{config_dir}/functions"
|
root_dir="{root_dir}"
|
||||||
parse_argv "$@"
|
parse_argv "$@"
|
||||||
setup_env
|
setup_env
|
||||||
tool_path="$root_dir/tools/{function_name}.sh"
|
tool_path="{tool_path}.sh"
|
||||||
run
|
run
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ setup_env() {
|
|||||||
export LLM_ROOT_DIR="$root_dir"
|
export LLM_ROOT_DIR="$root_dir"
|
||||||
export LLM_TOOL_NAME="{function_name}"
|
export LLM_TOOL_NAME="{function_name}"
|
||||||
export LLM_TOOL_CACHE_DIR="$LLM_ROOT_DIR/cache/{function_name}"
|
export LLM_TOOL_CACHE_DIR="$LLM_ROOT_DIR/cache/{function_name}"
|
||||||
|
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
|
||||||
}
|
}
|
||||||
|
|
||||||
load_env() {
|
load_env() {
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ set -e
|
|||||||
|
|
||||||
# @env LLM_OUTPUT=/dev/stdout The output path
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
PROMPT_UTILS="${LLM_ROOT_DIR:-$(dirname "${BASH_SOURCE[0]}")/..}/functions/utils/prompt-utils.sh"
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$PROMPT_UTILS"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
guard_operation
|
guard_operation
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ set -e
|
|||||||
# @env USQL_DSN! The database connection url. e.g. pgsql://user:pass@host:port
|
# @env USQL_DSN! The database connection url. e.g. pgsql://user:pass@host:port
|
||||||
# @env LLM_OUTPUT=/dev/stdout The output path
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
PROMPT_UTILS="${LLM_ROOT_DIR:-$(dirname "${BASH_SOURCE[0]}")/..}/functions/utils/prompt-utils.sh"
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$PROMPT_UTILS"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ set -e
|
|||||||
|
|
||||||
# @env LLM_OUTPUT=/dev/stdout The output path
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
PROMPT_UTILS="${LLM_ROOT_DIR:-$(dirname "${BASH_SOURCE[0]}")/..}/functions/utils/prompt-utils.sh"
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$PROMPT_UTILS"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ set -e
|
|||||||
|
|
||||||
# @env LLM_OUTPUT=/dev/stdout The output path
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
PROMPT_UTILS="${LLM_ROOT_DIR:-$(dirname "${BASH_SOURCE[0]}")/..}/functions/utils/prompt-utils.sh"
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$PROMPT_UTILS"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ set -e
|
|||||||
|
|
||||||
# @env LLM_OUTPUT=/dev/stdout The output path
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
PROMPT_UTILS="${LLM_ROOT_DIR:-$(dirname "${BASH_SOURCE[0]}")/..}/functions/utils/prompt-utils.sh"
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$PROMPT_UTILS"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# Loki Shell Integrations
|
||||||
|
Loki supports the following integrations with a handful of shell environments to enhance user experience and streamline workflows.
|
||||||
|
|
||||||
|
## Tab Completions
|
||||||
|
### Dynamic
|
||||||
|
Dynamic tab completions are supported by Loki to assist users in quickly completing commands, options, and arguments.
|
||||||
|
You can enable it by using the corresponding command for your shell. To enable dynamic tab completions for every
|
||||||
|
shell session (i.e. persistently), add the corresponding command to your shell's configuration file as indicated:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Bash
|
||||||
|
# (add to: `~/.bashrc`)
|
||||||
|
source <(COMPLETE=bash loki)
|
||||||
|
|
||||||
|
# Zsh
|
||||||
|
# (add to: `~/.zshrc`)
|
||||||
|
source <(COMPLETE=zsh loki)
|
||||||
|
|
||||||
|
# Fish
|
||||||
|
# (add to: `~/.config/fish/config.fish`)
|
||||||
|
source <(COMPLETE=fish loki | psub)
|
||||||
|
|
||||||
|
# Elvish
|
||||||
|
# (add to: `~/.elvish/rc.elv`)
|
||||||
|
eval (E:COMPLETE=elvish loki | slurp)
|
||||||
|
|
||||||
|
# PowerShell
|
||||||
|
# (add to: `$PROFILE`)
|
||||||
|
$env:COMPLETE = "powershell"
|
||||||
|
loki | Out-String | Invoke-Expression
|
||||||
|
```
|
||||||
|
|
||||||
|
At the time of writing, `nushell` is not yet fully supported for dynamic tab completions due to limitations
|
||||||
|
in the [`clap`](https://crates.io/crates/clap) crate. However, `nushell` support is being actively developed, and will
|
||||||
|
be added in a future release.
|
||||||
|
|
||||||
|
Progress on this feature can be tracked in the following issue: [Clap Issue #5840](https://github.com/clap-rs/clap/issues/5840).
|
||||||
|
|
||||||
|
### Static
|
||||||
|
Static tab completions (i.e. pre-generated completion scripts that are not context aware) can also be generated using the
|
||||||
|
`--completions` flag. You can enable static tab completions by using the corresponding commands for your shell. These commands
|
||||||
|
will enable them for every shell session (i.e. persistently):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Bash
|
||||||
|
echo 'source <(loki --completions bash)' >> ~/.bashrc
|
||||||
|
|
||||||
|
# Zsh
|
||||||
|
echo 'source <(loki --completions zsh)' >> ~/.zshrc
|
||||||
|
|
||||||
|
# Fish
|
||||||
|
echo 'loki --completions fish | source' >> ~/.config/fish/config.fish
|
||||||
|
|
||||||
|
# Elvish
|
||||||
|
echo 'eval (loki --completions elvish | slurp)' >> ~/.elvish/rc.elv
|
||||||
|
|
||||||
|
# Nushell
|
||||||
|
[[ -d ~/.config/nushell/completions ]] || mkdir -p ~/.config/nushell/completions
|
||||||
|
loki --completions nushell | save -f ~/.config/nushell/completions/loki.nu
|
||||||
|
echo 'use ~/.config/nushell/completions/cli.nu *' >> ~/.config/nushell/config.nu
|
||||||
|
|
||||||
|
# PowerShell
|
||||||
|
Add-content $PROFILE "loki --completions powershell | Out-String | Invoke-Expression"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shell Assistant
|
||||||
|
Loki has an `-e,--execute` flag that allows users to run natural language commands directly from the CLI. It accepts
|
||||||
|
natural language input and translates it into executable shell commands.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Intelligent Command Completions
|
||||||
|
Loki also provides shell scripts that bind `Alt-e` to `loki -e "<current command line>"`, allowing users to generate
|
||||||
|
commands from natural text directly without invoking the CLI.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ find all typescript files with more than 100 lines<Alt-e>
|
||||||
|
# Gets replaced with
|
||||||
|
$ find . -name '*.ts' -type f -exec awk 'NR>100{exit 1}' {} \; -print
|
||||||
|
```
|
||||||
|
|
||||||
|
To use the CLI helper, add the content of the appropriate integration script for your shell to your shell configuration file:
|
||||||
|
* [Bash Integration](../scripts/shell-integration/bash-integration.sh) (add to: `~/.bashrc`)
|
||||||
|
* [Zsh Integration](../scripts/shell-integration/zsh-integration.zsh) (add to: `~/.zshrc`)
|
||||||
|
* [Elvish Integration](../scripts/shell-integration/elvish-integration.elv) (add to: `~/.elvish/rc.elv`)
|
||||||
|
* [Fish Integration](../scripts/shell-integration/fish-integration.fish) (add to: `~/.config/fish/config.fish`)
|
||||||
|
* [Nushell Integration](../scripts/shell-integration/nushell-integration.nu) (add to: `~/.config/nushell/config.nu`)
|
||||||
|
* [PowerShell Integration](../scripts/shell-integration/powershell-integration.ps1) (add to: `$PROFILE`)
|
||||||
|
|
||||||
|
## Code Generation
|
||||||
|
Users can also directly generate code snippets from natural language prompts using the `-c,--code` flag.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Pro Tip:** Pipe the output of the code generation directly into `tee` to ensure the generated code is properly extracted
|
||||||
|
from any generated Markdown (i.e. remove any triple backticks).
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
# The Loki Vault
|
||||||
|
The Loki vault lets users store sensitive secrets and credentials securely so that there's no plaintext secrets
|
||||||
|
anywhere in your configurations.
|
||||||
|
|
||||||
|
It's based on the [G-Man library](https://github.com/Dark-Alex-17/gman) (which also comes in a binary format) which
|
||||||
|
functions as a universal secret management tool.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Usage
|
||||||
|
The Loki vault can be used in one of two ways: via the CLI or via the REPL for interactive usage.
|
||||||
|
|
||||||
|
### CLI Usage
|
||||||
|
The vault is utilized from the CLI with the following flags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--add-secret <SECRET_NAME> Add a secret to the Loki vault
|
||||||
|
--get-secret <SECRET_NAME> Decrypt a secret from the Loki vault and print the plaintext
|
||||||
|
--update-secret <SECRET_NAME> Update an existing secret in the Loki vault
|
||||||
|
--delete-secret <SECRET_NAME> Delete a secret from the Loki vault
|
||||||
|
--list-secrets List all secrets stored in the Loki vault
|
||||||
|
```
|
||||||
|
(The above is also documented in `loki --help`)
|
||||||
|
|
||||||
|
Loki will guide you through manipulating your secrets to make usage easier.
|
||||||
|
|
||||||
|
### REPL Usage
|
||||||
|
The vault can be access from within the Loki REPL using the `.vault` commands:
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
The manipulation of your vault is guided in the same way as the CLI usage, ensuring ease of use.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
Loki is intended to be highly configurable and adaptable to many different use cases. This means that users of Loki
|
||||||
|
should be able to share configurations for agents, tools, roles, etc. with other users or even entire teams.
|
||||||
|
|
||||||
|
My objective is to encourage this, and to make it so that users can easily version their configurations using version
|
||||||
|
control. Good VCS hygiene dictates that one *never* commits secrets or sensitive information to a repository.
|
||||||
|
|
||||||
|
Since a number of files and configurations in Loki may contain sensitive information, the vault exists to solve this problem.
|
||||||
|
|
||||||
|
Users can either share the vault password with a team, making it so a single configuration can be pulled from VCS and used
|
||||||
|
by said team. Alternatively, each user can maintain their own vault password and expect other users to replace secret values
|
||||||
|
with their user-specific secrets.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
When you first start Loki, if you don't already have a vault password file, it will prompt you to create one. This file
|
||||||
|
houses the password that is used to encrypt and decrypt secrets within Loki. This file exists so that you are not prompted
|
||||||
|
for a password every time Loki attempts to decrypt a secret.
|
||||||
|
|
||||||
|
When you encrypt a secret, it uses the local provider for `gman` to securely store those secrets in the Loki vault file.
|
||||||
|
This file is typically located at your Loki configuration directory under `vault.yml`. If you open this file, you'll see a
|
||||||
|
bunch of gibberish. This is because all secrets are encrypted using the password you provided, meaning only you can decrypt them.
|
||||||
|
|
||||||
|
Secrets are specified in Loki configurations using the same variable templating as the [Jinja templating engine](https://jinja.palletsprojects.com/en/stable/):
|
||||||
|
|
||||||
|
```
|
||||||
|
{{some_variable}}
|
||||||
|
```
|
||||||
|
|
||||||
|
So whenever you want Loki to use a secret from the vault, you simply specify the secret name in this format in the applicable
|
||||||
|
file.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
Suppose my vault has a secret called `GITHUB_TOKEN` in it, and I want to use that in the MCP configuration. Then, I simply replace
|
||||||
|
the expected value in my `mcp.json` with the templated secret:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"atlassian": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "mcp-remote", "https://mcp.atlassian.com/v1/sse"]
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"command": "docker",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"-i",
|
||||||
|
"--rm",
|
||||||
|
"-e",
|
||||||
|
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||||
|
"ghcr.io/github/github-mcp-server"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"GITHUB_PERSONAL_ACCESS_TOKEN": "{{GITHUB_TOKEN}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At runtime, Loki will detect the templated secret and replace it with the decrypted value from the vault before executing.
|
||||||
|
|
||||||
|
## Supported Files
|
||||||
|
At the time of writing, the following files support Loki secret injection:
|
||||||
|
|
||||||
|
| File Type | Description | Limitations |
|
||||||
|
|----------------------|-----------------------------------|----------------------------------------------------------------|
|
||||||
|
| `config.yaml` | The main Loki configuration file | Cannot use secret injection on the `vault_password_file` field |
|
||||||
|
| `functions/mcp.json` | The MCP server configuration file | |
|
||||||
|
|
||||||
|
|
||||||
|
Note that all paths are relative to the Loki configuration directory. The directory varies by system, so you can find yours by
|
||||||
|
running
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dirname $(loki --info | grep config_file | awk '{print $2}')
|
||||||
|
```
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 490 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -389,6 +389,7 @@ impl Agent {
|
|||||||
self.name().to_string(),
|
self.name().to_string(),
|
||||||
vec!["_instructions".into(), "{}".into()],
|
vec!["_instructions".into(), "{}".into()],
|
||||||
self.variable_envs(),
|
self.variable_envs(),
|
||||||
|
Some(self.name().to_string()),
|
||||||
)?;
|
)?;
|
||||||
match value {
|
match value {
|
||||||
Some(v) => Ok(v),
|
Some(v) => Ok(v),
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ const FUNCTIONS_BIN_DIR_NAME: &str = "bin";
|
|||||||
const AGENTS_DIR_NAME: &str = "agents";
|
const AGENTS_DIR_NAME: &str = "agents";
|
||||||
const GLOBAL_TOOLS_DIR_NAME: &str = "tools";
|
const GLOBAL_TOOLS_DIR_NAME: &str = "tools";
|
||||||
const GLOBAL_TOOLS_FILE_NAME: &str = "tools.txt";
|
const GLOBAL_TOOLS_FILE_NAME: &str = "tools.txt";
|
||||||
|
const GLOBAL_TOOLS_UTILS_DIR_NAME: &str = "utils";
|
||||||
|
const BASH_PROMPT_UTILS_FILE_NAME: &str = "prompt-utils.sh";
|
||||||
const MCP_FILE_NAME: &str = "mcp.json";
|
const MCP_FILE_NAME: &str = "mcp.json";
|
||||||
|
|
||||||
const CLIENTS_FIELD: &str = "clients";
|
const CLIENTS_FIELD: &str = "clients";
|
||||||
@@ -492,6 +494,14 @@ impl Config {
|
|||||||
Self::functions_dir().join(GLOBAL_TOOLS_DIR_NAME)
|
Self::functions_dir().join(GLOBAL_TOOLS_DIR_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn global_utils_dir() -> PathBuf {
|
||||||
|
Self::functions_dir().join(GLOBAL_TOOLS_UTILS_DIR_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bash_prompt_utils_file() -> PathBuf {
|
||||||
|
Self::global_utils_dir().join(BASH_PROMPT_UTILS_FILE_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_file(&self, name: &str) -> PathBuf {
|
pub fn session_file(&self, name: &str) -> PathBuf {
|
||||||
match name.split_once("/") {
|
match name.split_once("/") {
|
||||||
Some((dir, name)) => self.sessions_dir().join(dir).join(format!("{name}.yaml")),
|
Some((dir, name)) => self.sessions_dir().join(dir).join(format!("{name}.yaml")),
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ const PATH_SEP: &str = ";";
|
|||||||
const PATH_SEP: &str = ":";
|
const PATH_SEP: &str = ":";
|
||||||
|
|
||||||
#[derive(AsRefStr)]
|
#[derive(AsRefStr)]
|
||||||
enum BinaryType {
|
enum BinaryType<'a> {
|
||||||
Tool,
|
Tool(Option<&'a str>),
|
||||||
Agent,
|
Agent,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +169,7 @@ impl Functions {
|
|||||||
|
|
||||||
pub fn init() -> Result<Self> {
|
pub fn init() -> Result<Self> {
|
||||||
Self::install_global_tools()?;
|
Self::install_global_tools()?;
|
||||||
|
Self::clear_global_functions_bin_dir()?;
|
||||||
info!(
|
info!(
|
||||||
"Initializing global functions from {}",
|
"Initializing global functions from {}",
|
||||||
Config::global_tools_file().display()
|
Config::global_tools_file().display()
|
||||||
@@ -191,6 +192,8 @@ impl Functions {
|
|||||||
|
|
||||||
pub fn init_agent(name: &str, global_tools: &[String]) -> Result<Self> {
|
pub fn init_agent(name: &str, global_tools: &[String]) -> Result<Self> {
|
||||||
Self::install_global_tools()?;
|
Self::install_global_tools()?;
|
||||||
|
Self::clear_agent_bin_dir(name)?;
|
||||||
|
|
||||||
let global_tools_declarations = if !global_tools.is_empty() {
|
let global_tools_declarations = if !global_tools.is_empty() {
|
||||||
let enabled_tools = global_tools.join("\n");
|
let enabled_tools = global_tools.join("\n");
|
||||||
info!("Loading global tools for agent: {name}: {enabled_tools}");
|
info!("Loading global tools for agent: {name}: {enabled_tools}");
|
||||||
@@ -200,7 +203,7 @@ impl Functions {
|
|||||||
"Building global function binaries required by agent: {name} in {}",
|
"Building global function binaries required by agent: {name} in {}",
|
||||||
Config::functions_bin_dir().display()
|
Config::functions_bin_dir().display()
|
||||||
);
|
);
|
||||||
Self::build_global_function_binaries(&enabled_tools)?;
|
Self::build_global_function_binaries(&enabled_tools, Some(name))?;
|
||||||
tools_declarations
|
tools_declarations
|
||||||
} else {
|
} else {
|
||||||
debug!("No global tools found for agent: {}", name);
|
debug!("No global tools found for agent: {}", name);
|
||||||
@@ -381,17 +384,7 @@ impl Functions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_global_function_binaries(enabled_tools: &str) -> Result<()> {
|
fn build_global_function_binaries(enabled_tools: &str, agent_name: Option<&str>) -> Result<()> {
|
||||||
let bin_dir = Config::functions_bin_dir();
|
|
||||||
if !bin_dir.exists() {
|
|
||||||
fs::create_dir_all(&bin_dir)?;
|
|
||||||
}
|
|
||||||
info!(
|
|
||||||
"Clearing existing function binaries in {}",
|
|
||||||
bin_dir.display()
|
|
||||||
);
|
|
||||||
clear_dir(&bin_dir)?;
|
|
||||||
|
|
||||||
for line in enabled_tools.lines() {
|
for line in enabled_tools.lines() {
|
||||||
if line.starts_with('#') {
|
if line.starts_with('#') {
|
||||||
continue;
|
continue;
|
||||||
@@ -417,7 +410,7 @@ impl Functions {
|
|||||||
bail!("Unsupported tool file extension: {}", language.as_ref());
|
bail!("Unsupported tool file extension: {}", language.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::build_binaries(binary_name, language, BinaryType::Tool)?;
|
Self::build_binaries(binary_name, language, BinaryType::Tool(agent_name))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -427,10 +420,10 @@ impl Functions {
|
|||||||
let enabled_tools = fs::read_to_string(&tools_txt_path)
|
let enabled_tools = fs::read_to_string(&tools_txt_path)
|
||||||
.with_context(|| format!("failed to load functions at {}", tools_txt_path.display()))?;
|
.with_context(|| format!("failed to load functions at {}", tools_txt_path.display()))?;
|
||||||
|
|
||||||
Self::build_global_function_binaries(&enabled_tools)
|
Self::build_global_function_binaries(&enabled_tools, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_agent_tool_binaries(name: &str) -> Result<()> {
|
fn clear_agent_bin_dir(name: &str) -> Result<()> {
|
||||||
let agent_bin_directory = Config::agent_bin_dir(name);
|
let agent_bin_directory = Config::agent_bin_dir(name);
|
||||||
if !agent_bin_directory.exists() {
|
if !agent_bin_directory.exists() {
|
||||||
debug!(
|
debug!(
|
||||||
@@ -446,6 +439,25 @@ impl Functions {
|
|||||||
clear_dir(&agent_bin_directory)?;
|
clear_dir(&agent_bin_directory)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_global_functions_bin_dir() -> Result<()> {
|
||||||
|
let bin_dir = Config::functions_bin_dir();
|
||||||
|
if !bin_dir.exists() {
|
||||||
|
fs::create_dir_all(&bin_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Clearing existing function binaries in {}",
|
||||||
|
bin_dir.display()
|
||||||
|
);
|
||||||
|
clear_dir(&bin_dir)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_agent_tool_binaries(name: &str) -> Result<()> {
|
||||||
let language = Language::from(
|
let language = Language::from(
|
||||||
&Config::agent_functions_file(name)?
|
&Config::agent_functions_file(name)?
|
||||||
.extension()
|
.extension()
|
||||||
@@ -471,11 +483,16 @@ impl Functions {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use native::runtime;
|
use native::runtime;
|
||||||
let (binary_file, binary_script_file) = match binary_type {
|
let (binary_file, binary_script_file) = match binary_type {
|
||||||
BinaryType::Tool => (
|
BinaryType::Tool(None) => (
|
||||||
Config::functions_bin_dir().join(format!("{binary_name}.cmd")),
|
Config::functions_bin_dir().join(format!("{binary_name}.cmd")),
|
||||||
Config::functions_bin_dir()
|
Config::functions_bin_dir()
|
||||||
.join(format!("run-{binary_name}.{}", language.to_extension())),
|
.join(format!("run-{binary_name}.{}", language.to_extension())),
|
||||||
),
|
),
|
||||||
|
BinaryType::Tool(Some(agent_name)) => (
|
||||||
|
Config::agent_bin_dir(agent_name).join(format!("{binary_name}.cmd")),
|
||||||
|
Config::agent_bin_dir(agent_name)
|
||||||
|
.join(format!("run-{binary_name}.{}", language.to_extension())),
|
||||||
|
),
|
||||||
BinaryType::Agent => (
|
BinaryType::Agent => (
|
||||||
Config::agent_bin_dir(binary_name).join(format!("{binary_name}.cmd")),
|
Config::agent_bin_dir(binary_name).join(format!("{binary_name}.cmd")),
|
||||||
Config::agent_bin_dir(binary_name)
|
Config::agent_bin_dir(binary_name)
|
||||||
@@ -501,10 +518,36 @@ impl Functions {
|
|||||||
})?;
|
})?;
|
||||||
let content_template = unsafe { std::str::from_utf8_unchecked(&embedded_file.data) };
|
let content_template = unsafe { std::str::from_utf8_unchecked(&embedded_file.data) };
|
||||||
let content = match binary_type {
|
let content = match binary_type {
|
||||||
BinaryType::Tool => content_template.replace("{function_name}", binary_name),
|
BinaryType::Tool(None) => {
|
||||||
BinaryType::Agent => content_template.replace("{agent_name}", binary_name),
|
let root_dir = Config::functions_dir();
|
||||||
|
let tool_path = format!(
|
||||||
|
"{}/{binary_name}",
|
||||||
|
&Config::global_tools_dir().to_string_lossy()
|
||||||
|
);
|
||||||
|
content_template
|
||||||
|
.replace("{function_name}", binary_name)
|
||||||
|
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||||
|
.replace("{tool_path}", &tool_path)
|
||||||
|
}
|
||||||
|
BinaryType::Tool(Some(agent_name)) => {
|
||||||
|
let root_dir = Config::agent_data_dir(agent_name);
|
||||||
|
let tool_path = format!(
|
||||||
|
"{}/{binary_name}",
|
||||||
|
&Config::global_tools_dir().to_string_lossy()
|
||||||
|
);
|
||||||
|
content_template
|
||||||
|
.replace("{function_name}", binary_name)
|
||||||
|
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||||
|
.replace("{tool_path}", &tool_path)
|
||||||
|
}
|
||||||
|
BinaryType::Agent => content_template
|
||||||
|
.replace("{agent_name}", binary_name)
|
||||||
|
.replace("{config_dir}", &Config::config_dir().to_string_lossy()),
|
||||||
}
|
}
|
||||||
.replace("{config_dir}", &Config::config_dir().to_string_lossy());
|
.replace(
|
||||||
|
"{prompt_utils_file}",
|
||||||
|
&Config::bash_prompt_utils_file().to_string_lossy(),
|
||||||
|
);
|
||||||
if binary_script_file.exists() {
|
if binary_script_file.exists() {
|
||||||
fs::remove_file(&binary_script_file)?;
|
fs::remove_file(&binary_script_file)?;
|
||||||
}
|
}
|
||||||
@@ -578,7 +621,10 @@ impl Functions {
|
|||||||
use std::os::unix::prelude::PermissionsExt;
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
|
|
||||||
let binary_file = match binary_type {
|
let binary_file = match binary_type {
|
||||||
BinaryType::Tool => Config::functions_bin_dir().join(binary_name),
|
BinaryType::Tool(None) => Config::functions_bin_dir().join(binary_name),
|
||||||
|
BinaryType::Tool(Some(agent_name)) => {
|
||||||
|
Config::agent_bin_dir(agent_name).join(binary_name)
|
||||||
|
}
|
||||||
BinaryType::Agent => Config::agent_bin_dir(binary_name).join(binary_name),
|
BinaryType::Agent => Config::agent_bin_dir(binary_name).join(binary_name),
|
||||||
};
|
};
|
||||||
info!(
|
info!(
|
||||||
@@ -600,10 +646,36 @@ impl Functions {
|
|||||||
})?;
|
})?;
|
||||||
let content_template = unsafe { std::str::from_utf8_unchecked(&embedded_file.data) };
|
let content_template = unsafe { std::str::from_utf8_unchecked(&embedded_file.data) };
|
||||||
let content = match binary_type {
|
let content = match binary_type {
|
||||||
BinaryType::Tool => content_template.replace("{function_name}", binary_name),
|
BinaryType::Tool(None) => {
|
||||||
BinaryType::Agent => content_template.replace("{agent_name}", binary_name),
|
let root_dir = Config::functions_dir();
|
||||||
|
let tool_path = format!(
|
||||||
|
"{}/{binary_name}",
|
||||||
|
&Config::global_tools_dir().to_string_lossy()
|
||||||
|
);
|
||||||
|
content_template
|
||||||
|
.replace("{function_name}", binary_name)
|
||||||
|
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||||
|
.replace("{tool_path}", &tool_path)
|
||||||
|
}
|
||||||
|
BinaryType::Tool(Some(agent_name)) => {
|
||||||
|
let root_dir = Config::agent_data_dir(agent_name);
|
||||||
|
let tool_path = format!(
|
||||||
|
"{}/{binary_name}",
|
||||||
|
&Config::global_tools_dir().to_string_lossy()
|
||||||
|
);
|
||||||
|
content_template
|
||||||
|
.replace("{function_name}", binary_name)
|
||||||
|
.replace("{root_dir}", &root_dir.to_string_lossy())
|
||||||
|
.replace("{tool_path}", &tool_path)
|
||||||
|
}
|
||||||
|
BinaryType::Agent => content_template
|
||||||
|
.replace("{agent_name}", binary_name)
|
||||||
|
.replace("{config_dir}", &Config::config_dir().to_string_lossy()),
|
||||||
}
|
}
|
||||||
.replace("{config_dir}", &Config::config_dir().to_string_lossy());
|
.replace(
|
||||||
|
"{prompt_utils_file}",
|
||||||
|
&Config::bash_prompt_utils_file().to_string_lossy(),
|
||||||
|
);
|
||||||
if binary_file.exists() {
|
if binary_file.exists() {
|
||||||
fs::remove_file(&binary_file)?;
|
fs::remove_file(&binary_file)?;
|
||||||
}
|
}
|
||||||
@@ -696,6 +768,11 @@ impl ToolCall {
|
|||||||
Some(agent) => self.extract_call_config_from_agent(config, agent)?,
|
Some(agent) => self.extract_call_config_from_agent(config, agent)?,
|
||||||
None => self.extract_call_config_from_config(config)?,
|
None => self.extract_call_config_from_config(config)?,
|
||||||
};
|
};
|
||||||
|
let agent_name = config
|
||||||
|
.read()
|
||||||
|
.agent
|
||||||
|
.as_ref()
|
||||||
|
.map(|agent| agent.name().to_owned());
|
||||||
|
|
||||||
let json_data = if self.arguments.is_object() {
|
let json_data = if self.arguments.is_object() {
|
||||||
self.arguments.clone()
|
self.arguments.clone()
|
||||||
@@ -754,7 +831,7 @@ impl ToolCall {
|
|||||||
let result = registry_arc.invoke(server, tool, arguments).await?;
|
let result = registry_arc.invoke(server, tool, arguments).await?;
|
||||||
serde_json::to_value(result)?
|
serde_json::to_value(result)?
|
||||||
}
|
}
|
||||||
_ => match run_llm_function(cmd_name, cmd_args, envs)? {
|
_ => match run_llm_function(cmd_name, cmd_args, envs, agent_name)? {
|
||||||
Some(contents) => serde_json::from_str(&contents)
|
Some(contents) => serde_json::from_str(&contents)
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or_else(|| json!({"output": contents})),
|
.unwrap_or_else(|| json!({"output": contents})),
|
||||||
@@ -812,15 +889,17 @@ pub fn run_llm_function(
|
|||||||
cmd_name: String,
|
cmd_name: String,
|
||||||
cmd_args: Vec<String>,
|
cmd_args: Vec<String>,
|
||||||
mut envs: HashMap<String, String>,
|
mut envs: HashMap<String, String>,
|
||||||
|
agent_name: Option<String>,
|
||||||
) -> Result<Option<String>> {
|
) -> Result<Option<String>> {
|
||||||
let mut bin_dirs: Vec<PathBuf> = vec![];
|
let mut bin_dirs: Vec<PathBuf> = vec![];
|
||||||
if cmd_args.len() > 1 {
|
if let Some(agent_name) = agent_name {
|
||||||
let dir = Config::agent_bin_dir(&cmd_name);
|
let dir = Config::agent_bin_dir(&agent_name);
|
||||||
if dir.exists() {
|
if dir.exists() {
|
||||||
bin_dirs.push(dir);
|
bin_dirs.push(dir);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
bin_dirs.push(Config::functions_bin_dir());
|
||||||
}
|
}
|
||||||
bin_dirs.push(Config::functions_bin_dir());
|
|
||||||
let current_path = env::var("PATH").context("No PATH environment variable")?;
|
let current_path = env::var("PATH").context("No PATH environment variable")?;
|
||||||
let prepend_path = bin_dirs
|
let prepend_path = bin_dirs
|
||||||
.iter()
|
.iter()
|
||||||
Reference in New Issue
Block a user