Compare commits
7 Commits
43fbe448cb
...
d45375a454
| Author | SHA1 | Date | |
|---|---|---|---|
|
d45375a454
|
|||
|
ed8327e9d6
|
|||
|
bc1800db4f
|
|||
|
dd8da58105
|
|||
|
6a0df70777
|
|||
|
5e550a67ce
|
|||
|
b781dd8dc6
|
@@ -243,7 +243,9 @@ instructions: |
|
|||||||
|
|
||||||
When you write or modify files yourself (rather than delegating to coder):
|
When you write or modify files yourself (rather than delegating to coder):
|
||||||
|
|
||||||
- **For writing files**, ALWAYS use `fs_write` (new file / full overwrite) or `fs_patch` (surgical edit). NEVER write files via `execute_command`. Do not use:
|
- **For editing an existing file**, prefer `fs_patch`. It's a surgical edit that preserves unchanged content. Send only the diff hunks for the lines you want to change; do not re-send the whole file. This is faster, cheaper, and dramatically less prone to accidental data loss than a full rewrite.
|
||||||
|
- **For writing a NEW file or doing a COMPLETE rewrite**, use `fs_write`. Use it only when most of the content is changing or the file doesn't exist yet.
|
||||||
|
- **NEVER write files via `execute_command`.** Do not use:
|
||||||
- `cat > file`, `cat >> file`, `tee`
|
- `cat > file`, `cat >> file`, `tee`
|
||||||
- `echo >`, `printf >`
|
- `echo >`, `printf >`
|
||||||
- Heredocs (`<<EOF`, `<<-EOF`, `<<'EOF'`)
|
- Heredocs (`<<EOF`, `<<-EOF`, `<<'EOF'`)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def main():
|
|||||||
agent_data = parse_raw_data(raw_data)
|
agent_data = parse_raw_data(raw_data)
|
||||||
|
|
||||||
root_dir = "{config_dir}"
|
root_dir = "{config_dir}"
|
||||||
setup_env(root_dir, agent_func)
|
setup_env(root_dir, agent_func, raw_data)
|
||||||
|
|
||||||
agent_tools_path = os.path.join(root_dir, "agents/{agent_name}/tools.py")
|
agent_tools_path = os.path.join(root_dir, "agents/{agent_name}/tools.py")
|
||||||
run(agent_tools_path, agent_func, agent_data)
|
run(agent_tools_path, agent_func, agent_data)
|
||||||
@@ -65,13 +65,14 @@ def parse_argv():
|
|||||||
return agent_func, agent_data
|
return agent_func, agent_data
|
||||||
|
|
||||||
|
|
||||||
def setup_env(root_dir, agent_func):
|
def setup_env(root_dir, agent_func, raw_data):
|
||||||
load_env(os.path.join(root_dir, ".env"))
|
load_env(os.path.join(root_dir, ".env"))
|
||||||
os.environ["LLM_ROOT_DIR"] = root_dir
|
os.environ["LLM_ROOT_DIR"] = root_dir
|
||||||
os.environ["LLM_AGENT_NAME"] = "{agent_name}"
|
os.environ["LLM_AGENT_NAME"] = "{agent_name}"
|
||||||
os.environ["LLM_AGENT_FUNC"] = agent_func
|
os.environ["LLM_AGENT_FUNC"] = agent_func
|
||||||
os.environ["LLM_AGENT_ROOT_DIR"] = os.path.join(root_dir, "agents", "{agent_name}")
|
os.environ["LLM_AGENT_ROOT_DIR"] = os.path.join(root_dir, "agents", "{agent_name}")
|
||||||
os.environ["LLM_AGENT_CACHE_DIR"] = os.path.join(root_dir, "cache", "{agent_name}")
|
os.environ["LLM_AGENT_CACHE_DIR"] = os.path.join(root_dir, "cache", "{agent_name}")
|
||||||
|
os.environ["LLM_AGENT_RAW_JSON"] = raw_data
|
||||||
|
|
||||||
|
|
||||||
def load_env(file_path):
|
def load_env(file_path):
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ setup_env() {
|
|||||||
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}"
|
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
|
||||||
|
export LLM_AGENT_RAW_JSON="$agent_data"
|
||||||
}
|
}
|
||||||
|
|
||||||
load_env() {
|
load_env() {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ async function main(): Promise<void> {
|
|||||||
const agentData = parseRawData(rawData);
|
const agentData = parseRawData(rawData);
|
||||||
|
|
||||||
const configDir = "{config_dir}";
|
const configDir = "{config_dir}";
|
||||||
setupEnv(configDir, agentFunc);
|
setupEnv(configDir, agentFunc, rawData);
|
||||||
|
|
||||||
const agentToolsPath = join(configDir, "agents", "{agent_name}", "tools.ts");
|
const agentToolsPath = join(configDir, "agents", "{agent_name}", "tools.ts");
|
||||||
await run(agentToolsPath, agentFunc, agentData);
|
await run(agentToolsPath, agentFunc, agentData);
|
||||||
@@ -48,13 +48,14 @@ function parseArgv(): { agentFunc: string; rawData: string } {
|
|||||||
return { agentFunc, rawData: agentData };
|
return { agentFunc, rawData: agentData };
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupEnv(configDir: string, agentFunc: string): void {
|
function setupEnv(configDir: string, agentFunc: string, rawData: string): void {
|
||||||
loadEnv(join(configDir, ".env"));
|
loadEnv(join(configDir, ".env"));
|
||||||
process.env["LLM_ROOT_DIR"] = configDir;
|
process.env["LLM_ROOT_DIR"] = configDir;
|
||||||
process.env["LLM_AGENT_NAME"] = "{agent_name}";
|
process.env["LLM_AGENT_NAME"] = "{agent_name}";
|
||||||
process.env["LLM_AGENT_FUNC"] = agentFunc;
|
process.env["LLM_AGENT_FUNC"] = agentFunc;
|
||||||
process.env["LLM_AGENT_ROOT_DIR"] = join(configDir, "agents", "{agent_name}");
|
process.env["LLM_AGENT_ROOT_DIR"] = join(configDir, "agents", "{agent_name}");
|
||||||
process.env["LLM_AGENT_CACHE_DIR"] = join(configDir, "cache", "{agent_name}");
|
process.env["LLM_AGENT_CACHE_DIR"] = join(configDir, "cache", "{agent_name}");
|
||||||
|
process.env["LLM_AGENT_RAW_JSON"] = rawData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadEnv(filePath: string): void {
|
function loadEnv(filePath: string): void {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def main():
|
|||||||
tool_data = parse_raw_data(raw_data)
|
tool_data = parse_raw_data(raw_data)
|
||||||
|
|
||||||
root_dir = "{root_dir}"
|
root_dir = "{root_dir}"
|
||||||
setup_env(root_dir)
|
setup_env(root_dir, raw_data)
|
||||||
|
|
||||||
tool_path = "{tool_path}.py"
|
tool_path = "{tool_path}.py"
|
||||||
run(tool_path, "run", tool_data)
|
run(tool_path, "run", tool_data)
|
||||||
@@ -65,11 +65,12 @@ def parse_argv():
|
|||||||
return tool_data
|
return tool_data
|
||||||
|
|
||||||
|
|
||||||
def setup_env(root_dir):
|
def setup_env(root_dir, raw_data):
|
||||||
load_env(os.path.join(root_dir, ".env"))
|
load_env(os.path.join(root_dir, ".env"))
|
||||||
os.environ["LLM_ROOT_DIR"] = root_dir
|
os.environ["LLM_ROOT_DIR"] = root_dir
|
||||||
os.environ["LLM_TOOL_NAME"] = "{function_name}"
|
os.environ["LLM_TOOL_NAME"] = "{function_name}"
|
||||||
os.environ["LLM_TOOL_CACHE_DIR"] = os.path.join(root_dir, "cache", "{function_name}")
|
os.environ["LLM_TOOL_CACHE_DIR"] = os.path.join(root_dir, "cache", "{function_name}")
|
||||||
|
os.environ["LLM_TOOL_RAW_JSON"] = raw_data
|
||||||
|
|
||||||
|
|
||||||
def load_env(file_path):
|
def load_env(file_path):
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ setup_env() {
|
|||||||
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}"
|
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
|
||||||
|
export LLM_TOOL_RAW_JSON="$tool_data"
|
||||||
}
|
}
|
||||||
|
|
||||||
load_env() {
|
load_env() {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ async function main(): Promise<void> {
|
|||||||
const toolData = parseRawData(rawData);
|
const toolData = parseRawData(rawData);
|
||||||
|
|
||||||
const rootDir = "{root_dir}";
|
const rootDir = "{root_dir}";
|
||||||
setupEnv(rootDir);
|
setupEnv(rootDir, rawData);
|
||||||
|
|
||||||
const toolPath = "{tool_path}.ts";
|
const toolPath = "{tool_path}.ts";
|
||||||
await run(toolPath, "run", toolData);
|
await run(toolPath, "run", toolData);
|
||||||
@@ -45,11 +45,12 @@ function parseArgv(): string {
|
|||||||
return toolData;
|
return toolData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupEnv(rootDir: string): void {
|
function setupEnv(rootDir: string, rawData: string): void {
|
||||||
loadEnv(join(rootDir, ".env"));
|
loadEnv(join(rootDir, ".env"));
|
||||||
process.env["LLM_ROOT_DIR"] = rootDir;
|
process.env["LLM_ROOT_DIR"] = rootDir;
|
||||||
process.env["LLM_TOOL_NAME"] = "{function_name}";
|
process.env["LLM_TOOL_NAME"] = "{function_name}";
|
||||||
process.env["LLM_TOOL_CACHE_DIR"] = join(rootDir, "cache", "{function_name}");
|
process.env["LLM_TOOL_CACHE_DIR"] = join(rootDir, "cache", "{function_name}");
|
||||||
|
process.env["LLM_TOOL_RAW_JSON"] = rawData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadEnv(filePath: string): void {
|
function loadEnv(filePath: string): void {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ set -e
|
|||||||
source "$LLM_PROMPT_UTILS_FILE"
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
argc_command="$(jq -r '.command' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
|
||||||
guard_operation
|
guard_operation
|
||||||
local script
|
local script
|
||||||
script="$(mktemp)"
|
script="$(mktemp)"
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ source "$LLM_PROMPT_UTILS_FILE"
|
|||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
argc_code="$(jq -r '.code' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
|
||||||
if ! grep -qi '^select' <<<"$argc_code"; then
|
if ! grep -qi '^select' <<<"$argc_code"; then
|
||||||
guard_operation ""
|
guard_operation ""
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# @describe Apply a patch to a file at the specified path.
|
# @describe Apply a unified-diff patch to a file at the specified path. Use this for editing an existing file. It's the
|
||||||
# This can be used to edit a file without having to rewrite the whole file.
|
# PREFERRED way to modify a file. Prefer this over fs_write whenever the file already exists: it sends less data,
|
||||||
|
# preserves unchanged content automatically, and is less prone to accidental data loss from full rewrites.
|
||||||
|
# Use fs_write only when you are creating a new file or doing a complete rewrite where most of the content changes.
|
||||||
|
|
||||||
# @option --path! The path of the file to apply the patch to
|
# @option --path! The path of the file to apply the patch to
|
||||||
# @option --contents! The patch to apply to the file
|
# @option --contents! The patch to apply to the file
|
||||||
@@ -14,6 +16,9 @@ source "$LLM_PROMPT_UTILS_FILE"
|
|||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
argc_contents="$(jq -r '.contents' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
argc_path="$(jq -r '.path' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
|
||||||
if [[ ! -f "$argc_path" ]]; then
|
if [[ ! -f "$argc_path" ]]; then
|
||||||
error "Unable to find the specified file: $argc_path"
|
error "Unable to find the specified file: $argc_path"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# @describe Write the full file contents to a file at the specified path.
|
# @describe Write the FULL file contents to a file at the specified path. Use this for NEW files or COMPLETE rewrites
|
||||||
|
# only. For editing an existing file, prefer fs_patch. It's a surgical edit that preserves unchanged content, requires
|
||||||
|
# sending less data, and is less prone to accidental data loss.
|
||||||
|
|
||||||
# @option --path! The path of the file to write to
|
# @option --path! The path of the file to write to
|
||||||
# @option --contents! The full contents to write to the file
|
# @option --contents! The full contents to write to the file
|
||||||
@@ -13,6 +15,9 @@ source "$LLM_PROMPT_UTILS_FILE"
|
|||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
argc_contents="$(jq -r '.contents' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
argc_path="$(jq -r '.path' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
|
||||||
if [[ -f "$argc_path" ]]; then
|
if [[ -f "$argc_path" ]]; then
|
||||||
printf "%s" "$argc_contents" | git diff --no-index "$argc_path" - || true
|
printf "%s" "$argc_contents" | git diff --no-index "$argc_path" - || true
|
||||||
guard_operation "Apply changes?"
|
guard_operation "Apply changes?"
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ set -e
|
|||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
main() {
|
main() {
|
||||||
|
argc_recipient="$(jq -r '.recipient' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
argc_subject="$(jq -r '.subject' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
argc_body="$(jq -r '.body' <<< "$LLM_TOOL_RAW_JSON")"
|
||||||
|
|
||||||
sender_name="${EMAIL_SENDER_NAME:-$(echo "$EMAIL_SMTP_USER" | awk -F'@' '{print $1}')}"
|
sender_name="${EMAIL_SENDER_NAME:-$(echo "$EMAIL_SMTP_USER" | awk -F'@' '{print $1}')}"
|
||||||
printf "%s\n" "From: $sender_name <$EMAIL_SMTP_USER>
|
printf "%s\n" "From: $sender_name <$EMAIL_SMTP_USER>
|
||||||
To: $argc_recipient
|
To: $argc_recipient
|
||||||
|
|||||||
+20
-3
@@ -82,7 +82,14 @@ vault_password_file: null # Path to a file containing the password for th
|
|||||||
function_calling_support: true # Enables or disables function calling (Globally).
|
function_calling_support: true # Enables or disables function calling (Globally).
|
||||||
mapping_tools: # Alias for a tool or toolset
|
mapping_tools: # Alias for a tool or toolset
|
||||||
fs: 'fs_cat,fs_ls,fs_mkdir,fs_rm,fs_write,fs_read,fs_glob,fs_grep'
|
fs: 'fs_cat,fs_ls,fs_mkdir,fs_rm,fs_write,fs_read,fs_glob,fs_grep'
|
||||||
enabled_tools: null # Which tools to enable by default. (e.g. 'fs,web_search_coyote')
|
enabled_tools: null # Which tools to enable by default.
|
||||||
|
# Accepts either a YAML list or a comma-separated string. Use 'all' to enable everything.
|
||||||
|
# Example (list form):
|
||||||
|
# enabled_tools:
|
||||||
|
# - fs
|
||||||
|
# - web_search_coyote
|
||||||
|
# Example (comma-separated form):
|
||||||
|
# enabled_tools: fs,web_search_coyote
|
||||||
visible_tools: # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools')
|
visible_tools: # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools')
|
||||||
# - demo_py.py
|
# - demo_py.py
|
||||||
# - demo_sh.sh
|
# - demo_sh.sh
|
||||||
@@ -118,7 +125,14 @@ visible_tools: # Which tools are visible to be compiled (and a
|
|||||||
mcp_server_support: true # Enables or disables MCP servers (globally).
|
mcp_server_support: true # Enables or disables MCP servers (globally).
|
||||||
mapping_mcp_servers: # Alias for an MCP server or set of servers
|
mapping_mcp_servers: # Alias for an MCP server or set of servers
|
||||||
git: github,gitmcp
|
git: github,gitmcp
|
||||||
enabled_mcp_servers: null # Which MCP servers to enable by default (e.g. 'github,slack,ddg-search')
|
enabled_mcp_servers: null # Which MCP servers to enable by default.
|
||||||
|
# Accepts either a YAML list or a comma-separated string. Use 'all' to enable everything.
|
||||||
|
# Example (list form):
|
||||||
|
# enabled_mcp_servers:
|
||||||
|
# - github
|
||||||
|
# - slack
|
||||||
|
# Example (comma-separated form):
|
||||||
|
# enabled_mcp_servers: github,slack,ddg-search
|
||||||
|
|
||||||
# ---- Skills ----
|
# ---- Skills ----
|
||||||
# Skills are modular knowledge or capability packs the LLM can load and unload mid-conversation.
|
# Skills are modular knowledge or capability packs the LLM can load and unload mid-conversation.
|
||||||
@@ -131,10 +145,13 @@ visible_skills: # The universe of skills allowed to be enabled
|
|||||||
- frontend-ui-ux
|
- frontend-ui-ux
|
||||||
- git-master
|
- git-master
|
||||||
enabled_skills: null # Which skills are available by default (no role/agent/session active). null = all visible.
|
enabled_skills: null # Which skills are available by default (no role/agent/session active). null = all visible.
|
||||||
# Example: only expose two skills in the bare REPL.
|
# Accepts either a YAML list or a comma-separated string.
|
||||||
|
# Example (list form):
|
||||||
# enabled_skills:
|
# enabled_skills:
|
||||||
# - git-master
|
# - git-master
|
||||||
# - ai-slop-remover
|
# - ai-slop-remover
|
||||||
|
# Example (comma-separated form):
|
||||||
|
# enabled_skills: git-master,ai-slop-remover
|
||||||
|
|
||||||
# ---- Auto-Continue (Todo System) ----
|
# ---- Auto-Continue (Todo System) ----
|
||||||
# The auto-continue system provides built-in task tracking for improved reliability.
|
# The auto-continue system provides built-in task tracking for improved reliability.
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ name: <role-name> # The name of the role
|
|||||||
model: openai:gpt-4o # The model to use for this role
|
model: openai:gpt-4o # The model to use for this role
|
||||||
temperature: 0.2 # The temperature to use for this role when querying the model
|
temperature: 0.2 # The temperature to use for this role when querying the model
|
||||||
top_p: 0 # The top_p to use for this role when querying the model
|
top_p: 0 # The top_p to use for this role when querying the model
|
||||||
enabled_tools: fs_ls,fs_cat # A comma-separated list of tools to enable for this role
|
enabled_tools: # Tools to enable for this role. Accepts a YAML list (preferred)
|
||||||
enabled_mcp_servers: github,gitmcp # A comma-separated list of MCP servers to enable for this role
|
- fs_ls # or a comma-separated string (e.g. `enabled_tools: fs_ls,fs_cat`).
|
||||||
|
- fs_cat # Use `all` to enable every visible tool.
|
||||||
|
enabled_mcp_servers: # MCP servers to enable for this role. Accepts a YAML list (preferred)
|
||||||
|
- github # or a comma-separated string (e.g. `enabled_mcp_servers: github,gitmcp`).
|
||||||
|
- gitmcp # Use `all` to enable every configured MCP server.
|
||||||
skills_enabled: true # Master switch for skills in this role (default: inherit from global).
|
skills_enabled: true # Master switch for skills in this role (default: inherit from global).
|
||||||
# Skills also require `function_calling_support: true` in the global config.
|
# Skills also require `function_calling_support: true` in the global config.
|
||||||
enabled_skills: git-master,ai-slop-remover # Comma-separated list of skills available when this role is active.
|
enabled_skills: # Skills available when this role is active. Accepts a YAML list (preferred)
|
||||||
# Must be a subset of global `visible_skills`. Omit to inherit the global default.
|
- git-master # or a comma-separated string (e.g. `enabled_skills: git-master,ai-slop-remover`).
|
||||||
|
- ai-slop-remover # Must be a subset of global `visible_skills`. Omit to inherit the global default.
|
||||||
prompt: null # A custom prompt to use for this role that will immediately query
|
prompt: null # A custom prompt to use for this role that will immediately query
|
||||||
# the model for output instead of using the instructions below
|
# the model for output instead of using the instructions below
|
||||||
# Auto-Continue (Todo System)
|
# Auto-Continue (Todo System)
|
||||||
|
|||||||
+9
-11
@@ -548,12 +548,12 @@ impl RoleLike for Agent {
|
|||||||
self.config.top_p
|
self.config.top_p
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_tools(&self) -> Option<String> {
|
fn enabled_tools(&self) -> Option<Vec<String>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_mcp_servers(&self) -> Option<String> {
|
fn enabled_mcp_servers(&self) -> Option<Vec<String>> {
|
||||||
self.config.mcp_servers.clone().join(",").into()
|
Some(self.config.mcp_servers.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_model(&mut self, model: Model) {
|
fn set_model(&mut self, model: Model) {
|
||||||
@@ -569,15 +569,14 @@ impl RoleLike for Agent {
|
|||||||
self.config.top_p = value;
|
self.config.top_p = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_enabled_tools(&mut self, value: Option<String>) {
|
fn set_enabled_tools(&mut self, value: Option<Vec<String>>) {
|
||||||
match value {
|
match value {
|
||||||
Some(tools) => {
|
Some(tools) => {
|
||||||
let tools = tools
|
self.config.global_tools = tools
|
||||||
.split(',')
|
.into_iter()
|
||||||
.map(|v| v.trim().to_string())
|
.map(|v| v.trim().to_string())
|
||||||
.filter(|v| !v.is_empty())
|
.filter(|v| !v.is_empty())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.config.global_tools = tools;
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.config.global_tools.clear();
|
self.config.global_tools.clear();
|
||||||
@@ -585,15 +584,14 @@ impl RoleLike for Agent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_enabled_mcp_servers(&mut self, value: Option<String>) {
|
fn set_enabled_mcp_servers(&mut self, value: Option<Vec<String>>) {
|
||||||
match value {
|
match value {
|
||||||
Some(servers) => {
|
Some(servers) => {
|
||||||
let servers = servers
|
self.config.mcp_servers = servers
|
||||||
.split(',')
|
.into_iter()
|
||||||
.map(|v| v.trim().to_string())
|
.map(|v| v.trim().to_string())
|
||||||
.filter(|v| !v.is_empty())
|
.filter(|v| !v.is_empty())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.config.mcp_servers = servers;
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.config.mcp_servers.clear();
|
self.config.mcp_servers.clear();
|
||||||
|
|||||||
@@ -34,16 +34,19 @@ pub struct AppConfig {
|
|||||||
|
|
||||||
pub function_calling_support: bool,
|
pub function_calling_support: bool,
|
||||||
pub mapping_tools: IndexMap<String, String>,
|
pub mapping_tools: IndexMap<String, String>,
|
||||||
pub enabled_tools: Option<String>,
|
#[serde(default, deserialize_with = "super::deserialize_csv_or_vec")]
|
||||||
|
pub enabled_tools: Option<Vec<String>>,
|
||||||
pub visible_tools: Option<Vec<String>>,
|
pub visible_tools: Option<Vec<String>>,
|
||||||
|
|
||||||
pub skills_enabled: bool,
|
pub skills_enabled: bool,
|
||||||
pub enabled_skills: Option<String>,
|
#[serde(default, deserialize_with = "super::deserialize_csv_or_vec")]
|
||||||
|
pub enabled_skills: Option<Vec<String>>,
|
||||||
pub visible_skills: Option<Vec<String>>,
|
pub visible_skills: Option<Vec<String>>,
|
||||||
|
|
||||||
pub mcp_server_support: bool,
|
pub mcp_server_support: bool,
|
||||||
pub mapping_mcp_servers: IndexMap<String, String>,
|
pub mapping_mcp_servers: IndexMap<String, String>,
|
||||||
pub enabled_mcp_servers: Option<String>,
|
#[serde(default, deserialize_with = "super::deserialize_csv_or_vec")]
|
||||||
|
pub enabled_mcp_servers: Option<Vec<String>>,
|
||||||
|
|
||||||
pub auto_continue: bool,
|
pub auto_continue: bool,
|
||||||
pub max_auto_continues: usize,
|
pub max_auto_continues: usize,
|
||||||
@@ -392,7 +395,7 @@ impl AppConfig {
|
|||||||
self.mapping_tools = v;
|
self.mapping_tools = v;
|
||||||
}
|
}
|
||||||
if let Some(v) = super::read_env_value::<String>(&get_env_name("enabled_tools")) {
|
if let Some(v) = super::read_env_value::<String>(&get_env_name("enabled_tools")) {
|
||||||
self.enabled_tools = v;
|
self.enabled_tools = v.map(|raw| super::csv_to_vec(&raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Some(v)) = super::read_env_bool(&get_env_name("skills_enabled")) {
|
if let Some(Some(v)) = super::read_env_bool(&get_env_name("skills_enabled")) {
|
||||||
@@ -400,7 +403,7 @@ impl AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(v) = super::read_env_value::<String>(&get_env_name("enabled_skills")) {
|
if let Some(v) = super::read_env_value::<String>(&get_env_name("enabled_skills")) {
|
||||||
self.enabled_skills = v;
|
self.enabled_skills = v.map(|raw| super::csv_to_vec(&raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Some(v)) = super::read_env_bool(&get_env_name("mcp_server_support")) {
|
if let Some(Some(v)) = super::read_env_bool(&get_env_name("mcp_server_support")) {
|
||||||
@@ -412,7 +415,7 @@ impl AppConfig {
|
|||||||
self.mapping_mcp_servers = v;
|
self.mapping_mcp_servers = v;
|
||||||
}
|
}
|
||||||
if let Some(v) = super::read_env_value::<String>(&get_env_name("enabled_mcp_servers")) {
|
if let Some(v) = super::read_env_value::<String>(&get_env_name("enabled_mcp_servers")) {
|
||||||
self.enabled_mcp_servers = v;
|
self.enabled_mcp_servers = v.map(|raw| super::csv_to_vec(&raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(v) = super::read_env_value::<String>(&get_env_name("repl_prelude")) {
|
if let Some(v) = super::read_env_value::<String>(&get_env_name("repl_prelude")) {
|
||||||
@@ -514,12 +517,12 @@ impl AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn set_enabled_tools_default(&mut self, value: Option<String>) {
|
pub fn set_enabled_tools_default(&mut self, value: Option<Vec<String>>) {
|
||||||
self.enabled_tools = value;
|
self.enabled_tools = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn set_enabled_mcp_servers_default(&mut self, value: Option<String>) {
|
pub fn set_enabled_mcp_servers_default(&mut self, value: Option<Vec<String>>) {
|
||||||
self.enabled_mcp_servers = value;
|
self.enabled_mcp_servers = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ pub async fn macro_execute(
|
|||||||
let mut app_config = (*ctx.app.config).clone();
|
let mut app_config = (*ctx.app.config).clone();
|
||||||
app_config.temperature = role.temperature();
|
app_config.temperature = role.temperature();
|
||||||
app_config.top_p = role.top_p();
|
app_config.top_p = role.top_p();
|
||||||
app_config.enabled_tools = role.enabled_tools().clone();
|
app_config.enabled_tools = role.enabled_tools();
|
||||||
app_config.enabled_mcp_servers = role.enabled_mcp_servers().clone();
|
app_config.enabled_mcp_servers = role.enabled_mcp_servers();
|
||||||
|
|
||||||
let mut app_state = (*ctx.app).clone();
|
let mut app_state = (*ctx.app).clone();
|
||||||
app_state.config = Arc::new(app_config);
|
app_state.config = Arc::new(app_config);
|
||||||
|
|||||||
+72
-3
@@ -196,16 +196,19 @@ pub struct Config {
|
|||||||
|
|
||||||
pub function_calling_support: bool,
|
pub function_calling_support: bool,
|
||||||
pub mapping_tools: IndexMap<String, String>,
|
pub mapping_tools: IndexMap<String, String>,
|
||||||
pub enabled_tools: Option<String>,
|
#[serde(default, deserialize_with = "deserialize_csv_or_vec")]
|
||||||
|
pub enabled_tools: Option<Vec<String>>,
|
||||||
pub visible_tools: Option<Vec<String>>,
|
pub visible_tools: Option<Vec<String>>,
|
||||||
|
|
||||||
pub skills_enabled: bool,
|
pub skills_enabled: bool,
|
||||||
pub enabled_skills: Option<String>,
|
#[serde(default, deserialize_with = "deserialize_csv_or_vec")]
|
||||||
|
pub enabled_skills: Option<Vec<String>>,
|
||||||
pub visible_skills: Option<Vec<String>>,
|
pub visible_skills: Option<Vec<String>>,
|
||||||
|
|
||||||
pub mcp_server_support: bool,
|
pub mcp_server_support: bool,
|
||||||
pub mapping_mcp_servers: IndexMap<String, String>,
|
pub mapping_mcp_servers: IndexMap<String, String>,
|
||||||
pub enabled_mcp_servers: Option<String>,
|
#[serde(default, deserialize_with = "deserialize_csv_or_vec")]
|
||||||
|
pub enabled_mcp_servers: Option<Vec<String>>,
|
||||||
|
|
||||||
pub auto_continue: bool,
|
pub auto_continue: bool,
|
||||||
pub max_auto_continues: usize,
|
pub max_auto_continues: usize,
|
||||||
@@ -783,6 +786,72 @@ where
|
|||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn csv_to_vec(raw: &str) -> Vec<String> {
|
||||||
|
raw.split(',')
|
||||||
|
.map(|t| t.trim().to_string())
|
||||||
|
.filter(|t| !t.is_empty())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn deserialize_csv_or_vec<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<Option<Vec<String>>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use serde::de::{self, SeqAccess, Visitor};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
struct CsvOrVec;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for CsvOrVec {
|
||||||
|
type Value = Option<Vec<String>>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a comma-separated string, a list of strings, or null")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, value: &str) -> std::result::Result<Self::Value, E> {
|
||||||
|
Ok(Some(csv_to_vec(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E: de::Error>(self, value: String) -> std::result::Result<Self::Value, E> {
|
||||||
|
Ok(Some(csv_to_vec(&value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_none<E: de::Error>(self) -> std::result::Result<Self::Value, E> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_some<D2: serde::Deserializer<'de>>(
|
||||||
|
self,
|
||||||
|
deserializer: D2,
|
||||||
|
) -> std::result::Result<Self::Value, D2::Error> {
|
||||||
|
deserializer.deserialize_any(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_unit<E: de::Error>(self) -> std::result::Result<Self::Value, E> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A: SeqAccess<'de>>(
|
||||||
|
self,
|
||||||
|
mut seq: A,
|
||||||
|
) -> std::result::Result<Self::Value, A::Error> {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
while let Some(item) = seq.next_element::<String>()? {
|
||||||
|
let trimmed = item.trim().to_string();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
vec.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(vec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_option(CsvOrVec)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_env_bool(key: &str) -> Option<Option<bool>> {
|
fn read_env_bool(key: &str) -> Option<Option<bool>> {
|
||||||
let value = env::var(key).ok()?;
|
let value = env::var(key).ok()?;
|
||||||
Some(parse_bool(&value))
|
Some(parse_bool(&value))
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_enabled_tools_on_role_like(&mut self, value: Option<String>) -> bool {
|
pub fn set_enabled_tools_on_role_like(&mut self, value: Option<Vec<String>>) -> bool {
|
||||||
match self.role_like_mut() {
|
match self.role_like_mut() {
|
||||||
Some(role_like) => {
|
Some(role_like) => {
|
||||||
role_like.set_enabled_tools(value);
|
role_like.set_enabled_tools(value);
|
||||||
@@ -710,7 +710,7 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_enabled_mcp_servers_on_role_like(&mut self, value: Option<String>) -> bool {
|
pub fn set_enabled_mcp_servers_on_role_like(&mut self, value: Option<Vec<String>>) -> bool {
|
||||||
match self.role_like_mut() {
|
match self.role_like_mut() {
|
||||||
Some(role_like) => {
|
Some(role_like) => {
|
||||||
role_like.set_enabled_mcp_servers(value);
|
role_like.set_enabled_mcp_servers(value);
|
||||||
@@ -854,11 +854,11 @@ impl RequestContext {
|
|||||||
("top_p", super::format_option_value(&role.top_p())),
|
("top_p", super::format_option_value(&role.top_p())),
|
||||||
(
|
(
|
||||||
"enabled_tools",
|
"enabled_tools",
|
||||||
super::format_option_value(&role.enabled_tools()),
|
super::format_option_value(&role.enabled_tools().map(|v| v.join(","))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"enabled_mcp_servers",
|
"enabled_mcp_servers",
|
||||||
super::format_option_value(&role.enabled_mcp_servers()),
|
super::format_option_value(&role.enabled_mcp_servers().map(|v| v.join(","))),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"max_output_tokens",
|
"max_output_tokens",
|
||||||
@@ -1148,10 +1148,10 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut tool_names: HashSet<String> = Default::default();
|
let mut tool_names: HashSet<String> = Default::default();
|
||||||
if enabled_tools == "all" {
|
if enabled_tools.iter().any(|s| s.trim() == "all") {
|
||||||
tool_names.extend(declaration_names);
|
tool_names.extend(declaration_names);
|
||||||
} else {
|
} else {
|
||||||
for item in enabled_tools.split(',') {
|
for item in enabled_tools.iter() {
|
||||||
let item = item.trim();
|
let item = item.trim();
|
||||||
if item.is_empty() {
|
if item.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@@ -1279,10 +1279,10 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut server_names: HashSet<String> = Default::default();
|
let mut server_names: HashSet<String> = Default::default();
|
||||||
if enabled_mcp_servers == "all" {
|
if enabled_mcp_servers.iter().any(|s| s.trim() == "all") {
|
||||||
server_names.extend(mcp_declaration_names);
|
server_names.extend(mcp_declaration_names);
|
||||||
} else {
|
} else {
|
||||||
for item in enabled_mcp_servers.split(',') {
|
for item in enabled_mcp_servers.iter() {
|
||||||
let item = item.trim();
|
let item = item.trim();
|
||||||
if item.is_empty() {
|
if item.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@@ -1714,14 +1714,29 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"enabled_tools" => {
|
"enabled_tools" => {
|
||||||
let value = super::parse_value(value)?;
|
let raw: Option<String> = super::parse_value(value)?;
|
||||||
if !self.set_enabled_tools_on_role_like(value.clone()) {
|
let parsed: Option<Vec<String>> = raw.map(|s| super::csv_to_vec(&s));
|
||||||
self.update_app_config(|app| app.enabled_tools = value);
|
if !self.set_enabled_tools_on_role_like(parsed.clone()) {
|
||||||
|
self.update_app_config(|app| app.enabled_tools = parsed.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"enabled_skills" => {
|
||||||
|
let raw: Option<String> = super::parse_value(value)?;
|
||||||
|
let parsed: Option<Vec<String>> = raw.map(|s| super::csv_to_vec(&s));
|
||||||
|
self.update_app_config(|app| app.enabled_skills = parsed.clone());
|
||||||
|
}
|
||||||
|
"skills_enabled" => {
|
||||||
|
let value: Option<bool> = super::parse_value(value)?;
|
||||||
|
if let Some(session) = self.session.as_mut() {
|
||||||
|
session.set_skills_enabled(value);
|
||||||
|
} else {
|
||||||
|
self.update_app_config(|app| app.skills_enabled = value.unwrap_or(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"enabled_mcp_servers" => {
|
"enabled_mcp_servers" => {
|
||||||
let value: Option<String> = super::parse_value(value)?;
|
let raw: Option<String> = super::parse_value(value)?;
|
||||||
if let Some(servers) = value.as_ref() {
|
let parsed: Option<Vec<String>> = raw.map(|s| super::csv_to_vec(&s));
|
||||||
|
if let Some(servers) = parsed.as_ref() {
|
||||||
let Some(mcp_config) = &self.app.mcp_config else {
|
let Some(mcp_config) = &self.app.mcp_config else {
|
||||||
bail!(
|
bail!(
|
||||||
"No MCP servers are configured. Please configure MCP servers first before setting 'enabled_mcp_servers'."
|
"No MCP servers are configured. Please configure MCP servers first before setting 'enabled_mcp_servers'."
|
||||||
@@ -1733,7 +1748,7 @@ impl RequestContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !servers.split(',').all(|s| {
|
if !servers.iter().all(|s| {
|
||||||
let server = s.trim();
|
let server = s.trim();
|
||||||
server == "all" || mcp_config.mcp_servers.contains_key(server)
|
server == "all" || mcp_config.mcp_servers.contains_key(server)
|
||||||
}) {
|
}) {
|
||||||
@@ -1742,8 +1757,8 @@ impl RequestContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !self.set_enabled_mcp_servers_on_role_like(value.clone()) {
|
if !self.set_enabled_mcp_servers_on_role_like(parsed.clone()) {
|
||||||
self.update_app_config(|app| app.enabled_mcp_servers = value.clone());
|
self.update_app_config(|app| app.enabled_mcp_servers = parsed.clone());
|
||||||
}
|
}
|
||||||
if self.app.config.mcp_server_support {
|
if self.app.config.mcp_server_support {
|
||||||
let app = Arc::clone(&self.app.config);
|
let app = Arc::clone(&self.app.config);
|
||||||
@@ -1965,6 +1980,7 @@ impl RequestContext {
|
|||||||
"dry_run",
|
"dry_run",
|
||||||
"function_calling_support",
|
"function_calling_support",
|
||||||
"mcp_server_support",
|
"mcp_server_support",
|
||||||
|
"skills_enabled",
|
||||||
"stream",
|
"stream",
|
||||||
"save",
|
"save",
|
||||||
"highlight",
|
"highlight",
|
||||||
@@ -2063,6 +2079,14 @@ impl RequestContext {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
"mcp_server_support" => super::complete_bool(app.mcp_server_support),
|
"mcp_server_support" => super::complete_bool(app.mcp_server_support),
|
||||||
|
"skills_enabled" => {
|
||||||
|
let current = if let Some(session) = &self.session {
|
||||||
|
session.skills_enabled()
|
||||||
|
} else {
|
||||||
|
Some(app.skills_enabled)
|
||||||
|
};
|
||||||
|
super::complete_option_bool(current)
|
||||||
|
}
|
||||||
"enabled_mcp_servers" => {
|
"enabled_mcp_servers" => {
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
let mut ignores = HashSet::new();
|
let mut ignores = HashSet::new();
|
||||||
@@ -2141,7 +2165,7 @@ impl RequestContext {
|
|||||||
async fn rebuild_tool_scope(
|
async fn rebuild_tool_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
app: &AppConfig,
|
app: &AppConfig,
|
||||||
enabled_mcp_servers: Option<String>,
|
enabled_mcp_servers: Option<Vec<String>>,
|
||||||
abort_signal: AbortSignal,
|
abort_signal: AbortSignal,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let policy = SkillPolicy::effective(
|
let policy = SkillPolicy::effective(
|
||||||
@@ -2153,21 +2177,23 @@ impl RequestContext {
|
|||||||
|
|
||||||
let enabled_mcp_servers = if policy.skills_enabled && app.mcp_server_support {
|
let enabled_mcp_servers = if policy.skills_enabled && app.mcp_server_support {
|
||||||
let skill_mcps = self.skill_registry.loaded_mcp_servers();
|
let skill_mcps = self.skill_registry.loaded_mcp_servers();
|
||||||
match (enabled_mcp_servers.as_deref(), skill_mcps.is_empty()) {
|
let has_all = enabled_mcp_servers
|
||||||
(Some("all"), _) | (_, true) => enabled_mcp_servers,
|
.as_ref()
|
||||||
(base, false) => {
|
.map(|v| v.iter().any(|s| s.trim() == "all"))
|
||||||
|
.unwrap_or(false);
|
||||||
|
if has_all || skill_mcps.is_empty() {
|
||||||
|
enabled_mcp_servers
|
||||||
|
} else {
|
||||||
let mut merged: BTreeSet<String> = skill_mcps;
|
let mut merged: BTreeSet<String> = skill_mcps;
|
||||||
if let Some(s) = base {
|
if let Some(servers) = &enabled_mcp_servers {
|
||||||
for token in s.split(',') {
|
for token in servers {
|
||||||
let t = token.trim();
|
let t = token.trim();
|
||||||
if !t.is_empty() {
|
if !t.is_empty() {
|
||||||
merged.insert(t.to_string());
|
merged.insert(t.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(merged.into_iter().collect())
|
||||||
Some(merged.into_iter().collect::<Vec<_>>().join(","))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
enabled_mcp_servers
|
enabled_mcp_servers
|
||||||
@@ -2179,12 +2205,12 @@ impl RequestContext {
|
|||||||
&& let Some(mcp_config) = &self.app.mcp_config
|
&& let Some(mcp_config) = &self.app.mcp_config
|
||||||
{
|
{
|
||||||
let server_ids: Vec<String> = match &enabled_mcp_servers {
|
let server_ids: Vec<String> = match &enabled_mcp_servers {
|
||||||
Some(servers) if servers == "all" => {
|
Some(servers) if servers.iter().any(|s| s.trim() == "all") => {
|
||||||
mcp_config.mcp_servers.keys().cloned().collect()
|
mcp_config.mcp_servers.keys().cloned().collect()
|
||||||
}
|
}
|
||||||
Some(servers) => {
|
Some(servers) => {
|
||||||
let mut ids = Vec::new();
|
let mut ids = Vec::new();
|
||||||
for item in servers.split(',').map(|s| s.trim()) {
|
for item in servers.iter().map(|s| s.trim()) {
|
||||||
if mcp_config.mcp_servers.contains_key(item) {
|
if mcp_config.mcp_servers.contains_key(item) {
|
||||||
ids.push(item.to_string());
|
ids.push(item.to_string());
|
||||||
} else if let Some(mapped) = app.mapping_mcp_servers.get(item) {
|
} else if let Some(mapped) = app.mapping_mcp_servers.get(item) {
|
||||||
@@ -2263,7 +2289,7 @@ impl RequestContext {
|
|||||||
if names.is_empty() {
|
if names.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(names.join(","))
|
Some(names.to_vec())
|
||||||
}
|
}
|
||||||
} else if let Some(role) = &self.role {
|
} else if let Some(role) = &self.role {
|
||||||
role.enabled_mcp_servers()
|
role.enabled_mcp_servers()
|
||||||
@@ -2423,7 +2449,7 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mcp_servers = if app.mcp_server_support {
|
let mcp_servers = if app.mcp_server_support {
|
||||||
(!agent.mcp_server_names().is_empty()).then(|| agent.mcp_server_names().join(","))
|
(!agent.mcp_server_names().is_empty()).then(|| agent.mcp_server_names().to_vec())
|
||||||
} else {
|
} else {
|
||||||
if !agent.mcp_server_names().is_empty() {
|
if !agent.mcp_server_names().is_empty() {
|
||||||
bail!(
|
bail!(
|
||||||
@@ -2599,7 +2625,7 @@ impl RequestContext {
|
|||||||
let skill = Skill::load(name)?;
|
let skill = Skill::load(name)?;
|
||||||
let needs_mcps = skill
|
let needs_mcps = skill
|
||||||
.enabled_mcp_servers()
|
.enabled_mcp_servers()
|
||||||
.map(|s| !s.trim().is_empty())
|
.map(|v| !v.is_empty())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if needs_mcps && !self.app.config.mcp_server_support {
|
if needs_mcps && !self.app.config.mcp_server_support {
|
||||||
@@ -2706,13 +2732,13 @@ impl RequestContext {
|
|||||||
&self,
|
&self,
|
||||||
app: &AppConfig,
|
app: &AppConfig,
|
||||||
start_mcp_servers: bool,
|
start_mcp_servers: bool,
|
||||||
) -> Option<String> {
|
) -> Option<Vec<String>> {
|
||||||
if !start_mcp_servers || !app.mcp_server_support {
|
if !start_mcp_servers || !app.mcp_server_support {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Some(agent) = self.agent.as_ref() {
|
if let Some(agent) = self.agent.as_ref() {
|
||||||
return (!agent.mcp_server_names().is_empty())
|
return (!agent.mcp_server_names().is_empty())
|
||||||
.then(|| agent.mcp_server_names().join(","));
|
.then(|| agent.mcp_server_names().to_vec());
|
||||||
}
|
}
|
||||||
if let Some(session) = self.session.as_ref() {
|
if let Some(session) = self.session.as_ref() {
|
||||||
return session.enabled_mcp_servers();
|
return session.enabled_mcp_servers();
|
||||||
@@ -3205,7 +3231,7 @@ mod tests {
|
|||||||
let app = ctx.app.config.clone();
|
let app = ctx.app.config.clone();
|
||||||
let abort = utils::create_abort_signal();
|
let abort = utils::create_abort_signal();
|
||||||
|
|
||||||
run_async(ctx.rebuild_tool_scope(&app, Some("all".to_string()), abort)).unwrap();
|
run_async(ctx.rebuild_tool_scope(&app, Some(vec!["all".to_string()]), abort)).unwrap();
|
||||||
|
|
||||||
assert!(ctx.tool_scope.mcp_runtime.is_empty());
|
assert!(ctx.tool_scope.mcp_runtime.is_empty());
|
||||||
}
|
}
|
||||||
@@ -3233,7 +3259,7 @@ mod tests {
|
|||||||
let app = ctx.app.config.clone();
|
let app = ctx.app.config.clone();
|
||||||
let abort = utils::create_abort_signal();
|
let abort = utils::create_abort_signal();
|
||||||
|
|
||||||
run_async(ctx.rebuild_tool_scope(&app, Some("all".to_string()), abort)).unwrap();
|
run_async(ctx.rebuild_tool_scope(&app, Some(vec!["all".to_string()]), abort)).unwrap();
|
||||||
|
|
||||||
assert!(ctx.tool_scope.mcp_runtime.is_empty());
|
assert!(ctx.tool_scope.mcp_runtime.is_empty());
|
||||||
}
|
}
|
||||||
@@ -3341,7 +3367,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let ctx = RequestContext::new(app_state, WorkingMode::Cmd);
|
let ctx = RequestContext::new(app_state, WorkingMode::Cmd);
|
||||||
let mut role = Role::new("r", "p");
|
let mut role = Role::new("r", "p");
|
||||||
role.set_enabled_tools(Some("all".to_string()));
|
role.set_enabled_tools(Some(vec!["all".to_string()]));
|
||||||
assert!(ctx.select_functions(&role).is_none());
|
assert!(ctx.select_functions(&role).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3352,7 +3378,7 @@ mod tests {
|
|||||||
ctx.tool_scope.functions.append_user_interaction_functions();
|
ctx.tool_scope.functions.append_user_interaction_functions();
|
||||||
|
|
||||||
let mut role = Role::new("r", "p");
|
let mut role = Role::new("r", "p");
|
||||||
role.set_enabled_tools(Some("all".to_string()));
|
role.set_enabled_tools(Some(vec!["all".to_string()]));
|
||||||
|
|
||||||
let fns = ctx.select_functions(&role).unwrap();
|
let fns = ctx.select_functions(&role).unwrap();
|
||||||
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
||||||
@@ -3366,7 +3392,10 @@ mod tests {
|
|||||||
ctx.tool_scope.functions.append_todo_functions();
|
ctx.tool_scope.functions.append_todo_functions();
|
||||||
|
|
||||||
let mut role = Role::new("r", "p");
|
let mut role = Role::new("r", "p");
|
||||||
role.set_enabled_tools(Some("todo__init, todo__add".to_string()));
|
role.set_enabled_tools(Some(vec![
|
||||||
|
"todo__init".to_string(),
|
||||||
|
"todo__add".to_string(),
|
||||||
|
]));
|
||||||
|
|
||||||
let fns = ctx.select_functions(&role).unwrap();
|
let fns = ctx.select_functions(&role).unwrap();
|
||||||
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
||||||
@@ -3395,7 +3424,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let ctx = RequestContext::new(app_state, WorkingMode::Cmd);
|
let ctx = RequestContext::new(app_state, WorkingMode::Cmd);
|
||||||
let mut role = Role::new("r", "p");
|
let mut role = Role::new("r", "p");
|
||||||
role.set_enabled_mcp_servers(Some("all".to_string()));
|
role.set_enabled_mcp_servers(Some(vec!["all".to_string()]));
|
||||||
let result = ctx.select_enabled_mcp_servers(&role);
|
let result = ctx.select_enabled_mcp_servers(&role);
|
||||||
assert!(result.is_empty());
|
assert!(result.is_empty());
|
||||||
}
|
}
|
||||||
@@ -3408,7 +3437,7 @@ mod tests {
|
|||||||
.append_mcp_meta_functions(vec!["github".into(), "slack".into()]);
|
.append_mcp_meta_functions(vec!["github".into(), "slack".into()]);
|
||||||
|
|
||||||
let mut role = Role::new("r", "p");
|
let mut role = Role::new("r", "p");
|
||||||
role.set_enabled_mcp_servers(Some("all".to_string()));
|
role.set_enabled_mcp_servers(Some(vec!["all".to_string()]));
|
||||||
|
|
||||||
let fns = ctx.select_enabled_mcp_servers(&role);
|
let fns = ctx.select_enabled_mcp_servers(&role);
|
||||||
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
||||||
@@ -3425,7 +3454,7 @@ mod tests {
|
|||||||
.append_mcp_meta_functions(vec!["github".into(), "slack".into()]);
|
.append_mcp_meta_functions(vec!["github".into(), "slack".into()]);
|
||||||
|
|
||||||
let mut role = Role::new("r", "p");
|
let mut role = Role::new("r", "p");
|
||||||
role.set_enabled_mcp_servers(Some("github".to_string()));
|
role.set_enabled_mcp_servers(Some(vec!["github".to_string()]));
|
||||||
|
|
||||||
let fns = ctx.select_enabled_mcp_servers(&role);
|
let fns = ctx.select_enabled_mcp_servers(&role);
|
||||||
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect();
|
||||||
|
|||||||
+71
-27
@@ -28,13 +28,13 @@ pub trait RoleLike {
|
|||||||
fn model(&self) -> &Model;
|
fn model(&self) -> &Model;
|
||||||
fn temperature(&self) -> Option<f64>;
|
fn temperature(&self) -> Option<f64>;
|
||||||
fn top_p(&self) -> Option<f64>;
|
fn top_p(&self) -> Option<f64>;
|
||||||
fn enabled_tools(&self) -> Option<String>;
|
fn enabled_tools(&self) -> Option<Vec<String>>;
|
||||||
fn enabled_mcp_servers(&self) -> Option<String>;
|
fn enabled_mcp_servers(&self) -> Option<Vec<String>>;
|
||||||
fn set_model(&mut self, model: Model);
|
fn set_model(&mut self, model: Model);
|
||||||
fn set_temperature(&mut self, value: Option<f64>);
|
fn set_temperature(&mut self, value: Option<f64>);
|
||||||
fn set_top_p(&mut self, value: Option<f64>);
|
fn set_top_p(&mut self, value: Option<f64>);
|
||||||
fn set_enabled_tools(&mut self, value: Option<String>);
|
fn set_enabled_tools(&mut self, value: Option<Vec<String>>);
|
||||||
fn set_enabled_mcp_servers(&mut self, value: Option<String>);
|
fn set_enabled_mcp_servers(&mut self, value: Option<Vec<String>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||||
@@ -51,14 +51,26 @@ pub struct Role {
|
|||||||
temperature: Option<f64>,
|
temperature: Option<f64>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
top_p: Option<f64>,
|
top_p: Option<f64>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(
|
||||||
enabled_tools: Option<String>,
|
default,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
skip_serializing_if = "Option::is_none",
|
||||||
enabled_mcp_servers: Option<String>,
|
deserialize_with = "super::deserialize_csv_or_vec"
|
||||||
|
)]
|
||||||
|
enabled_tools: Option<Vec<String>>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
deserialize_with = "super::deserialize_csv_or_vec"
|
||||||
|
)]
|
||||||
|
enabled_mcp_servers: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
skills_enabled: Option<bool>,
|
skills_enabled: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(
|
||||||
enabled_skills: Option<String>,
|
default,
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
deserialize_with = "super::deserialize_csv_or_vec"
|
||||||
|
)]
|
||||||
|
enabled_skills: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
auto_continue: Option<bool>,
|
auto_continue: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -98,12 +110,12 @@ impl Role {
|
|||||||
"model" => role.model_id = value.as_str().map(|v| v.to_string()),
|
"model" => role.model_id = value.as_str().map(|v| v.to_string()),
|
||||||
"temperature" => role.temperature = value.as_f64(),
|
"temperature" => role.temperature = value.as_f64(),
|
||||||
"top_p" => role.top_p = value.as_f64(),
|
"top_p" => role.top_p = value.as_f64(),
|
||||||
"enabled_tools" => role.enabled_tools = value.as_str().map(|v| v.to_string()),
|
"enabled_tools" => role.enabled_tools = parse_string_or_array(value),
|
||||||
"enabled_mcp_servers" => {
|
"enabled_mcp_servers" => {
|
||||||
role.enabled_mcp_servers = value.as_str().map(|v| v.to_string())
|
role.enabled_mcp_servers = parse_string_or_array(value)
|
||||||
}
|
}
|
||||||
"skills_enabled" => role.skills_enabled = value.as_bool(),
|
"skills_enabled" => role.skills_enabled = value.as_bool(),
|
||||||
"enabled_skills" => role.enabled_skills = value.as_str().map(|v| v.to_string()),
|
"enabled_skills" => role.enabled_skills = parse_string_or_array(value),
|
||||||
"auto_continue" => role.auto_continue = value.as_bool(),
|
"auto_continue" => role.auto_continue = value.as_bool(),
|
||||||
"max_auto_continues" => {
|
"max_auto_continues" => {
|
||||||
role.max_auto_continues = value.as_u64().map(|v| v as usize)
|
role.max_auto_continues = value.as_u64().map(|v| v as usize)
|
||||||
@@ -147,17 +159,21 @@ impl Role {
|
|||||||
if let Some(top_p) = self.top_p() {
|
if let Some(top_p) = self.top_p() {
|
||||||
metadata.push(format!("top_p: {top_p}"));
|
metadata.push(format!("top_p: {top_p}"));
|
||||||
}
|
}
|
||||||
if let Some(enabled_tools) = self.enabled_tools() {
|
if let Some(enabled_tools) = &self.enabled_tools {
|
||||||
metadata.push(format!("enabled_tools: {enabled_tools}"));
|
let inline = serde_json::to_string(enabled_tools).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
metadata.push(format!("enabled_tools: {inline}"));
|
||||||
}
|
}
|
||||||
if let Some(enabled_mcp_servers) = self.enabled_mcp_servers() {
|
if let Some(enabled_mcp_servers) = &self.enabled_mcp_servers {
|
||||||
metadata.push(format!("enabled_mcp_servers: {enabled_mcp_servers}"));
|
let inline =
|
||||||
|
serde_json::to_string(enabled_mcp_servers).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
metadata.push(format!("enabled_mcp_servers: {inline}"));
|
||||||
}
|
}
|
||||||
if let Some(skills_enabled) = self.skills_enabled {
|
if let Some(skills_enabled) = self.skills_enabled {
|
||||||
metadata.push(format!("skills_enabled: {skills_enabled}"));
|
metadata.push(format!("skills_enabled: {skills_enabled}"));
|
||||||
}
|
}
|
||||||
if let Some(enabled_skills) = &self.enabled_skills {
|
if let Some(enabled_skills) = &self.enabled_skills {
|
||||||
metadata.push(format!("enabled_skills: {enabled_skills}"));
|
let inline = serde_json::to_string(enabled_skills).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
metadata.push(format!("enabled_skills: {inline}"));
|
||||||
}
|
}
|
||||||
if let Some(auto_continue) = self.auto_continue {
|
if let Some(auto_continue) = self.auto_continue {
|
||||||
metadata.push(format!("auto_continue: {auto_continue}"));
|
metadata.push(format!("auto_continue: {auto_continue}"));
|
||||||
@@ -225,8 +241,8 @@ impl Role {
|
|||||||
model: &Model,
|
model: &Model,
|
||||||
temperature: Option<f64>,
|
temperature: Option<f64>,
|
||||||
top_p: Option<f64>,
|
top_p: Option<f64>,
|
||||||
enabled_tools: Option<String>,
|
enabled_tools: Option<Vec<String>>,
|
||||||
enabled_mcp_servers: Option<String>,
|
enabled_mcp_servers: Option<Vec<String>>,
|
||||||
) {
|
) {
|
||||||
self.set_model(model.clone());
|
self.set_model(model.clone());
|
||||||
if temperature.is_some() {
|
if temperature.is_some() {
|
||||||
@@ -287,7 +303,7 @@ impl Role {
|
|||||||
self.skills_enabled
|
self.skills_enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enabled_skills(&self) -> Option<&str> {
|
pub fn enabled_skills(&self) -> Option<&[String]> {
|
||||||
self.enabled_skills.as_deref()
|
self.enabled_skills.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,11 +376,11 @@ impl RoleLike for Role {
|
|||||||
self.top_p
|
self.top_p
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_tools(&self) -> Option<String> {
|
fn enabled_tools(&self) -> Option<Vec<String>> {
|
||||||
self.enabled_tools.clone()
|
self.enabled_tools.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_mcp_servers(&self) -> Option<String> {
|
fn enabled_mcp_servers(&self) -> Option<Vec<String>> {
|
||||||
self.enabled_mcp_servers.clone()
|
self.enabled_mcp_servers.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,15 +399,37 @@ impl RoleLike for Role {
|
|||||||
self.top_p = value;
|
self.top_p = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_enabled_tools(&mut self, value: Option<String>) {
|
fn set_enabled_tools(&mut self, value: Option<Vec<String>>) {
|
||||||
self.enabled_tools = value;
|
self.enabled_tools = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_enabled_mcp_servers(&mut self, value: Option<String>) {
|
fn set_enabled_mcp_servers(&mut self, value: Option<Vec<String>>) {
|
||||||
self.enabled_mcp_servers = value;
|
self.enabled_mcp_servers = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_string_or_array(value: &Value) -> Option<Vec<String>> {
|
||||||
|
if value.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = value.as_str() {
|
||||||
|
return Some(csv_to_vec(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(arr) = value.as_array() {
|
||||||
|
let items: Vec<String> = arr
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.as_str().map(|s| s.trim().to_string()))
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return Some(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_structure_prompt(prompt: &str) -> (&str, Vec<(&str, &str)>) {
|
fn parse_structure_prompt(prompt: &str) -> (&str, Vec<(&str, &str)>) {
|
||||||
let mut text = prompt;
|
let mut text = prompt;
|
||||||
let mut search_input = true;
|
let mut search_input = true;
|
||||||
@@ -466,14 +504,20 @@ mod tests {
|
|||||||
fn role_new_parses_enabled_tools() {
|
fn role_new_parses_enabled_tools() {
|
||||||
let content = "---\nenabled_tools: tool1,tool2\n---\nPrompt";
|
let content = "---\nenabled_tools: tool1,tool2\n---\nPrompt";
|
||||||
let role = Role::new("test", content);
|
let role = Role::new("test", content);
|
||||||
assert_eq!(role.enabled_tools(), Some("tool1,tool2".to_string()));
|
assert_eq!(
|
||||||
|
role.enabled_tools(),
|
||||||
|
Some(vec!["tool1".to_string(), "tool2".to_string()])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn role_new_parses_enabled_mcp_servers() {
|
fn role_new_parses_enabled_mcp_servers() {
|
||||||
let content = "---\nenabled_mcp_servers: github,jira\n---\nPrompt";
|
let content = "---\nenabled_mcp_servers: github,jira\n---\nPrompt";
|
||||||
let role = Role::new("test", content);
|
let role = Role::new("test", content);
|
||||||
assert_eq!(role.enabled_mcp_servers(), Some("github,jira".to_string()));
|
assert_eq!(
|
||||||
|
role.enabled_mcp_servers(),
|
||||||
|
Some(vec!["github".to_string(), "jira".to_string()])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+48
-15
@@ -24,14 +24,26 @@ pub struct Session {
|
|||||||
temperature: Option<f64>,
|
temperature: Option<f64>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
top_p: Option<f64>,
|
top_p: Option<f64>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(
|
||||||
enabled_tools: Option<String>,
|
default,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
skip_serializing_if = "Option::is_none",
|
||||||
enabled_mcp_servers: Option<String>,
|
deserialize_with = "super::deserialize_csv_or_vec"
|
||||||
|
)]
|
||||||
|
enabled_tools: Option<Vec<String>>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
deserialize_with = "super::deserialize_csv_or_vec"
|
||||||
|
)]
|
||||||
|
enabled_mcp_servers: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
skills_enabled: Option<bool>,
|
skills_enabled: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(
|
||||||
enabled_skills: Option<String>,
|
default,
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
deserialize_with = "super::deserialize_csv_or_vec"
|
||||||
|
)]
|
||||||
|
enabled_skills: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
save_session: Option<bool>,
|
save_session: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -83,10 +95,17 @@ impl Session {
|
|||||||
self.skills_enabled
|
self.skills_enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enabled_skills(&self) -> Option<&str> {
|
pub fn enabled_skills(&self) -> Option<&[String]> {
|
||||||
self.enabled_skills.as_deref()
|
self.enabled_skills.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_skills_enabled(&mut self, value: Option<bool>) {
|
||||||
|
if self.skills_enabled != value {
|
||||||
|
self.skills_enabled = value;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_from_ctx(ctx: &RequestContext, app: &AppConfig, name: &str) -> Self {
|
pub fn new_from_ctx(ctx: &RequestContext, app: &AppConfig, name: &str) -> Self {
|
||||||
let role = ctx.extract_role(app);
|
let role = ctx.extract_role(app);
|
||||||
let mut session = Self {
|
let mut session = Self {
|
||||||
@@ -182,10 +201,16 @@ impl Session {
|
|||||||
data["top_p"] = top_p.into();
|
data["top_p"] = top_p.into();
|
||||||
}
|
}
|
||||||
if let Some(enabled_tools) = self.enabled_tools() {
|
if let Some(enabled_tools) = self.enabled_tools() {
|
||||||
data["enabled_tools"] = enabled_tools.into();
|
data["enabled_tools"] = json!(enabled_tools);
|
||||||
}
|
}
|
||||||
if let Some(enabled_mcp_servers) = self.enabled_mcp_servers() {
|
if let Some(enabled_mcp_servers) = self.enabled_mcp_servers() {
|
||||||
data["enabled_mcp_servers"] = enabled_mcp_servers.into();
|
data["enabled_mcp_servers"] = json!(enabled_mcp_servers);
|
||||||
|
}
|
||||||
|
if let Some(skills_enabled) = self.skills_enabled() {
|
||||||
|
data["skills_enabled"] = skills_enabled.into();
|
||||||
|
}
|
||||||
|
if let Some(enabled_skills) = self.enabled_skills() {
|
||||||
|
data["enabled_skills"] = json!(enabled_skills);
|
||||||
}
|
}
|
||||||
if let Some(save_session) = self.save_session() {
|
if let Some(save_session) = self.save_session() {
|
||||||
data["save_session"] = save_session.into();
|
data["save_session"] = save_session.into();
|
||||||
@@ -242,11 +267,19 @@ impl Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(enabled_tools) = self.enabled_tools() {
|
if let Some(enabled_tools) = self.enabled_tools() {
|
||||||
items.push(("enabled_tools", enabled_tools));
|
items.push(("enabled_tools", enabled_tools.join(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(enabled_mcp_servers) = self.enabled_mcp_servers() {
|
if let Some(enabled_mcp_servers) = self.enabled_mcp_servers() {
|
||||||
items.push(("enabled_mcp_servers", enabled_mcp_servers));
|
items.push(("enabled_mcp_servers", enabled_mcp_servers.join(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(skills_enabled) = self.skills_enabled() {
|
||||||
|
items.push(("skills_enabled", skills_enabled.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(enabled_skills) = self.enabled_skills() {
|
||||||
|
items.push(("enabled_skills", enabled_skills.join(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(save_session) = self.save_session() {
|
if let Some(save_session) = self.save_session() {
|
||||||
@@ -682,11 +715,11 @@ impl RoleLike for Session {
|
|||||||
self.top_p
|
self.top_p
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_tools(&self) -> Option<String> {
|
fn enabled_tools(&self) -> Option<Vec<String>> {
|
||||||
self.enabled_tools.clone()
|
self.enabled_tools.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enabled_mcp_servers(&self) -> Option<String> {
|
fn enabled_mcp_servers(&self) -> Option<Vec<String>> {
|
||||||
self.enabled_mcp_servers.clone()
|
self.enabled_mcp_servers.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,14 +746,14 @@ impl RoleLike for Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_enabled_tools(&mut self, value: Option<String>) {
|
fn set_enabled_tools(&mut self, value: Option<Vec<String>>) {
|
||||||
if self.enabled_tools != value {
|
if self.enabled_tools != value {
|
||||||
self.enabled_tools = value;
|
self.enabled_tools = value;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_enabled_mcp_servers(&mut self, value: Option<String>) {
|
fn set_enabled_mcp_servers(&mut self, value: Option<Vec<String>>) {
|
||||||
if self.enabled_mcp_servers != value {
|
if self.enabled_mcp_servers != value {
|
||||||
self.enabled_mcp_servers = value;
|
self.enabled_mcp_servers = value;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|||||||
+33
-9
@@ -33,9 +33,9 @@ pub struct Skill {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
body: String,
|
body: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
enabled_tools: Option<String>,
|
enabled_tools: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
enabled_mcp_servers: Option<String>,
|
enabled_mcp_servers: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
auto_unload: Option<bool>,
|
auto_unload: Option<bool>,
|
||||||
}
|
}
|
||||||
@@ -69,10 +69,10 @@ impl Skill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"enabled_tools" => {
|
"enabled_tools" => {
|
||||||
skill.enabled_tools = value.as_str().map(|v| v.to_string());
|
skill.enabled_tools = parse_skill_string_or_array(value);
|
||||||
}
|
}
|
||||||
"enabled_mcp_servers" => {
|
"enabled_mcp_servers" => {
|
||||||
skill.enabled_mcp_servers = value.as_str().map(|v| v.to_string());
|
skill.enabled_mcp_servers = parse_skill_string_or_array(value);
|
||||||
}
|
}
|
||||||
"auto_unload" => {
|
"auto_unload" => {
|
||||||
skill.auto_unload = value.as_bool();
|
skill.auto_unload = value.as_bool();
|
||||||
@@ -134,11 +134,11 @@ impl Skill {
|
|||||||
&self.body
|
&self.body
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enabled_tools(&self) -> Option<&str> {
|
pub fn enabled_tools(&self) -> Option<&[String]> {
|
||||||
self.enabled_tools.as_deref()
|
self.enabled_tools.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enabled_mcp_servers(&self) -> Option<&str> {
|
pub fn enabled_mcp_servers(&self) -> Option<&[String]> {
|
||||||
self.enabled_mcp_servers.as_deref()
|
self.enabled_mcp_servers.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,11 +157,29 @@ impl Skill {
|
|||||||
fn declares_mcp_servers(&self) -> bool {
|
fn declares_mcp_servers(&self) -> bool {
|
||||||
self.enabled_mcp_servers
|
self.enabled_mcp_servers
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|s| !s.trim().is_empty())
|
.map(|servers| !servers.is_empty())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_skill_string_or_array(value: &Value) -> Option<Vec<String>> {
|
||||||
|
if value.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(s) = value.as_str() {
|
||||||
|
return Some(csv_to_vec(s));
|
||||||
|
}
|
||||||
|
if let Some(arr) = value.as_array() {
|
||||||
|
let items: Vec<String> = arr
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.as_str().map(|s| s.trim().to_string()))
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
return Some(items);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -189,8 +207,14 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(skill.name(), "git-master");
|
assert_eq!(skill.name(), "git-master");
|
||||||
assert_eq!(skill.description(), "Atomic commits, rebase surgery");
|
assert_eq!(skill.description(), "Atomic commits, rebase surgery");
|
||||||
assert_eq!(skill.enabled_tools(), Some("shell,fs"));
|
assert_eq!(
|
||||||
assert_eq!(skill.enabled_mcp_servers(), Some("github"));
|
skill.enabled_tools(),
|
||||||
|
Some(["shell".to_string(), "fs".to_string()].as_slice())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
skill.enabled_mcp_servers(),
|
||||||
|
Some(["github".to_string()].as_slice())
|
||||||
|
);
|
||||||
assert!(skill.auto_unload());
|
assert!(skill.auto_unload());
|
||||||
assert_eq!(skill.body(), "You are a git expert");
|
assert_eq!(skill.body(), "You are a git expert");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ impl SkillPolicy {
|
|||||||
.map(|v| v.iter().cloned().collect());
|
.map(|v| v.iter().cloned().collect());
|
||||||
|
|
||||||
let enabled_raw: Option<Vec<String>> = session
|
let enabled_raw: Option<Vec<String>> = session
|
||||||
.and_then(|s| parse_csv_opt(s.enabled_skills()))
|
.and_then(|s| s.enabled_skills().map(|v| v.to_vec()))
|
||||||
.or_else(|| agent.and_then(|a| a.enabled_skills().map(|v| v.to_vec())))
|
.or_else(|| agent.and_then(|a| a.enabled_skills().map(|v| v.to_vec())))
|
||||||
.or_else(|| role.and_then(|r| parse_csv_opt(r.enabled_skills())))
|
.or_else(|| role.and_then(|r| r.enabled_skills().map(|v| v.to_vec())))
|
||||||
.or_else(|| parse_csv_opt(global.enabled_skills.as_deref()));
|
.or_else(|| global.enabled_skills.clone());
|
||||||
|
|
||||||
let enabled: HashSet<String> = match enabled_raw {
|
let enabled: HashSet<String> = match enabled_raw {
|
||||||
Some(explicit) => {
|
Some(explicit) => {
|
||||||
@@ -107,17 +107,9 @@ impl SkillPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_csv_opt(s: Option<&str>) -> Option<Vec<String>> {
|
|
||||||
s.map(|raw| {
|
|
||||||
raw.split(',')
|
|
||||||
.map(|t| t.trim().to_string())
|
|
||||||
.filter(|t| !t.is_empty())
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::super::csv_to_vec;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn always_true(_: &str) -> bool {
|
fn always_true(_: &str) -> bool {
|
||||||
@@ -135,7 +127,7 @@ mod tests {
|
|||||||
) -> AppConfig {
|
) -> AppConfig {
|
||||||
AppConfig {
|
AppConfig {
|
||||||
skills_enabled,
|
skills_enabled,
|
||||||
enabled_skills: enabled.map(|s| s.to_string()),
|
enabled_skills: enabled.map(csv_to_vec),
|
||||||
visible_skills: visible.map(|v| v.iter().map(|s| s.to_string()).collect()),
|
visible_skills: visible.map(|v| v.iter().map(|s| s.to_string()).collect()),
|
||||||
..AppConfig::default()
|
..AppConfig::default()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ impl SkillRegistry {
|
|||||||
pub fn loaded_mcp_servers(&self) -> BTreeSet<String> {
|
pub fn loaded_mcp_servers(&self) -> BTreeSet<String> {
|
||||||
let mut out = BTreeSet::new();
|
let mut out = BTreeSet::new();
|
||||||
for skill in self.loaded.values() {
|
for skill in self.loaded.values() {
|
||||||
if let Some(csv) = skill.enabled_mcp_servers() {
|
if let Some(servers) = skill.enabled_mcp_servers() {
|
||||||
for token in csv.split(',') {
|
for token in servers {
|
||||||
let t = token.trim();
|
let t = token.trim();
|
||||||
if !t.is_empty() {
|
if !t.is_empty() {
|
||||||
out.insert(t.to_string());
|
out.insert(t.to_string());
|
||||||
@@ -69,12 +69,22 @@ impl SkillRegistry {
|
|||||||
let base_tools_set = effective.enabled_tools().is_some();
|
let base_tools_set = effective.enabled_tools().is_some();
|
||||||
let base_mcps_set = effective.enabled_mcp_servers().is_some();
|
let base_mcps_set = effective.enabled_mcp_servers().is_some();
|
||||||
|
|
||||||
let mut tools = parse_csv(effective.enabled_tools().as_deref());
|
let mut tools: BTreeSet<String> = effective
|
||||||
let mut mcps = parse_csv(effective.enabled_mcp_servers().as_deref());
|
.enabled_tools()
|
||||||
|
.map(|v| v.into_iter().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut mcps: BTreeSet<String> = effective
|
||||||
|
.enabled_mcp_servers()
|
||||||
|
.map(|v| v.into_iter().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
for (_, skill) in &self.loaded {
|
for (_, skill) in &self.loaded {
|
||||||
tools.extend(parse_csv(skill.enabled_tools()));
|
if let Some(skill_tools) = skill.enabled_tools() {
|
||||||
mcps.extend(parse_csv(skill.enabled_mcp_servers()));
|
tools.extend(skill_tools.iter().cloned());
|
||||||
|
}
|
||||||
|
if let Some(servers) = skill.enabled_mcp_servers() {
|
||||||
|
mcps.extend(servers.iter().cloned());
|
||||||
|
}
|
||||||
if !skip_body && !skill.body().is_empty() {
|
if !skip_body && !skill.body().is_empty() {
|
||||||
let separator = if effective.is_empty_prompt() {
|
let separator = if effective.is_empty_prompt() {
|
||||||
""
|
""
|
||||||
@@ -87,34 +97,17 @@ impl SkillRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if base_tools_set || !tools.is_empty() {
|
if base_tools_set || !tools.is_empty() {
|
||||||
effective.set_enabled_tools(Some(join_csv(&tools)));
|
effective.set_enabled_tools(Some(tools.into_iter().collect()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if base_mcps_set || !mcps.is_empty() {
|
if base_mcps_set || !mcps.is_empty() {
|
||||||
effective.set_enabled_mcp_servers(Some(join_csv(&mcps)));
|
effective.set_enabled_mcp_servers(Some(mcps.into_iter().collect()));
|
||||||
}
|
}
|
||||||
|
|
||||||
effective
|
effective
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_csv(s: Option<&str>) -> BTreeSet<String> {
|
|
||||||
let mut set = BTreeSet::new();
|
|
||||||
if let Some(raw) = s {
|
|
||||||
for token in raw.split(',') {
|
|
||||||
let trimmed = token.trim();
|
|
||||||
if !trimmed.is_empty() {
|
|
||||||
set.insert(trimmed.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
}
|
|
||||||
|
|
||||||
fn join_csv(set: &BTreeSet<String>) -> String {
|
|
||||||
set.iter().cloned().collect::<Vec<_>>().join(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl SkillRegistry {
|
impl SkillRegistry {
|
||||||
fn insert_for_test(&mut self, skill: Skill) {
|
fn insert_for_test(&mut self, skill: Skill) {
|
||||||
@@ -194,7 +187,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(effective.prompt(), "Process: __INPUT__");
|
assert_eq!(effective.prompt(), "Process: __INPUT__");
|
||||||
let tools = effective.enabled_tools().expect("tools set by skill");
|
let tools = effective.enabled_tools().expect("tools set by skill");
|
||||||
assert!(tools.contains("shell"));
|
assert!(tools.iter().any(|s| s == "shell"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -223,16 +216,16 @@ mod tests {
|
|||||||
));
|
));
|
||||||
|
|
||||||
let mut base = Role::new("test", "body");
|
let mut base = Role::new("test", "body");
|
||||||
base.set_enabled_tools(Some("web_search".to_string()));
|
base.set_enabled_tools(Some(vec!["web_search".to_string()]));
|
||||||
|
|
||||||
let effective = registry.effective_role(&base);
|
let effective = registry.effective_role(&base);
|
||||||
|
|
||||||
let tools_str = effective.enabled_tools().unwrap();
|
let tools_vec = effective.enabled_tools().unwrap();
|
||||||
let tools: BTreeSet<&str> = tools_str.split(',').collect();
|
let tools: BTreeSet<&str> = tools_vec.iter().map(|s| s.as_str()).collect();
|
||||||
assert_eq!(tools, BTreeSet::from(["fs", "git", "shell", "web_search"]));
|
assert_eq!(tools, BTreeSet::from(["fs", "git", "shell", "web_search"]));
|
||||||
|
|
||||||
let mcps_str = effective.enabled_mcp_servers().unwrap();
|
let mcps_vec = effective.enabled_mcp_servers().unwrap();
|
||||||
let mcps: BTreeSet<&str> = mcps_str.split(',').collect();
|
let mcps: BTreeSet<&str> = mcps_vec.iter().map(|s| s.as_str()).collect();
|
||||||
assert_eq!(mcps, BTreeSet::from(["github", "jira"]));
|
assert_eq!(mcps, BTreeSet::from(["github", "jira"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,10 +247,10 @@ mod tests {
|
|||||||
registry.insert_for_test(make_skill("knowledge", "", "Pure knowledge"));
|
registry.insert_for_test(make_skill("knowledge", "", "Pure knowledge"));
|
||||||
|
|
||||||
let mut base = Role::new("test", "Base");
|
let mut base = Role::new("test", "Base");
|
||||||
base.set_enabled_tools(Some(String::new()));
|
base.set_enabled_tools(Some(Vec::new()));
|
||||||
let effective = registry.effective_role(&base);
|
let effective = registry.effective_role(&base);
|
||||||
|
|
||||||
assert_eq!(effective.enabled_tools().as_deref(), Some(""));
|
assert_eq!(effective.enabled_tools().as_deref(), Some([].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+4
-28
@@ -127,8 +127,8 @@ fn handle_list(ctx: &RequestContext, policy: &SkillPolicy) -> Result<Value> {
|
|||||||
entries.push(json!({
|
entries.push(json!({
|
||||||
"name": skill.name(),
|
"name": skill.name(),
|
||||||
"description": skill.description(),
|
"description": skill.description(),
|
||||||
"grants_tools": csv_to_vec(skill.enabled_tools()),
|
"grants_tools": skill.enabled_tools().unwrap_or_default(),
|
||||||
"grants_mcp_servers": csv_to_vec(skill.enabled_mcp_servers()),
|
"grants_mcp_servers": skill.enabled_mcp_servers().unwrap_or_default(),
|
||||||
"loaded": ctx.skill_registry.is_loaded(skill.name()),
|
"loaded": ctx.skill_registry.is_loaded(skill.name()),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -166,11 +166,11 @@ async fn handle_load(
|
|||||||
|
|
||||||
let tools_declared = skill
|
let tools_declared = skill
|
||||||
.enabled_tools()
|
.enabled_tools()
|
||||||
.map(|s| !s.trim().is_empty())
|
.map(|v| !v.is_empty())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let mcps_declared = skill
|
let mcps_declared = skill
|
||||||
.enabled_mcp_servers()
|
.enabled_mcp_servers()
|
||||||
.map(|s| !s.trim().is_empty())
|
.map(|v| !v.is_empty())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if tools_declared && !function_calling_on {
|
if tools_declared && !function_calling_on {
|
||||||
@@ -226,16 +226,6 @@ async fn handle_unload(ctx: &mut RequestContext, args: &Value) -> Result<Value>
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn csv_to_vec(csv: Option<&str>) -> Vec<String> {
|
|
||||||
csv.map(|raw| {
|
|
||||||
raw.split(',')
|
|
||||||
.map(|t| t.trim().to_string())
|
|
||||||
.filter(|t| !t.is_empty())
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -293,18 +283,4 @@ mod tests {
|
|||||||
|
|
||||||
assert!(required, "skill__list should have no required parameters");
|
assert!(required, "skill__list should have no required parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn csv_to_vec_empty_input() {
|
|
||||||
assert!(csv_to_vec(None).is_empty());
|
|
||||||
assert!(csv_to_vec(Some("")).is_empty());
|
|
||||||
assert!(csv_to_vec(Some(" ")).is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn csv_to_vec_parses_and_trims() {
|
|
||||||
let v = csv_to_vec(Some("a, b ,c,, d"));
|
|
||||||
|
|
||||||
assert_eq!(v, vec!["a", "b", "c", "d"]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -256,18 +256,18 @@ fn build_inline_role(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if node.tools.as_deref().unwrap_or_default().is_empty() {
|
if node.tools.as_deref().unwrap_or_default().is_empty() {
|
||||||
role.set_enabled_tools(Some(String::new()));
|
role.set_enabled_tools(Some(Vec::new()));
|
||||||
role.set_enabled_mcp_servers(Some(String::new()));
|
role.set_enabled_mcp_servers(Some(Vec::new()));
|
||||||
} else {
|
} else {
|
||||||
if !regular_tools.is_empty() {
|
if !regular_tools.is_empty() {
|
||||||
role.set_enabled_tools(Some(regular_tools.join(",")));
|
role.set_enabled_tools(Some(regular_tools.to_vec()));
|
||||||
} else {
|
} else {
|
||||||
role.set_enabled_tools(Some(String::new()));
|
role.set_enabled_tools(Some(Vec::new()));
|
||||||
}
|
}
|
||||||
if !mcp_servers.is_empty() {
|
if !mcp_servers.is_empty() {
|
||||||
role.set_enabled_mcp_servers(Some(mcp_servers.join(",")));
|
role.set_enabled_mcp_servers(Some(mcp_servers.to_vec()));
|
||||||
} else {
|
} else {
|
||||||
role.set_enabled_mcp_servers(Some(String::new()));
|
role.set_enabled_mcp_servers(Some(Vec::new()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ async fn extract_via_extractor(
|
|||||||
|
|
||||||
fn build_extractor_role() -> Result<Role> {
|
fn build_extractor_role() -> Result<Role> {
|
||||||
let mut role = Role::new(EXTRACTOR_ROLE_NAME, EXTRACTOR_ROLE_PROMPT);
|
let mut role = Role::new(EXTRACTOR_ROLE_NAME, EXTRACTOR_ROLE_PROMPT);
|
||||||
role.set_enabled_tools(Some(String::new()));
|
role.set_enabled_tools(Some(Vec::new()));
|
||||||
role.set_enabled_mcp_servers(Some(String::new()));
|
role.set_enabled_mcp_servers(Some(Vec::new()));
|
||||||
Ok(role)
|
Ok(role)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ mod tests {
|
|||||||
fn build_extractor_role_disables_tools_and_mcp() {
|
fn build_extractor_role_disables_tools_and_mcp() {
|
||||||
let role = build_extractor_role().expect("builtin role must exist");
|
let role = build_extractor_role().expect("builtin role must exist");
|
||||||
|
|
||||||
assert_eq!(role.enabled_tools().as_deref(), Some(""));
|
assert_eq!(role.enabled_tools().as_deref(), Some([].as_slice()));
|
||||||
assert_eq!(role.enabled_mcp_servers().as_deref(), Some(""));
|
assert_eq!(role.enabled_mcp_servers().as_deref(), Some([].as_slice()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-12
@@ -146,7 +146,7 @@ impl McpRegistry {
|
|||||||
pub async fn init(
|
pub async fn init(
|
||||||
log_path: Option<PathBuf>,
|
log_path: Option<PathBuf>,
|
||||||
start_mcp_servers: bool,
|
start_mcp_servers: bool,
|
||||||
enabled_mcp_servers: Option<String>,
|
enabled_mcp_servers: Option<Vec<String>>,
|
||||||
abort_signal: AbortSignal,
|
abort_signal: AbortSignal,
|
||||||
app_config: &AppConfig,
|
app_config: &AppConfig,
|
||||||
vault: &Vault,
|
vault: &Vault,
|
||||||
@@ -216,7 +216,7 @@ impl McpRegistry {
|
|||||||
|
|
||||||
async fn start_select_mcp_servers(
|
async fn start_select_mcp_servers(
|
||||||
&mut self,
|
&mut self,
|
||||||
enabled_mcp_servers: Option<String>,
|
enabled_mcp_servers: Option<Vec<String>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if self.config.is_none() {
|
if self.config.is_none() {
|
||||||
debug!(
|
debug!(
|
||||||
@@ -292,15 +292,15 @@ impl McpRegistry {
|
|||||||
Ok((id.to_string(), service, catalog))
|
Ok((id.to_string(), service, catalog))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_server_ids(&self, enabled_mcp_servers: Option<String>) -> Vec<String> {
|
fn resolve_server_ids(&self, enabled_mcp_servers: Option<Vec<String>>) -> Vec<String> {
|
||||||
if let Some(config) = &self.config
|
if let Some(config) = &self.config
|
||||||
&& let Some(servers) = enabled_mcp_servers
|
&& let Some(servers) = enabled_mcp_servers
|
||||||
{
|
{
|
||||||
if servers == "all" {
|
if servers.iter().any(|s| s.trim() == "all") {
|
||||||
config.mcp_servers.keys().cloned().collect()
|
config.mcp_servers.keys().cloned().collect()
|
||||||
} else {
|
} else {
|
||||||
let enabled_servers: HashSet<String> =
|
let enabled_servers: HashSet<String> =
|
||||||
servers.split(',').map(|s| s.trim().to_string()).collect();
|
servers.into_iter().map(|s| s.trim().to_string()).collect();
|
||||||
config
|
config
|
||||||
.mcp_servers
|
.mcp_servers
|
||||||
.keys()
|
.keys()
|
||||||
@@ -754,7 +754,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn resolve_all_returns_all_configured_servers() {
|
fn resolve_all_returns_all_configured_servers() {
|
||||||
let registry = make_registry_with_config(&["github", "slack", "jira"]);
|
let registry = make_registry_with_config(&["github", "slack", "jira"]);
|
||||||
let mut ids = registry.resolve_server_ids(Some("all".to_string()));
|
let mut ids = registry.resolve_server_ids(Some(vec!["all".to_string()]));
|
||||||
ids.sort();
|
ids.sort();
|
||||||
assert_eq!(ids, vec!["github", "jira", "slack"]);
|
assert_eq!(ids, vec!["github", "jira", "slack"]);
|
||||||
}
|
}
|
||||||
@@ -762,7 +762,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn resolve_comma_separated_returns_matching_servers() {
|
fn resolve_comma_separated_returns_matching_servers() {
|
||||||
let registry = make_registry_with_config(&["github", "slack", "jira"]);
|
let registry = make_registry_with_config(&["github", "slack", "jira"]);
|
||||||
let mut ids = registry.resolve_server_ids(Some("github, jira".to_string()));
|
let mut ids =
|
||||||
|
registry.resolve_server_ids(Some(vec!["github".to_string(), "jira".to_string()]));
|
||||||
ids.sort();
|
ids.sort();
|
||||||
assert_eq!(ids, vec!["github", "jira"]);
|
assert_eq!(ids, vec!["github", "jira"]);
|
||||||
}
|
}
|
||||||
@@ -770,7 +771,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn resolve_single_server_name() {
|
fn resolve_single_server_name() {
|
||||||
let registry = make_registry_with_config(&["github", "slack"]);
|
let registry = make_registry_with_config(&["github", "slack"]);
|
||||||
let ids = registry.resolve_server_ids(Some("slack".to_string()));
|
let ids = registry.resolve_server_ids(Some(vec!["slack".to_string()]));
|
||||||
assert_eq!(ids, vec!["slack"]);
|
assert_eq!(ids, vec!["slack"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,28 +785,32 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn resolve_no_config_returns_empty() {
|
fn resolve_no_config_returns_empty() {
|
||||||
let registry = McpRegistry::default();
|
let registry = McpRegistry::default();
|
||||||
let ids = registry.resolve_server_ids(Some("all".to_string()));
|
let ids = registry.resolve_server_ids(Some(vec!["all".to_string()]));
|
||||||
assert!(ids.is_empty());
|
assert!(ids.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_nonexistent_server_filtered_out() {
|
fn resolve_nonexistent_server_filtered_out() {
|
||||||
let registry = make_registry_with_config(&["github"]);
|
let registry = make_registry_with_config(&["github"]);
|
||||||
let ids = registry.resolve_server_ids(Some("github, nonexistent".to_string()));
|
let ids = registry
|
||||||
|
.resolve_server_ids(Some(vec!["github".to_string(), "nonexistent".to_string()]));
|
||||||
assert_eq!(ids, vec!["github"]);
|
assert_eq!(ids, vec!["github"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_all_nonexistent_returns_empty() {
|
fn resolve_all_nonexistent_returns_empty() {
|
||||||
let registry = make_registry_with_config(&["github"]);
|
let registry = make_registry_with_config(&["github"]);
|
||||||
let ids = registry.resolve_server_ids(Some("foo, bar".to_string()));
|
let ids = registry.resolve_server_ids(Some(vec!["foo".to_string(), "bar".to_string()]));
|
||||||
assert!(ids.is_empty());
|
assert!(ids.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_trims_whitespace() {
|
fn resolve_trims_whitespace() {
|
||||||
let registry = make_registry_with_config(&["github", "slack"]);
|
let registry = make_registry_with_config(&["github", "slack"]);
|
||||||
let mut ids = registry.resolve_server_ids(Some(" github , slack ".to_string()));
|
let mut ids = registry.resolve_server_ids(Some(vec![
|
||||||
|
" github ".to_string(),
|
||||||
|
" slack ".to_string(),
|
||||||
|
]));
|
||||||
ids.sort();
|
ids.sort();
|
||||||
assert_eq!(ids, vec!["github", "slack"]);
|
assert_eq!(ids, vec!["github", "slack"]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user