feat: improved first-time run experience and included a templated configuration file that now has comments like the config.example.yaml so users don't have to go to the repo to see all the knobs
This commit is contained in:
@@ -0,0 +1,279 @@
|
|||||||
|
# Coyote configuration. Generated by the first-run wizard.
|
||||||
|
# Every setting is listed with its effective value and a short description.
|
||||||
|
# For richer examples of each section, see
|
||||||
|
# https://github.com/Dark-Alex-17/coyote/blob/main/config.example.yaml
|
||||||
|
|
||||||
|
# ---- LLM ----
|
||||||
|
__MODEL_BLOCK__
|
||||||
|
temperature: null # Set default temperature parameter (0, 1)
|
||||||
|
top_p: null # Set default top-p parameter, with a range of (0, 1) or (0, 2) depending on the model
|
||||||
|
|
||||||
|
# ---- Behavior ----
|
||||||
|
dry_run: false # Display the messages that would be sent to the LLM without actually sending them
|
||||||
|
stream: true # Controls whether to use the stream-style APIs when querying for completions from LLM clients
|
||||||
|
save: true # Indicates whether to persist the conversation to messages.md for posterity
|
||||||
|
keybindings: emacs # Choose keybinding style (emacs, vi)
|
||||||
|
editor: null # Specifies the editor used to edit the input buffer or session. (e.g. vim, emacs, nano, hx). Defaults to $EDITOR
|
||||||
|
wrap: auto # Controls text wrapping (no, auto, <max-width>)
|
||||||
|
wrap_code: false # Enables or disables the wrapping of code blocks
|
||||||
|
|
||||||
|
# ---- Vault ----
|
||||||
|
# See the [Vault documentation](https://github.com/Dark-Alex-17/coyote/wiki/Vault) for more information on the Coyote vault.
|
||||||
|
#
|
||||||
|
# The secrets_provider tells Coyote where to read and write secrets referenced via {{SECRET_NAME}} syntax.
|
||||||
|
#
|
||||||
|
# Shorthand: set vault_password_file to enable the local provider with that password
|
||||||
|
# file (it cannot be a secret template).
|
||||||
|
#
|
||||||
|
# Explicit: set secrets_provider to one of the supported types below. When secrets_provider is set,
|
||||||
|
# vault_password_file is ignored. Note: secrets_provider itself cannot use secret template syntax.
|
||||||
|
# The vault must be initialized before any secrets can be resolved.
|
||||||
|
#
|
||||||
|
# Local (same as the shorthand above):
|
||||||
|
# secrets_provider:
|
||||||
|
# type: local
|
||||||
|
# password_file: ~/.coyote_password
|
||||||
|
#
|
||||||
|
# AWS Secrets Manager (requires an authenticated AWS CLI; see `aws sso login` or `aws configure`):
|
||||||
|
# secrets_provider:
|
||||||
|
# type: aws_secrets_manager
|
||||||
|
# aws_profile: default
|
||||||
|
# aws_region: us-east-1
|
||||||
|
#
|
||||||
|
# GCP Secret Manager (requires `gcloud auth application-default login`):
|
||||||
|
# secrets_provider:
|
||||||
|
# type: gcp_secret_manager
|
||||||
|
# gcp_project_id: my-project-id
|
||||||
|
#
|
||||||
|
# Azure Key Vault (requires `az login`):
|
||||||
|
# secrets_provider:
|
||||||
|
# type: azure_key_vault
|
||||||
|
# vault_name: my-vault-name
|
||||||
|
#
|
||||||
|
# gopass (requires the `gopass` CLI to be installed and initialized):
|
||||||
|
# secrets_provider:
|
||||||
|
# type: gopass
|
||||||
|
# store: my-store # Optional; omit to use the default store
|
||||||
|
#
|
||||||
|
# 1Password (requires the `op` CLI to be installed and signed in via `op signin`):
|
||||||
|
# secrets_provider:
|
||||||
|
# type: one_password
|
||||||
|
# vault: Production # Optional; omit to use the default vault
|
||||||
|
# account: my.1password.com # Optional; omit to use the default account
|
||||||
|
__SECRETS_BLOCK__
|
||||||
|
|
||||||
|
# ---- Function Calling ----
|
||||||
|
# See the [Tools documentation](https://github.com/Dark-Alex-17/coyote/wiki/Tools) for more details
|
||||||
|
function_calling_support: true # Enables or disables function calling (globally)
|
||||||
|
mapping_tools: {} # Alias for a tool or toolset
|
||||||
|
# Example:
|
||||||
|
# mapping_tools:
|
||||||
|
# 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.
|
||||||
|
# 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: null # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools').
|
||||||
|
# Null/missing = all tools in the global tools dir are visible; [] = none;
|
||||||
|
# an explicit list makes only those tools visible.
|
||||||
|
# Example:
|
||||||
|
# visible_tools:
|
||||||
|
# - execute_command.sh
|
||||||
|
# - fs_cat.sh
|
||||||
|
# - fs_ls.sh
|
||||||
|
|
||||||
|
# ---- Skills ----
|
||||||
|
# Skills are modular knowledge or capability packs the LLM can load and unload mid-conversation.
|
||||||
|
# See the [Skills documentation](https://github.com/Dark-Alex-17/coyote/wiki/Skills) for more details.
|
||||||
|
skills_enabled: true # Master switch. Set to false to hide all skill management tools from the model.
|
||||||
|
# Skills also require `function_calling_support: true` above to work at all.
|
||||||
|
enabled_skills: null # Which skills are available by default (no role/agent/session active). null = all visible.
|
||||||
|
# Accepts either a YAML list or a comma-separated string.
|
||||||
|
# Example (list form):
|
||||||
|
# enabled_skills:
|
||||||
|
# - git-master
|
||||||
|
# - ai-slop-remover
|
||||||
|
# Example (comma-separated form):
|
||||||
|
# enabled_skills: git-master,ai-slop-remover
|
||||||
|
visible_skills: null # The universe of skills allowed to be enabled in any context. null = all installed.
|
||||||
|
# Example:
|
||||||
|
# visible_skills:
|
||||||
|
# - ai-slop-remover
|
||||||
|
# - code-review
|
||||||
|
# - git-master
|
||||||
|
|
||||||
|
# ---- Macros ----
|
||||||
|
# Macros are Coyote's custom commands: named sequences of REPL commands and prompts, invoked directly by name
|
||||||
|
# (a macro file named `review.yaml` runs as `.review [args]`; built-in commands always win a name collision).
|
||||||
|
# Workspace-local macros in `.coyote/macros/` shadow same-named global macros (skip them with --no-workspace-macros).
|
||||||
|
# See the [Macros documentation](https://github.com/Dark-Alex-17/coyote/wiki/Macros) for more details.
|
||||||
|
enabled_macros: null # Which macros are invocable by default (no role/agent/session active). null = all visible.
|
||||||
|
# An empty list means NO macros are invocable. Accepts either a YAML list or a
|
||||||
|
# comma-separated string. Roles, agents, and sessions may define their own
|
||||||
|
# `enabled_macros`; the most specific active one wins (session > agent > role > global).
|
||||||
|
# Example (list form):
|
||||||
|
# enabled_macros:
|
||||||
|
# - generate-commit-message
|
||||||
|
# Example (comma-separated form):
|
||||||
|
# enabled_macros: generate-commit-message,review
|
||||||
|
|
||||||
|
# ---- MCP Servers ----
|
||||||
|
# See the [MCP Servers documentation](https://github.com/Dark-Alex-17/coyote/wiki/MCP-Servers) for more details
|
||||||
|
mcp_server_support: true # Enables or disables MCP servers (globally)
|
||||||
|
mapping_mcp_servers: {} # Alias for an MCP server or set of servers
|
||||||
|
# Example:
|
||||||
|
# mapping_mcp_servers:
|
||||||
|
# git: github,gitmcp
|
||||||
|
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
|
||||||
|
mcp_tools: null # Per-server MCP tool allowlists (glob patterns: * and ? supported).
|
||||||
|
# Tools that match no pattern are hidden from the model as if they
|
||||||
|
# don't exist. Stacks with the other allowlist layers (mcp.json
|
||||||
|
# `allowedTools`, role, agent, session, skill, graph node). Every
|
||||||
|
# configured layer must allow a tool, so layers only ever narrow.
|
||||||
|
# An empty list blocks all of a server's tools.
|
||||||
|
# Example:
|
||||||
|
# mcp_tools:
|
||||||
|
# github:
|
||||||
|
# - get_*
|
||||||
|
# - list_*
|
||||||
|
# slack: []
|
||||||
|
|
||||||
|
# ---- Auto-Continue (Todo System) ----
|
||||||
|
# The auto-continue system provides built-in task tracking for improved reliability.
|
||||||
|
# When enabled, the model can create todo lists and the system will automatically
|
||||||
|
# prompt it to continue when incomplete tasks remain.
|
||||||
|
# See the [Todo System documentation](https://github.com/Dark-Alex-17/coyote/wiki/TODO-System) for more information
|
||||||
|
auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false)
|
||||||
|
max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10)
|
||||||
|
inject_todo_instructions: true # Inject default todo usage instructions into the system prompt (default: true)
|
||||||
|
continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default
|
||||||
|
inject_skill_instructions: true # Inject a short hint pointing the model at `skill__list` when skills are enabled
|
||||||
|
# in this context. Only injected if `function_calling_support`, `skills_enabled`, and the
|
||||||
|
# effective enabled skill set is non-empty (default: true)
|
||||||
|
skill_instructions: null # Custom text used for the skill hint when injected. If null, uses built-in default
|
||||||
|
|
||||||
|
# ---- Prelude ----
|
||||||
|
repl_prelude: null # Set a default session or role for REPL mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
||||||
|
cmd_prelude: null # Set a default session or role for CMD mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
||||||
|
agent_session: null # Set a session to use when starting an agent (e.g. temp, default)
|
||||||
|
|
||||||
|
# ---- Session ----
|
||||||
|
# See the [Session documentation](https://github.com/Dark-Alex-17/coyote/wiki/Sessions) for more information
|
||||||
|
save_session: null # Controls the persistence of the session. If true, auto save; if false, don't auto-save; if null, ask the user what to do
|
||||||
|
compression_threshold: 4000 # Compress the session when the token count reaches or exceeds this threshold
|
||||||
|
compression_keep_last: 0 # Number of most-recent messages to keep visible after compression (0 = compress all messages)
|
||||||
|
summarization_prompt: null # The text prompt used for creating a concise summary of session messages. If null, uses built-in default
|
||||||
|
summary_context_prompt: null # The text prompt used for including the summary of the entire session as context to the model. If null, uses built-in default
|
||||||
|
max_tool_result_chars: null # Cap on tool result characters forwarded to the model per call (null = no cap)
|
||||||
|
max_concurrent_jobs: null # Max background jobs (`job__*` tools) running at once per context (null = 5; 0 disables background jobs entirely)
|
||||||
|
|
||||||
|
# ---- Memory ----
|
||||||
|
# See the [Memory documentation](https://github.com/Dark-Alex-17/coyote/wiki/Memory) for more information.
|
||||||
|
# Memory is opt-in by workspace presence (`.coyote/memory/MEMORY.md`) and global
|
||||||
|
# presence (`<config_dir>/memory/MEMORY.md`). Set `memory: false` to disable
|
||||||
|
# even when memory files exist. The cascade is: agent > session > role > app.
|
||||||
|
# Bootstrap with `coyote --init-memory [global|workspace]` to create the marker file
|
||||||
|
# the LLM needs before it will write any memory.
|
||||||
|
memory: null # null = enabled when memory exists on disk; true = force on; false = force off
|
||||||
|
memory_cap_with_tools: null # Char cap for injected memory when function calling is available (null = 6000).
|
||||||
|
# Only MEMORY.md indexes are injected; the LLM uses memory__read to fetch drill files.
|
||||||
|
memory_cap_without_tools: null # Char cap when function calling is unavailable (null = 12000).
|
||||||
|
# Indexes plus drill file bodies are injected up to this cap.
|
||||||
|
|
||||||
|
# ---- Workspace Instructions ----
|
||||||
|
# Human-curated project instructions injected read-only into the system prompt, in full.
|
||||||
|
# Coyote walks up from the current directory and injects the first match from the file
|
||||||
|
# chain below (per directory, in order). Scaffold with `coyote --init-instructions`.
|
||||||
|
# Disable per-invocation with --no-workspace-instructions, or override the chain with
|
||||||
|
# repeatable --workspace-instructions-file flags.
|
||||||
|
workspace_instructions: null # null/true = inject when an instructions file exists; false = never inject
|
||||||
|
workspace_instructions_files: null # File name chain to search, in priority order.
|
||||||
|
# Default: [COYOTE.md, AGENTS.md, CLAUDE.md, GEMINI.md]
|
||||||
|
# Set to a custom list to reorder or drop fallbacks, e.g.:
|
||||||
|
# workspace_instructions_files: [COYOTE.md]
|
||||||
|
|
||||||
|
# ---- RAG ----
|
||||||
|
# See the [RAG Docs](https://github.com/Dark-Alex-17/coyote/wiki/RAG) for more details.
|
||||||
|
rag_embedding_model: null # Specifies the embedding model used for context retrieval
|
||||||
|
rag_reranker_model: null # Specifies the reranker model used for sorting retrieved documents; Coyote uses Reciprocal Rank Fusion by default
|
||||||
|
rag_top_k: 5 # Specifies the number of documents to retrieve for answering queries
|
||||||
|
rag_chunk_size: null # Defines the size of chunks for document processing in characters
|
||||||
|
rag_chunk_overlap: null # Defines the overlap between chunks
|
||||||
|
rag_template: null # Defines the query structure using variables like __CONTEXT__, __SOURCES__, and __INPUT__
|
||||||
|
# to tailor searches to specific needs. If null, uses built-in default
|
||||||
|
rag_extractor_model: null # LLM model for graph-based entity/relationship extraction; when set, enables a graph RAG signal alongside vector and BM25
|
||||||
|
rag_extractor_prompt: null # Custom extraction prompt template; must contain __CHUNK__ placeholder; defaults to built-in prompt when null
|
||||||
|
rag_graph_hops: 1 # Number of hops to expand from matched entities at query time (0 = seed nodes only; 1 = direct neighbors; increase for denser graphs)
|
||||||
|
|
||||||
|
# Define document loaders to control how RAG and `.file`/`--file` load files of specific formats.
|
||||||
|
document_loaders: {}
|
||||||
|
# You can add custom loaders using the following syntax:
|
||||||
|
# <file-extension>: <command-to-load-the-file>
|
||||||
|
# Note: Use `$1` for input file and `$2` for output file. If `$2` is omitted, use stdout as output.
|
||||||
|
# Examples:
|
||||||
|
# document_loaders:
|
||||||
|
# pdf: 'pdftotext $1 -' # https://poppler.freedesktop.org
|
||||||
|
# docx: 'pandoc --to plain $1' # https://pandoc.org
|
||||||
|
# jina: 'curl -fsSL https://r.jina.ai/$1 -H "Authorization: Bearer {{JINA_API_KEY}}"' # Requires a Jina API key in the Coyote vault
|
||||||
|
|
||||||
|
# ---- Appearance ----
|
||||||
|
highlight: true # Controls syntax highlighting
|
||||||
|
raw_markdown: false # When true, render markdown as raw text with syntax highlighting only. When false (default), transforms markdown syntax (headings, bold, lists, etc.) into styled terminal output
|
||||||
|
theme: null # null = the built-in dark theme; set to `light` for the built-in light theme.
|
||||||
|
# Custom themes: place a `dark.tmTheme` or `light.tmTheme` file in the Coyote config
|
||||||
|
# directory and it is used in place of the corresponding built-in.
|
||||||
|
|
||||||
|
# ---- REPL Prompt ----
|
||||||
|
# Custom REPL left/right prompts; see the [REPL Prompt Documentation](https://github.com/Dark-Alex-17/coyote/wiki/REPL-Prompt) for more information
|
||||||
|
left_prompt: null # If null, uses the built-in default:
|
||||||
|
# '{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} '
|
||||||
|
right_prompt: null # If null, uses the built-in default:
|
||||||
|
# '{color.cyan}{?reasoning_effort [{reasoning_effort}] }{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}'
|
||||||
|
|
||||||
|
# ---- Miscellaneous ----
|
||||||
|
user_agent: null # Set User-Agent HTTP header, use `auto` for coyote/<current-version>
|
||||||
|
save_shell_history: true # Whether to save shell execution command to the history file
|
||||||
|
sync_models_url: null # URL to sync model changes from. If null, uses the built-in default:
|
||||||
|
# https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/models.yaml
|
||||||
|
|
||||||
|
# ---- Clients ----
|
||||||
|
# See the [Clients documentation](https://github.com/Dark-Alex-17/coyote/wiki/Clients) for more details
|
||||||
|
#
|
||||||
|
# All clients have the following configuration:
|
||||||
|
# - type: xxxx
|
||||||
|
# name: xxxx # Only use it to distinguish clients with the same client type. Optional
|
||||||
|
# models:
|
||||||
|
# - name: xxxx # Chat model
|
||||||
|
# max_input_tokens: 100000
|
||||||
|
# supports_vision: true
|
||||||
|
# supports_function_calling: true
|
||||||
|
# - name: xxxx # Embedding model
|
||||||
|
# type: embedding
|
||||||
|
# default_chunk_size: 1500
|
||||||
|
# max_batch_size: 100
|
||||||
|
# - name: xxxx # Reranker model
|
||||||
|
# type: reranker
|
||||||
|
# patch: # Patch API calls
|
||||||
|
# chat_completions: # API type; Possible values: chat_completions, embeddings, and rerank
|
||||||
|
# <regex>: # The regex to match model names, e.g. '.*' 'gpt-4o' 'gpt-4o|gpt-4-.*'
|
||||||
|
# url: '' # Patch request URL
|
||||||
|
# body: # Patch request body
|
||||||
|
# <json>
|
||||||
|
# headers: # Patch request headers
|
||||||
|
# <key>: <value>
|
||||||
|
# extra:
|
||||||
|
# proxy: socks5://127.0.0.1:1080 # Set proxy
|
||||||
|
# connect_timeout: 10 # Set timeout in seconds for connect to api
|
||||||
|
# read_timeout: 300 # Set timeout in seconds for a read stall (no bytes received); 0 disables (default: 300)
|
||||||
|
__CLIENTS_BLOCK__
|
||||||
+181
-195
@@ -1,39 +1,40 @@
|
|||||||
# ---- LLM ----
|
# ---- LLM ----
|
||||||
model: openai:gpt-4o # Specify the LLM to use
|
model: openai:gpt-4o # Specify the LLM to use
|
||||||
temperature: null # Set default temperature parameter (0, 1)
|
temperature: null # Set default temperature parameter (0, 1)
|
||||||
top_p: null # Set default top-p parameter, with a range of (0, 1) or (0, 2) depending on the model
|
top_p: null # Set default top-p parameter, with a range of (0, 1) or (0, 2) depending on the model
|
||||||
reasoning_effort:
|
reasoning_effort: null # Reasoning effort level for models that support it (e.g. low, medium, high).
|
||||||
null # Reasoning effort level for models that support it (e.g. low, medium, high).
|
# Only valid when the active model declares reasoning_levels. See the Clients docs.
|
||||||
# Only valid when the active model declares reasoning_levels. See the Clients docs.
|
|
||||||
|
|
||||||
# ---- Behavior ----
|
# ---- Behavior ----
|
||||||
stream: true # Controls whether to use the stream-style APIs when querying for completions from LLM clients.
|
stream: true # Controls whether to use the stream-style APIs when querying for completions from LLM clients.
|
||||||
save: true # Indicates whether to persist the conversation to messages.md for posterity
|
save: true # Indicates whether to persist the conversation to messages.md for posterity
|
||||||
keybindings: emacs # Choose keybinding style (emacs, vi)
|
keybindings: emacs # Choose keybinding style (emacs, vi)
|
||||||
editor: null # Specifies the editor used to edit the input buffer or session. (e.g. vim, emacs, nano, hx). Defaults to $EDITOR
|
editor: null # Specifies the editor used to edit the input buffer or session. (e.g. vim, emacs, nano, hx). Defaults to $EDITOR
|
||||||
wrap: no # Controls text wrapping (no, auto, <max-width>)
|
wrap: no # Controls text wrapping (no, auto, <max-width>)
|
||||||
wrap_code: false # Enables or disables the wrapping of code blocks
|
wrap_code: false # Enables or disables the wrapping of code blocks
|
||||||
|
|
||||||
# ---- Prelude ----
|
# ---- Prelude ----
|
||||||
repl_prelude: null # Set a default session or role for REPL mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
repl_prelude: null # Set a default session or role for REPL mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
||||||
cmd_prelude: null # Set a default session or role for CMD mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
cmd_prelude: null # Set a default session or role for CMD mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
||||||
agent_session: null # Set a session to use when starting an agent (e.g. temp, default)
|
agent_session: null # Set a session to use when starting an agent (e.g. temp, default)
|
||||||
|
|
||||||
# ---- Appearance ----
|
# ---- Appearance ----
|
||||||
highlight: true # Controls syntax highlighting
|
highlight: true # Controls syntax highlighting
|
||||||
raw_markdown: false # When true, render markdown as raw text with syntax highlighting only. When false (default), transforms markdown syntax (headings, bold, lists, etc.) into styled terminal output
|
raw_markdown: false # When true, render markdown as raw text with syntax highlighting only. When false (default), transforms markdown syntax (headings, bold, lists, etc.) into styled terminal output
|
||||||
light_theme: false # Activates a light color theme when true. env: COYOTE_LIGHT_THEME
|
light_theme: false # Activates a light color theme when true. env: COYOTE_LIGHT_THEME
|
||||||
|
|
||||||
# ---- Miscellaneous ----
|
# ---- Miscellaneous ----
|
||||||
user_agent: null # Set User-Agent HTTP header, use `auto` for coyote/<current-version>
|
user_agent: null # Set User-Agent HTTP header, use `auto` for coyote/<current-version>
|
||||||
save_shell_history: true # Whether to save shell execution command to the history file
|
save_shell_history: true # Whether to save shell execution command to the history file
|
||||||
sync_models_url: > # URL to sync model changes from
|
sync_models_url: > # URL to sync model changes from
|
||||||
https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/models.yaml
|
https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/models.yaml
|
||||||
|
|
||||||
# ---- REPL Prompt ----
|
# ---- REPL Prompt ----
|
||||||
# Custom REPL left/right prompts; see the [REPL Prompt Documentation](https://github.com/Dark-Alex-17/coyote/wiki/REPL-Prompt) for more information
|
# Custom REPL left/right prompts; see the [REPL Prompt Documentation](https://github.com/Dark-Alex-17/coyote/wiki/REPL-Prompt) for more information
|
||||||
left_prompt: '{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} '
|
left_prompt:
|
||||||
right_prompt: '{color.cyan}{?reasoning_effort [{reasoning_effort}] }{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}'
|
'{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} '
|
||||||
|
right_prompt:
|
||||||
|
'{color.cyan}{?reasoning_effort [{reasoning_effort}] }{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}'
|
||||||
|
|
||||||
# ---- Vault ----
|
# ---- Vault ----
|
||||||
# See the [Vault documentation](https://github.com/Dark-Alex-17/coyote/wiki/Vault) for more information on the Coyote vault.
|
# See the [Vault documentation](https://github.com/Dark-Alex-17/coyote/wiki/Vault) for more information on the Coyote vault.
|
||||||
@@ -41,7 +42,7 @@ right_prompt: '{color.cyan}{?reasoning_effort [{reasoning_effort}] }{color.purpl
|
|||||||
# The secrets_provider tells Coyote where to read and write secrets referenced via {{SECRET_NAME}} syntax.
|
# The secrets_provider tells Coyote where to read and write secrets referenced via {{SECRET_NAME}} syntax.
|
||||||
#
|
#
|
||||||
# Shorthand: set vault_password_file to enable the local provider with that password file.
|
# Shorthand: set vault_password_file to enable the local provider with that password file.
|
||||||
vault_password_file: null # Path to a file containing the password for the Coyote vault (cannot be a secret template)
|
vault_password_file: null # Path to a file containing the password for the Coyote vault (cannot be a secret template)
|
||||||
#
|
#
|
||||||
# Explicit: set secrets_provider to one of the supported types below. When secrets_provider is set,
|
# Explicit: set secrets_provider to one of the supported types below. When secrets_provider is set,
|
||||||
# vault_password_file is ignored. Note: secrets_provider itself cannot use {{SECRET}} template syntax.
|
# vault_password_file is ignored. Note: secrets_provider itself cannot use {{SECRET}} template syntax.
|
||||||
@@ -81,39 +82,39 @@ vault_password_file: null # Path to a file containing the password for the Coyot
|
|||||||
|
|
||||||
# ---- Function Calling ----
|
# ---- Function Calling ----
|
||||||
# See the [Tools documentation](https://github.com/Dark-Alex-17/coyote/wiki/Tools) for more details
|
# See the [Tools documentation](https://github.com/Dark-Alex-17/coyote/wiki/Tools) for more details
|
||||||
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:
|
enabled_tools: null # Which tools to enable by default.
|
||||||
null # Which tools to enable by default.
|
# Accepts either a YAML list or a comma-separated string. Use 'all' to enable everything.
|
||||||
# Accepts either a YAML list or a comma-separated string. Use 'all' to enable everything.
|
# Example (list form):
|
||||||
# Example (list form):
|
# enabled_tools:
|
||||||
# enabled_tools:
|
# - fs
|
||||||
# - fs
|
# - web_search_coyote
|
||||||
# - web_search_coyote
|
# Example (comma-separated form):
|
||||||
# Example (comma-separated form):
|
# enabled_tools: fs,web_search_coyote
|
||||||
# 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')
|
# Null/missing = all tools in the global tools dir are visible; [] = none.
|
||||||
# - ast_grep.sh
|
# - ast_grep.sh
|
||||||
# - demo_py.py
|
# - demo_py.py
|
||||||
# - demo_sh.sh
|
# - demo_sh.sh
|
||||||
# - demo_ts.ts
|
# - demo_ts.ts
|
||||||
- execute_command.sh
|
- execute_command.sh
|
||||||
# - execute_py_code.py
|
# - execute_py_code.py
|
||||||
# - execute_sql_code.sh
|
# - execute_sql_code.sh
|
||||||
# - fetch_url_via_curl.sh
|
# - fetch_url_via_curl.sh
|
||||||
# - fetch_url_via_jina.sh
|
# - fetch_url_via_jina.sh
|
||||||
- fs_cat.sh
|
- fs_cat.sh
|
||||||
- fs_ls.sh
|
- fs_ls.sh
|
||||||
# - fs_read.sh
|
# - fs_read.sh
|
||||||
# - fs_glob.sh
|
# - fs_glob.sh
|
||||||
# - fs_grep.sh
|
# - fs_grep.sh
|
||||||
# - fs_mkdir.sh
|
# - fs_mkdir.sh
|
||||||
# - fs_patch.sh
|
# - fs_patch.sh
|
||||||
# - fs_write.sh
|
# - fs_write.sh
|
||||||
- get_current_time.sh
|
- get_current_time.sh
|
||||||
# - get_current_weather.py
|
# - get_current_weather.py
|
||||||
# - get_current_weather.ts
|
# - get_current_weather.ts
|
||||||
- get_current_weather.sh
|
- get_current_weather.sh
|
||||||
# - search_arxiv.sh
|
# - search_arxiv.sh
|
||||||
# - search_wikipedia.sh
|
# - search_wikipedia.sh
|
||||||
@@ -126,106 +127,97 @@ visible_tools: # Which tools are visible to be compiled (and are thus able to be
|
|||||||
|
|
||||||
# ---- MCP Servers ----
|
# ---- MCP Servers ----
|
||||||
# See the [MCP Servers documentation](https://github.com/Dark-Alex-17/coyote/wiki/MCP-Servers) for more details
|
# See the [MCP Servers documentation](https://github.com/Dark-Alex-17/coyote/wiki/MCP-Servers) for more details
|
||||||
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:
|
enabled_mcp_servers: null # Which MCP servers to enable by default.
|
||||||
null # Which MCP servers to enable by default.
|
# Accepts either a YAML list or a comma-separated string. Use 'all' to enable everything.
|
||||||
# Accepts either a YAML list or a comma-separated string. Use 'all' to enable everything.
|
# Example (list form):
|
||||||
# Example (list form):
|
# enabled_mcp_servers:
|
||||||
# enabled_mcp_servers:
|
# - github
|
||||||
# - github
|
# - slack
|
||||||
# - slack
|
# Example (comma-separated form):
|
||||||
# Example (comma-separated form):
|
# enabled_mcp_servers: github,slack,ddg-search
|
||||||
# enabled_mcp_servers: github,slack,ddg-search
|
mcp_tools: null # Per-server MCP tool allowlists (glob patterns: * and ? supported).
|
||||||
mcp_tools:
|
# Tools that match no pattern are hidden from the model as if they
|
||||||
null # Per-server MCP tool allowlists (glob patterns: * and ? supported).
|
# don't exist. Stacks with the other allowlist layers (mcp.json
|
||||||
# Tools that match no pattern are hidden from the model as if they
|
# `allowedTools`, role, agent, session, skill, graph node). Every
|
||||||
# don't exist. Stacks with the other allowlist layers (mcp.json
|
# configured layer must allow a tool, so layers only ever narrow.
|
||||||
# `allowedTools`, role, agent, session, skill, graph node). Every
|
# An empty list blocks all of a server's tools.
|
||||||
# configured layer must allow a tool, so layers only ever narrow.
|
# Example:
|
||||||
# An empty list blocks all of a server's tools.
|
# mcp_tools:
|
||||||
# Example:
|
# github:
|
||||||
# mcp_tools:
|
# - get_*
|
||||||
# github:
|
# - list_*
|
||||||
# - get_*
|
# slack: []
|
||||||
# - list_*
|
no_workspace_mcp: false # Disable loading workspace-local MCP servers (default: false).
|
||||||
# slack: []
|
# When false (the default), Coyote merges the first workspace MCP config it finds
|
||||||
no_workspace_mcp:
|
# into the global MCP registry at startup, checking in order:
|
||||||
false # Disable loading workspace-local MCP servers (default: false).
|
# 1. .coyote/mcp.json
|
||||||
# When false (the default), Coyote merges the first workspace MCP config it finds
|
# 2. .coyote/.mcp.json (Claude-style file name)
|
||||||
# into the global MCP registry at startup, checking in order:
|
# 3. .mcp.json (project root; Claude Code convention)
|
||||||
# 1. .coyote/mcp.json
|
# Workspace entries shadow global ones on name collision.
|
||||||
# 2. .coyote/.mcp.json (Claude-style file name)
|
# Set to true (or pass --no-workspace-mcp) to skip this entirely.
|
||||||
# 3. .mcp.json (project root; Claude Code convention)
|
|
||||||
# Workspace entries shadow global ones on name collision.
|
|
||||||
# Set to true (or pass --no-workspace-mcp) to skip this entirely.
|
|
||||||
|
|
||||||
# ---- 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.
|
||||||
# See the [Skills documentation](https://github.com/Dark-Alex-17/coyote/wiki/Skills) for more details.
|
# See the [Skills documentation](https://github.com/Dark-Alex-17/coyote/wiki/Skills) for more details.
|
||||||
skills_enabled:
|
skills_enabled: true # Master switch. Set to false to hide all skill management tools from the model.
|
||||||
true # Master switch. Set to false to hide all skill management tools from the model.
|
# Skills also require `function_calling_support: true` above to work at all.
|
||||||
# Skills also require `function_calling_support: true` above to work at all.
|
visible_skills: # The universe of skills allowed to be enabled in any context. Omit (null) for "all installed".
|
||||||
visible_skills: # The universe of skills allowed to be enabled in any context. Omit (null) for "all installed".
|
|
||||||
- ai-slop-remover
|
- ai-slop-remover
|
||||||
- code-review
|
- code-review
|
||||||
- frontend-ui-ux
|
- frontend-ui-ux
|
||||||
- git-master
|
- git-master
|
||||||
enabled_skills:
|
enabled_skills: null # Which skills are available by default (no role/agent/session active). null = all visible.
|
||||||
null # Which skills are available by default (no role/agent/session active). null = all visible.
|
# Accepts either a YAML list or a comma-separated string.
|
||||||
# Accepts either a YAML list or a comma-separated string.
|
# Example (list form):
|
||||||
# Example (list form):
|
# enabled_skills:
|
||||||
# enabled_skills:
|
# - git-master
|
||||||
# - git-master
|
# - ai-slop-remover
|
||||||
# - ai-slop-remover
|
# Example (comma-separated form):
|
||||||
# Example (comma-separated form):
|
# enabled_skills: git-master,ai-slop-remover
|
||||||
# enabled_skills: git-master,ai-slop-remover
|
inject_skill_instructions: true # Inject a short hint pointing the model at `skill__list` when skills are enabled in
|
||||||
inject_skill_instructions:
|
# this context. Only injected if `function_calling_support`, `skills_enabled`, and the
|
||||||
true # Inject a short hint pointing the model at `skill__list` when skills are enabled in
|
# effective enabled skill set is non-empty (default: true).
|
||||||
# this context. Only injected if `function_calling_support`, `skills_enabled`, and the
|
skill_instructions: null # Custom text used for the skill hint when injected. If null, uses built-in default.
|
||||||
# effective enabled skill set is non-empty (default: true).
|
|
||||||
skill_instructions: null # Custom text used for the skill hint when injected. If null, uses built-in default.
|
|
||||||
|
|
||||||
# ---- Macros ----
|
# ---- Macros ----
|
||||||
# Macros are Coyote's custom commands: named sequences of REPL commands and prompts, invoked directly by name
|
# Macros are Coyote's custom commands: named sequences of REPL commands and prompts, invoked directly by name
|
||||||
# (a macro file named `review.yaml` runs as `.review [args]`; built-in commands always win a name collision).
|
# (a macro file named `review.yaml` runs as `.review [args]`; built-in commands always win a name collision).
|
||||||
# Workspace-local macros in `.coyote/macros/` shadow same-named global macros (skip them with --no-workspace-macros).
|
# Workspace-local macros in `.coyote/macros/` shadow same-named global macros (skip them with --no-workspace-macros).
|
||||||
# See the [Macros documentation](https://github.com/Dark-Alex-17/coyote/wiki/Macros) for more details.
|
# See the [Macros documentation](https://github.com/Dark-Alex-17/coyote/wiki/Macros) for more details.
|
||||||
enabled_macros:
|
enabled_macros: null # Which macros are invocable by default (no role/agent/session active). null = all visible.
|
||||||
null # Which macros are invocable by default (no role/agent/session active). null = all visible.
|
# An empty list means NO macros are invocable. Accepts either a YAML list or a
|
||||||
# An empty list means NO macros are invocable. Accepts either a YAML list or a
|
# comma-separated string. Roles, agents, and sessions may define their own
|
||||||
# comma-separated string. Roles, agents, and sessions may define their own
|
# `enabled_macros`; the most specific active one wins (session > agent > role > global).
|
||||||
# `enabled_macros`; the most specific active one wins (session > agent > role > global).
|
# Example (list form):
|
||||||
# Example (list form):
|
# enabled_macros:
|
||||||
# enabled_macros:
|
# - generate-commit-message
|
||||||
# - generate-commit-message
|
# Example (comma-separated form):
|
||||||
# Example (comma-separated form):
|
# enabled_macros: generate-commit-message,review
|
||||||
# enabled_macros: generate-commit-message,review
|
|
||||||
|
|
||||||
# ---- 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.
|
||||||
# When enabled, the model can create todo lists and the system will automatically
|
# When enabled, the model can create todo lists and the system will automatically
|
||||||
# prompt it to continue when incomplete tasks remain.
|
# prompt it to continue when incomplete tasks remain.
|
||||||
# See the [Todo System documentation](https://github.com/Dark-Alex-17/coyote/wiki/TODO-System) for more information
|
# See the [Todo System documentation](https://github.com/Dark-Alex-17/coyote/wiki/TODO-System) for more information
|
||||||
auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false)
|
auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false)
|
||||||
max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10)
|
max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10)
|
||||||
inject_todo_instructions: true # Inject default todo usage instructions into the system prompt (default: true)
|
inject_todo_instructions: true # Inject default todo usage instructions into the system prompt (default: true)
|
||||||
continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default
|
continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default
|
||||||
|
|
||||||
# ---- Session ----
|
# ---- Session ----
|
||||||
# See the [Session documentation](https://github.com/Dark-Alex-17/coyote/wiki/Sessions) for more information
|
# See the [Session documentation](https://github.com/Dark-Alex-17/coyote/wiki/Sessions) for more information
|
||||||
save_session: null # Controls the persistence of the session. If true, auto save; if false, don't auto-save save; if null, ask the user what to do
|
save_session: null # Controls the persistence of the session. If true, auto save; if false, don't auto-save save; if null, ask the user what to do
|
||||||
compression_threshold: 4000 # Compress the session when the token count reaches or exceeds this threshold
|
compression_threshold: 4000 # Compress the session when the token count reaches or exceeds this threshold
|
||||||
summarization_prompt:
|
summarization_prompt: > # The text prompt used for creating a concise summary of session message
|
||||||
> # The text prompt used for creating a concise summary of session message
|
|
||||||
'Summarize the discussion briefly in 200 words or less to use as a prompt for future context.'
|
'Summarize the discussion briefly in 200 words or less to use as a prompt for future context.'
|
||||||
summary_context_prompt:
|
summary_context_prompt: > # The text prompt used for including the summary of the entire session as context to the model
|
||||||
> # The text prompt used for including the summary of the entire session as context to the model
|
|
||||||
'This is a summary of the chat history as a recap: '
|
'This is a summary of the chat history as a recap: '
|
||||||
compression_keep_last: 0 # Number of most-recent messages to keep visible after compression (0 = compress all messages)
|
compression_keep_last: 0 # Number of most-recent messages to keep visible after compression (0 = compress all messages)
|
||||||
max_tool_result_chars: null # Cap on tool result characters forwarded to the model per call (null = no cap)
|
max_tool_result_chars: null # Cap on tool result characters forwarded to the model per call (null = no cap)
|
||||||
max_concurrent_jobs: 5 # Max background jobs (`job__*` tools) running at once per context (default: 5; 0 disables background jobs entirely)
|
max_concurrent_jobs: 5 # Max background jobs (`job__*` tools) running at once per context (default: 5; 0 disables background jobs entirely)
|
||||||
|
|
||||||
# ---- Memory ----
|
# ---- Memory ----
|
||||||
# See the [Memory documentation](https://github.com/Dark-Alex-17/coyote/wiki/Memory) for more information.
|
# See the [Memory documentation](https://github.com/Dark-Alex-17/coyote/wiki/Memory) for more information.
|
||||||
@@ -234,13 +226,11 @@ max_concurrent_jobs: 5 # Max background jobs (`job__*` tools) running at once pe
|
|||||||
# even when memory files exist. The cascade is: agent > session > role > app.
|
# even when memory files exist. The cascade is: agent > session > role > app.
|
||||||
# Bootstrap with `coyote --init-memory [global|workspace]` to create the marker file
|
# Bootstrap with `coyote --init-memory [global|workspace]` to create the marker file
|
||||||
# the LLM needs before it will write any memory.
|
# the LLM needs before it will write any memory.
|
||||||
memory: null # null = enabled when memory exists on disk; true = force on; false = force off
|
memory: null # null = enabled when memory exists on disk; true = force on; false = force off
|
||||||
memory_cap_with_tools:
|
memory_cap_with_tools: null # Char cap for injected memory when function calling is available (default: 6000).
|
||||||
null # Char cap for injected memory when function calling is available (default: 6000).
|
# Only MEMORY.md indexes are injected; the LLM uses memory__read to fetch drill files.
|
||||||
# Only MEMORY.md indexes are injected; the LLM uses memory__read to fetch drill files.
|
memory_cap_without_tools: null # Char cap when function calling is unavailable (default: 12000).
|
||||||
memory_cap_without_tools:
|
# Indexes plus drill file bodies are injected up to this cap.
|
||||||
null # Char cap when function calling is unavailable (default: 12000).
|
|
||||||
# Indexes plus drill file bodies are injected up to this cap.
|
|
||||||
|
|
||||||
# ---- Workspace Instructions ----
|
# ---- Workspace Instructions ----
|
||||||
# Human-curated project instructions injected read-only into the system prompt, in full.
|
# Human-curated project instructions injected read-only into the system prompt, in full.
|
||||||
@@ -248,23 +238,22 @@ memory_cap_without_tools:
|
|||||||
# chain below (per directory, in order). Scaffold with `coyote --init-instructions`.
|
# chain below (per directory, in order). Scaffold with `coyote --init-instructions`.
|
||||||
# Disable per-invocation with --no-workspace-instructions, or override the chain with
|
# Disable per-invocation with --no-workspace-instructions, or override the chain with
|
||||||
# repeatable --workspace-instructions-file flags.
|
# repeatable --workspace-instructions-file flags.
|
||||||
workspace_instructions: null # null/true = inject when an instructions file exists; false = never inject
|
workspace_instructions: null # null/true = inject when an instructions file exists; false = never inject
|
||||||
workspace_instructions_files:
|
workspace_instructions_files: null # File name chain to search, in priority order.
|
||||||
null # File name chain to search, in priority order.
|
# Default: [COYOTE.md, AGENTS.md, CLAUDE.md, GEMINI.md]
|
||||||
# Default: [COYOTE.md, AGENTS.md, CLAUDE.md, GEMINI.md]
|
# Set to a custom list to reorder or drop fallbacks, e.g.:
|
||||||
# Set to a custom list to reorder or drop fallbacks, e.g.:
|
# workspace_instructions_files: [COYOTE.md]
|
||||||
# workspace_instructions_files: [COYOTE.md]
|
|
||||||
|
|
||||||
# ---- RAG ----
|
# ---- RAG ----
|
||||||
# See the [RAG Docs](https://github.com/Dark-Alex-17/coyote/wiki/RAG) for more details.
|
# See the [RAG Docs](https://github.com/Dark-Alex-17/coyote/wiki/RAG) for more details.
|
||||||
rag_embedding_model: null # Specifies the embedding model used for context retrieval
|
rag_embedding_model: null # Specifies the embedding model used for context retrieval
|
||||||
rag_reranker_model: null # Specifies the reranker model used for sorting retrieved documents; Coyote uses Reciprocal Rank Fusion by default
|
rag_reranker_model: null # Specifies the reranker model used for sorting retrieved documents; Coyote uses Reciprocal Rank Fusion by default
|
||||||
rag_top_k: 5 # Specifies the number of documents to retrieve for answering queries
|
rag_top_k: 5 # Specifies the number of documents to retrieve for answering queries
|
||||||
rag_chunk_size: null # Defines the size of chunks for document processing in characters
|
rag_chunk_size: null # Defines the size of chunks for document processing in characters
|
||||||
rag_chunk_overlap: null # Defines the overlap between chunks
|
rag_chunk_overlap: null # Defines the overlap between chunks
|
||||||
rag_extractor_model: null # LLM model for graph-based entity/relationship extraction; when set, enables a graph RAG signal alongside vector and BM25
|
rag_extractor_model: null # LLM model for graph-based entity/relationship extraction; when set, enables a graph RAG signal alongside vector and BM25
|
||||||
rag_extractor_prompt: null # Custom extraction prompt template; must contain __CHUNK__ placeholder; defaults to built-in prompt when null
|
rag_extractor_prompt: null # Custom extraction prompt template; must contain __CHUNK__ placeholder; defaults to built-in prompt when null
|
||||||
rag_graph_hops: 1 # Number of hops to expand from matched entities at query time (0 = seed nodes only; 1 = direct neighbors; increase for denser graphs)
|
rag_graph_hops: 1 # Number of hops to expand from matched entities at query time (0 = seed nodes only; 1 = direct neighbors; increase for denser graphs)
|
||||||
# Defines the query structure using variables like __CONTEXT__, __SOURCES__, and __INPUT__ to tailor searches to specific needs
|
# Defines the query structure using variables like __CONTEXT__, __SOURCES__, and __INPUT__ to tailor searches to specific needs
|
||||||
rag_template: |
|
rag_template: |
|
||||||
Answer the query based on the context while respecting the rules. (user query, some textual context and rules, all inside xml tags)
|
Answer the query based on the context while respecting the rules. (user query, some textual context and rules, all inside xml tags)
|
||||||
@@ -295,14 +284,13 @@ document_loaders:
|
|||||||
# You can add custom loaders using the following syntax:
|
# You can add custom loaders using the following syntax:
|
||||||
# <file-extension>: <command-to-load-the-file>
|
# <file-extension>: <command-to-load-the-file>
|
||||||
# Note: Use `$1` for input file and `$2` for output file. If `$2` is omitted, use stdout as output.
|
# Note: Use `$1` for input file and `$2` for output file. If `$2` is omitted, use stdout as output.
|
||||||
pdf: 'pdftotext $1 -' # Use pdftotext to convert a PDF file to text
|
pdf: 'pdftotext $1 -' # Use pdftotext to convert a PDF file to text
|
||||||
# (see https://poppler.freedesktop.org for details on how to install pdftotext)
|
# (see https://poppler.freedesktop.org for details on how to install pdftotext)
|
||||||
docx: 'pandoc --to plain $1' # Use pandoc to convert a .docx file to text
|
docx: 'pandoc --to plain $1' # Use pandoc to convert a .docx file to text
|
||||||
# (see https://pandoc.org for details on how to install pandoc)
|
# (see https://pandoc.org for details on how to install pandoc)
|
||||||
jina: 'curl -fsSL https://r.jina.ai/$1 -H "Authorization: Bearer {{JINA_API_KEY}}' # Use Jina to translate a website into text;
|
jina: 'curl -fsSL https://r.jina.ai/$1 -H "Authorization: Bearer {{JINA_API_KEY}}' # Use Jina to translate a website into text;
|
||||||
# Requires a Jina API key to be added to the Coyote vault
|
# Requires a Jina API key to be added to the Coyote vault
|
||||||
git:
|
git: > # Use yek to load a git repository into the knowledgebase (https://github.com/bodo-run/yek)
|
||||||
> # Use yek to load a git repository into the knowledgebase (https://github.com/bodo-run/yek)
|
|
||||||
sh -c "yek $1 --json | jq 'map({ path: .filename, contents: .content })'"
|
sh -c "yek $1 --json | jq 'map({ path: .filename, contents: .content })'"
|
||||||
|
|
||||||
# ---- Clients ----
|
# ---- Clients ----
|
||||||
@@ -337,15 +325,15 @@ clients:
|
|||||||
|
|
||||||
# See https://platform.openai.com/docs/quickstart
|
# See https://platform.openai.com/docs/quickstart
|
||||||
- type: openai
|
- type: openai
|
||||||
api_base: https://api.openai.com/v1 # Optional
|
api_base: https://api.openai.com/v1 # Optional
|
||||||
api_key: '{{OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
organization_id: org-xxx # Optional
|
organization_id: org-xxx # Optional
|
||||||
|
|
||||||
# For any platform compatible with OpenAI's API
|
# For any platform compatible with OpenAI's API
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: ollama
|
name: ollama
|
||||||
api_base: http://localhost:11434/v1
|
api_base: http://localhost:11434/v1
|
||||||
api_key: '{{OLLAMA_API_KEY}}' # Optional; You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{OLLAMA_API_KEY}}' # Optional; You can either hard-code or inject secrets from the Coyote vault
|
||||||
models:
|
models:
|
||||||
- name: deepseek-r1
|
- name: deepseek-r1
|
||||||
max_input_tokens: 131072
|
max_input_tokens: 131072
|
||||||
@@ -363,10 +351,9 @@ clients:
|
|||||||
# See https://ai.google.dev/docs
|
# See https://ai.google.dev/docs
|
||||||
- type: gemini
|
- type: gemini
|
||||||
api_base: https://generativelanguage.googleapis.com/v1beta
|
api_base: https://generativelanguage.googleapis.com/v1beta
|
||||||
api_key: '{{GEMINI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{GEMINI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
auth:
|
auth: null # When set to 'oauth', Coyote will use OAuth instead of an API key
|
||||||
null # When set to 'oauth', Coyote will use OAuth instead of an API key
|
# Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
|
||||||
# Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
|
|
||||||
patch:
|
patch:
|
||||||
chat_completions:
|
chat_completions:
|
||||||
'.*':
|
'.*':
|
||||||
@@ -383,27 +370,25 @@ clients:
|
|||||||
|
|
||||||
# See https://docs.anthropic.com/claude/reference/getting-started-with-the-api
|
# See https://docs.anthropic.com/claude/reference/getting-started-with-the-api
|
||||||
- type: claude
|
- type: claude
|
||||||
api_base: https://api.anthropic.com/v1 # Optional
|
api_base: https://api.anthropic.com/v1 # Optional
|
||||||
api_key: '{{ANTHROPIC_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{ANTHROPIC_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
auth:
|
auth: null # When set to 'oauth', Coyote will use OAuth instead of an API key
|
||||||
null # When set to 'oauth', Coyote will use OAuth instead of an API key
|
# Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
|
||||||
# Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
|
|
||||||
|
|
||||||
# See https://docs.mistral.ai/
|
# See https://docs.mistral.ai/
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: mistral
|
name: mistral
|
||||||
api_base: https://api.mistral.ai/v1
|
api_base: https://api.mistral.ai/v1
|
||||||
api_key: '{{MISTRAL_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{MISTRAL_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://docs.x.ai/docs - OAuth via SuperGrok / X Premium+ subscription
|
# See https://docs.x.ai/docs - OAuth via SuperGrok / X Premium+ subscription
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: xai
|
name: xai
|
||||||
api_base: https://api.x.ai/v1
|
api_base: https://api.x.ai/v1
|
||||||
api_key: '{{XAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{XAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
auth:
|
auth: null # When set to 'oauth', Coyote will use OAuth instead of an API key
|
||||||
null # When set to 'oauth', Coyote will use OAuth instead of an API key
|
# Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
|
||||||
# Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
|
# Note: Oauth requires SuperGrok/X Premium+ subscription
|
||||||
# Note: Oauth requires SuperGrok/X Premium+ subscription
|
|
||||||
|
|
||||||
# Example: private OpenAI-compatible gateway with client_credentials OAuth
|
# Example: private OpenAI-compatible gateway with client_credentials OAuth
|
||||||
# - type: openai-compatible
|
# - type: openai-compatible
|
||||||
@@ -433,31 +418,31 @@ clients:
|
|||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: ai12
|
name: ai12
|
||||||
api_base: https://api.ai21.com/studio/v1
|
api_base: https://api.ai21.com/studio/v1
|
||||||
api_key: '{{AI21_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{AI21_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://docs.cohere.com/docs/the-cohere-platform
|
# See https://docs.cohere.com/docs/the-cohere-platform
|
||||||
- type: cohere
|
- type: cohere
|
||||||
api_base: https://api.cohere.ai/v2 # Optional
|
api_base: https://api.cohere.ai/v2 # Optional
|
||||||
api_key: '{{COHERE_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{COHERE_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://docs.perplexity.ai/getting-started/overview
|
# See https://docs.perplexity.ai/getting-started/overview
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: perplexity
|
name: perplexity
|
||||||
api_base: https://api.perplexity.ai
|
api_base: https://api.perplexity.ai
|
||||||
api_key: '{{PERPLEXITY_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{PERPLEXITY_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://console.groq.com/docs/quickstart
|
# See https://console.groq.com/docs/quickstart
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: groq
|
name: groq
|
||||||
api_base: https://api.groq.com/openai/v1
|
api_base: https://api.groq.com/openai/v1
|
||||||
api_key: '{{GROQ_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{GROQ_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart
|
# See https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart
|
||||||
- type: azure-openai
|
- type: azure-openai
|
||||||
api_base: https://{RESOURCE}.openai.azure.com
|
api_base: https://{RESOURCE}.openai.azure.com
|
||||||
api_key: '{{AZURE_OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{AZURE_OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
models:
|
models:
|
||||||
- name: gpt-4o # Model deployment name
|
- name: gpt-4o # Model deployment name
|
||||||
max_input_tokens: 128000
|
max_input_tokens: 128000
|
||||||
supports_vision: true
|
supports_vision: true
|
||||||
supports_function_calling: true
|
supports_function_calling: true
|
||||||
@@ -469,7 +454,7 @@ clients:
|
|||||||
# Specifies an application default credentials (adc) file
|
# Specifies an application default credentials (adc) file
|
||||||
# Run `gcloud auth application-default login` to initialize the ADC file
|
# Run `gcloud auth application-default login` to initialize the ADC file
|
||||||
# see https://cloud.google.com/docs/authentication/external/set-up-adc for more information
|
# see https://cloud.google.com/docs/authentication/external/set-up-adc for more information
|
||||||
adc_file: <gcloud-config-dir>/application_default_credentials.json # Optional
|
adc_file: <gcloud-config-dir>/application_default_credentials.json # Optional
|
||||||
patch:
|
patch:
|
||||||
chat_completions:
|
chat_completions:
|
||||||
'gemini-.*':
|
'gemini-.*':
|
||||||
@@ -486,76 +471,77 @@ clients:
|
|||||||
|
|
||||||
# See https://docs.aws.amazon.com/bedrock/latest/userguide/
|
# See https://docs.aws.amazon.com/bedrock/latest/userguide/
|
||||||
- type: bedrock
|
- type: bedrock
|
||||||
access_key_id: '{{AWS_ACCESS_KEY_ID}}' # You can either hard-code or inject secrets from the Coyote vault
|
access_key_id: '{{AWS_ACCESS_KEY_ID}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
secret_access_key: '{{AWS_SECRET_ACCESS_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
secret_access_key: '{{AWS_SECRET_ACCESS_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
region: xxx
|
region: xxx
|
||||||
session_token: xxx # Optional, only needed for temporary credentials
|
session_token: xxx # Optional, only needed for temporary credentials
|
||||||
|
|
||||||
# See https://developers.cloudflare.com/workers-ai/
|
# See https://developers.cloudflare.com/workers-ai/
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: cloudflare
|
name: cloudflare
|
||||||
api_base: https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/v1
|
api_base: https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/v1
|
||||||
api_key: '{{CLOUDFLARE_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{CLOUDFLARE_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html
|
# See https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: ernie
|
name: ernie
|
||||||
api_base: https://qianfan.baidubce.com/v2
|
api_base: https://qianfan.baidubce.com/v2
|
||||||
api_key: '{{BAIDU_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{BAIDU_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://dashscope.aliyun.com/
|
# See https://dashscope.aliyun.com/
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: qianwen
|
name: qianwen
|
||||||
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1
|
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
api_key: '{{ALIYUN_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{ALIYUN_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://cloud.tencent.com/product/hunyuan
|
# See https://cloud.tencent.com/product/hunyuan
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: hunyuan
|
name: hunyuan
|
||||||
api_base: https://api.hunyuan.cloud.tencent.com/v1
|
api_base: https://api.hunyuan.cloud.tencent.com/v1
|
||||||
api_key: '{{TENCENT_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{TENCENT_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://platform.moonshot.cn/docs/intro
|
# See https://platform.moonshot.cn/docs/intro
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: moonshot
|
name: moonshot
|
||||||
api_base: https://api.moonshot.cn/v1
|
api_base: https://api.moonshot.cn/v1
|
||||||
api_key: '{{MOONSHOT_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{MOONSHOT_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://platform.deepseek.com/api-docs/
|
# See https://platform.deepseek.com/api-docs/
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: deepseek
|
name: deepseek
|
||||||
api_base: https://api.deepseek.com
|
api_base: https://api.deepseek.com
|
||||||
api_key: '{{DEEPSEEK_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{DEEPSEEK_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://open.bigmodel.cn/dev/howuse/introduction
|
# See https://open.bigmodel.cn/dev/howuse/introduction
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: zhipuai
|
name: zhipuai
|
||||||
api_base: https://open.bigmodel.cn/api/paas/v4
|
api_base: https://open.bigmodel.cn/api/paas/v4
|
||||||
api_key: '{{ZHIPUAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{ZHIPUAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://platform.minimaxi.com/document/Fast%20access
|
# See https://platform.minimaxi.com/document/Fast%20access
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: minimax
|
name: minimax
|
||||||
api_base: https://api.minimax.chat/v1
|
api_base: https://api.minimax.chat/v1
|
||||||
api_key: '{{MINIMAX_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{MINIMAX_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://openrouter.ai/docs#quick-start
|
# See https://openrouter.ai/docs#quick-start
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: openrouter
|
name: openrouter
|
||||||
api_base: https://openrouter.ai/api/v1
|
api_base: https://openrouter.ai/api/v1
|
||||||
api_key: '{{OPENROUTER_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{OPENROUTER_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://github.com/marketplace/models
|
# See https://github.com/marketplace/models
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: github
|
name: github
|
||||||
api_base: https://models.inference.ai.azure.com
|
api_base: https://models.inference.ai.azure.com
|
||||||
api_key: '{{GITHUB_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{GITHUB_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://deepinfra.com/docs
|
# See https://deepinfra.com/docs
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: deepinfra
|
name: deepinfra
|
||||||
api_base: https://api.deepinfra.com/v1/openai
|
api_base: https://api.deepinfra.com/v1/openai
|
||||||
api_key: '{{DEEPINFRA_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{DEEPINFRA_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
|
|
||||||
# ----- RAG dedicated -----
|
# ----- RAG dedicated -----
|
||||||
|
|
||||||
@@ -563,10 +549,10 @@ clients:
|
|||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: jina
|
name: jina
|
||||||
api_base: https://api.jina.ai/v1
|
api_base: https://api.jina.ai/v1
|
||||||
api_key: '{{JINA_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{JINA_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
|
|
||||||
# See https://docs.voyageai.com/docs/introduction
|
# See https://docs.voyageai.com/docs/introduction
|
||||||
- type: openai-compatible
|
- type: openai-compatible
|
||||||
name: voyageai
|
name: voyageai
|
||||||
api_base: https://api.voyageai.com/v1
|
api_base: https://api.voyageai.com/v1
|
||||||
api_key: '{{VOYAGEAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
api_key: '{{VOYAGEAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
|
||||||
@@ -69,7 +69,7 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut functions = Functions::init(config.visible_tools.as_ref().unwrap_or(&Vec::new()))?;
|
let mut functions = Functions::init(config.visible_tools.as_deref())?;
|
||||||
if !mcp_registry.is_empty() && config.mcp_server_support {
|
if !mcp_registry.is_empty() && config.mcp_server_support {
|
||||||
functions.append_mcp_meta_functions(mcp_registry.server_features());
|
functions.append_mcp_meta_functions(mcp_registry.server_features());
|
||||||
}
|
}
|
||||||
|
|||||||
+91
-83
@@ -150,6 +150,9 @@ fn validate_no_template_in_secrets_provider(content: &str) -> Result<()> {
|
|||||||
const DARK_THEME: &[u8] = include_bytes!("../../assets/monokai-extended.theme.bin");
|
const DARK_THEME: &[u8] = include_bytes!("../../assets/monokai-extended.theme.bin");
|
||||||
const LIGHT_THEME: &[u8] = include_bytes!("../../assets/monokai-extended-light.theme.bin");
|
const LIGHT_THEME: &[u8] = include_bytes!("../../assets/monokai-extended-light.theme.bin");
|
||||||
|
|
||||||
|
/// Fully documented config skeleton the first-run wizard splices dynamic values into.
|
||||||
|
const CONFIG_TEMPLATE: &str = include_str!("../../assets/config-template.yaml");
|
||||||
|
|
||||||
const CONFIG_FILE_NAME: &str = "config.yaml";
|
const CONFIG_FILE_NAME: &str = "config.yaml";
|
||||||
const AGENT_GRAPH_FILE_NAME: &str = "graph.yaml";
|
const AGENT_GRAPH_FILE_NAME: &str = "graph.yaml";
|
||||||
const ROLES_DIR_NAME: &str = "roles";
|
const ROLES_DIR_NAME: &str = "roles";
|
||||||
@@ -180,27 +183,6 @@ const BUNDLE_MANIFEST_FILE: &str = "coyote-bundle.yaml";
|
|||||||
const SBX_MIXIN_KITS_DIR_NAME: &str = "sbx-mixin-kits";
|
const SBX_MIXIN_KITS_DIR_NAME: &str = "sbx-mixin-kits";
|
||||||
const GIT_DIR_NAME: &str = ".git";
|
const GIT_DIR_NAME: &str = ".git";
|
||||||
const GITIGNORE_FILE_NAME: &str = ".gitignore";
|
const GITIGNORE_FILE_NAME: &str = ".gitignore";
|
||||||
const DEFAULT_VISIBLE_TOOLS: [&str; 19] = [
|
|
||||||
"execute_command.sh",
|
|
||||||
"execute_py_code.py",
|
|
||||||
"execute_sql_code.sh",
|
|
||||||
"fetch_url_via_curl.sh",
|
|
||||||
"fs_cat.sh",
|
|
||||||
"fs_glob.sh",
|
|
||||||
"fs_grep.sh",
|
|
||||||
"fs_ls.sh",
|
|
||||||
"fs_mkdir.sh",
|
|
||||||
"fs_patch.sh",
|
|
||||||
"fs_read.sh",
|
|
||||||
"fs_rm.sh",
|
|
||||||
"fs_write.sh",
|
|
||||||
"ast_grep.sh",
|
|
||||||
"get_current_time.sh",
|
|
||||||
"get_current_weather.sh",
|
|
||||||
"search_wikipedia.sh",
|
|
||||||
"search_arxiv.sh",
|
|
||||||
"web_search_coyote.sh",
|
|
||||||
];
|
|
||||||
|
|
||||||
const CLIENTS_FIELD: &str = "clients";
|
const CLIENTS_FIELD: &str = "clients";
|
||||||
|
|
||||||
@@ -811,51 +793,20 @@ pub async fn create_config_file(config_path: &Path) -> Result<()> {
|
|||||||
|
|
||||||
let client = Select::new("API Provider (required):", list_client_types()).prompt()?;
|
let client = Select::new("API Provider (required):", list_client_types()).prompt()?;
|
||||||
|
|
||||||
let mut config = json!({});
|
|
||||||
let (model, clients_config) = create_client_config(client, &vault).await?;
|
let (model, clients_config) = create_client_config(client, &vault).await?;
|
||||||
config["model"] = model.into();
|
let secrets = match &provider_choice {
|
||||||
match &provider_choice {
|
None => json!({
|
||||||
None => {
|
"vault_password_file": vault.local_password_file()?.display().to_string()
|
||||||
config["vault_password_file"] =
|
}),
|
||||||
vault.local_password_file()?.display().to_string().into();
|
|
||||||
}
|
|
||||||
Some(provider) => {
|
Some(provider) => {
|
||||||
config["secrets_provider"] = serde_json::to_value(provider)
|
let provider = serde_json::to_value(provider)
|
||||||
.with_context(|| "failed to serialize secrets_provider config")?;
|
.with_context(|| "failed to serialize secrets_provider config")?;
|
||||||
|
json!({ "secrets_provider": provider })
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
config["stream"] = json!(true);
|
|
||||||
config["save"] = json!(true);
|
|
||||||
config["keybindings"] = json!("vi");
|
|
||||||
config["wrap"] = json!("auto");
|
|
||||||
config["wrap_code"] = json!(false);
|
|
||||||
config["function_calling_support"] = json!(true);
|
|
||||||
config["enabled_tools"] = json!(null);
|
|
||||||
config["visible_tools"] = json!(DEFAULT_VISIBLE_TOOLS);
|
|
||||||
config["mcp_server_support"] = json!(true);
|
|
||||||
config["enabled_mcp_servers"] = json!(null);
|
|
||||||
config["highlight"] = json!(true);
|
|
||||||
config["light_theme"] = json!(false);
|
|
||||||
config[CLIENTS_FIELD] = clients_config;
|
|
||||||
|
|
||||||
let config_data = serde_yaml::to_string(&config).with_context(|| "Failed to create config")?;
|
let config_data = render_config_template(&model, Some(&secrets), &clients_config)?;
|
||||||
let config_data = format!(
|
write_config_file(config_path, &config_data)
|
||||||
"# see https://github.com/Dark-Alex-17/coyote/blob/main/config.example.yaml\n\n{config_data}"
|
|
||||||
);
|
|
||||||
|
|
||||||
ensure_parent_exists(config_path)?;
|
|
||||||
std::fs::write(config_path, config_data)
|
|
||||||
.with_context(|| format!("Failed to write to '{}'", config_path.display()))?;
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
use std::os::unix::prelude::PermissionsExt;
|
|
||||||
let perms = std::fs::Permissions::from_mode(0o600);
|
|
||||||
std::fs::set_permissions(config_path, perms)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("✓ Saved the config file to '{}'.\n", config_path.display());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_config_file_sandbox(config_path: &Path) -> Result<()> {
|
async fn create_config_file_sandbox(config_path: &Path) -> Result<()> {
|
||||||
@@ -865,7 +816,7 @@ async fn create_config_file_sandbox(config_path: &Path) -> Result<()> {
|
|||||||
"Running in sandbox mode — your API provider credentials are managed by your host Coyote configuration if configured."
|
"Running in sandbox mode — your API provider credentials are managed by your host Coyote configuration if configured."
|
||||||
);
|
);
|
||||||
|
|
||||||
let oai_api_base = client::OPENAI_COMPATIBLE_PROVIDERS
|
let oai_api_base = OPENAI_COMPATIBLE_PROVIDERS
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(name, _)| *name == client)
|
.find(|(name, _)| *name == client)
|
||||||
.map(|(_, url)| *url);
|
.map(|(_, url)| *url);
|
||||||
@@ -878,13 +829,13 @@ async fn create_config_file_sandbox(config_path: &Path) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
api_base.to_string()
|
api_base.to_string()
|
||||||
};
|
};
|
||||||
serde_json::json!({
|
json!({
|
||||||
"type": "openai-compatible",
|
"type": "openai-compatible",
|
||||||
"name": client,
|
"name": client,
|
||||||
"api_base": api_base_str,
|
"api_base": api_base_str,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
serde_json::json!({ "type": client })
|
json!({ "type": client })
|
||||||
};
|
};
|
||||||
|
|
||||||
if client::client_type_supports_oauth(client) {
|
if client::client_type_supports_oauth(client) {
|
||||||
@@ -898,27 +849,35 @@ async fn create_config_file_sandbox(config_path: &Path) -> Result<()> {
|
|||||||
|
|
||||||
let model = set_client_models_config(&mut client_config, client).await?;
|
let model = set_client_models_config(&mut client_config, client).await?;
|
||||||
|
|
||||||
let mut config = serde_json::json!({});
|
let config_data = render_config_template(&model, None, &json!([client_config]))?;
|
||||||
config["model"] = model.into();
|
write_config_file(config_path, &config_data)
|
||||||
config["stream"] = serde_json::json!(true);
|
}
|
||||||
config["save"] = serde_json::json!(true);
|
|
||||||
config["keybindings"] = serde_json::json!("vi");
|
|
||||||
config["wrap"] = serde_json::json!("auto");
|
|
||||||
config["wrap_code"] = serde_json::json!(false);
|
|
||||||
config["function_calling_support"] = serde_json::json!(true);
|
|
||||||
config["enabled_tools"] = serde_json::json!(null);
|
|
||||||
config["visible_tools"] = serde_json::json!(DEFAULT_VISIBLE_TOOLS);
|
|
||||||
config["mcp_server_support"] = serde_json::json!(true);
|
|
||||||
config["enabled_mcp_servers"] = serde_json::json!(null);
|
|
||||||
config["highlight"] = serde_json::json!(true);
|
|
||||||
config["light_theme"] = serde_json::json!(false);
|
|
||||||
config[CLIENTS_FIELD] = serde_json::json!(vec![client_config]);
|
|
||||||
|
|
||||||
let config_data = serde_yaml::to_string(&config).with_context(|| "Failed to create config")?;
|
fn render_config_template(
|
||||||
let config_data = format!(
|
model: &str,
|
||||||
"# see https://github.com/Dark-Alex-17/coyote/blob/main/config.example.yaml\n\n{config_data}"
|
secrets: Option<&serde_json::Value>,
|
||||||
);
|
clients: &serde_json::Value,
|
||||||
|
) -> Result<String> {
|
||||||
|
let to_yaml = |value: &serde_json::Value| {
|
||||||
|
serde_yaml::to_string(value).with_context(|| "Failed to create config")
|
||||||
|
};
|
||||||
|
|
||||||
|
let model_block = to_yaml(&json!({ "model": model }))?;
|
||||||
|
let secrets_block = match secrets {
|
||||||
|
Some(value) => to_yaml(value)?,
|
||||||
|
None => "# Sandbox mode: no vault provider is configured; secrets are provisioned\n\
|
||||||
|
# from the host when the sandbox is created.\n"
|
||||||
|
.to_string(),
|
||||||
|
};
|
||||||
|
let clients_block = to_yaml(&json!({ CLIENTS_FIELD: clients }))?;
|
||||||
|
|
||||||
|
Ok(CONFIG_TEMPLATE
|
||||||
|
.replacen("__MODEL_BLOCK__\n", &model_block, 1)
|
||||||
|
.replacen("__SECRETS_BLOCK__\n", &secrets_block, 1)
|
||||||
|
.replacen("__CLIENTS_BLOCK__\n", &clients_block, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_config_file(config_path: &Path, config_data: &str) -> Result<()> {
|
||||||
ensure_parent_exists(config_path)?;
|
ensure_parent_exists(config_path)?;
|
||||||
std::fs::write(config_path, config_data)
|
std::fs::write(config_path, config_data)
|
||||||
.with_context(|| format!("Failed to write to '{}'", config_path.display()))?;
|
.with_context(|| format!("Failed to write to '{}'", config_path.display()))?;
|
||||||
@@ -1174,6 +1133,55 @@ clients:
|
|||||||
assert_eq!(cfg.enabled_macros, None);
|
assert_eq!(cfg.enabled_macros, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_template_renders_parseable_config() {
|
||||||
|
let secrets = json!({ "vault_password_file": "/home/user/.coyote_password" });
|
||||||
|
let clients = json!([{ "type": "openai", "api_key": "sk-test" }]);
|
||||||
|
|
||||||
|
let rendered = render_config_template("openai:gpt-4o", Some(&secrets), &clients).unwrap();
|
||||||
|
|
||||||
|
assert!(!rendered.contains("__MODEL_BLOCK__"));
|
||||||
|
assert!(!rendered.contains("__SECRETS_BLOCK__"));
|
||||||
|
assert!(!rendered.contains("__CLIENTS_BLOCK__"));
|
||||||
|
|
||||||
|
let cfg = Config::load_from_str(&rendered).unwrap();
|
||||||
|
assert_eq!(cfg.model_id, "openai:gpt-4o");
|
||||||
|
assert_eq!(
|
||||||
|
cfg.vault_password_file,
|
||||||
|
Some(PathBuf::from("/home/user/.coyote_password"))
|
||||||
|
);
|
||||||
|
assert!(cfg.secrets_provider.is_none());
|
||||||
|
assert_eq!(cfg.keybindings, "emacs");
|
||||||
|
assert!(cfg.save);
|
||||||
|
assert_eq!(cfg.wrap.as_deref(), Some("auto"));
|
||||||
|
assert!(cfg.visible_tools.is_none());
|
||||||
|
assert!(cfg.mapping_tools.is_empty());
|
||||||
|
assert!(cfg.document_loaders.is_empty());
|
||||||
|
assert_eq!(cfg.compression_threshold, 4000);
|
||||||
|
assert!(cfg.theme.is_none());
|
||||||
|
assert_eq!(cfg.clients.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_template_renders_parseable_sandbox_config() {
|
||||||
|
let clients = json!([{ "type": "claude" }]);
|
||||||
|
|
||||||
|
let rendered =
|
||||||
|
render_config_template("claude:claude-sonnet-4-20250514", None, &clients).unwrap();
|
||||||
|
|
||||||
|
assert!(!rendered.contains("__SECRETS_BLOCK__"));
|
||||||
|
|
||||||
|
let cfg = Config::load_from_str(&rendered).unwrap();
|
||||||
|
assert_eq!(cfg.model_id, "claude:claude-sonnet-4-20250514");
|
||||||
|
assert!(cfg.vault_password_file.is_none());
|
||||||
|
assert!(cfg.secrets_provider.is_none());
|
||||||
|
assert_eq!(cfg.keybindings, "emacs");
|
||||||
|
assert!(cfg.save);
|
||||||
|
assert!(cfg.visible_tools.is_none());
|
||||||
|
assert_eq!(cfg.compression_threshold, 4000);
|
||||||
|
assert_eq!(cfg.clients.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_enabled_macros_empty_string_is_some_empty() {
|
fn config_enabled_macros_empty_string_is_some_empty() {
|
||||||
let cfg: Config = serde_yaml::from_str("enabled_macros: \"\"").unwrap();
|
let cfg: Config = serde_yaml::from_str("enabled_macros: \"\"").unwrap();
|
||||||
|
|||||||
@@ -4266,7 +4266,7 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut functions = Functions::init(app.visible_tools.as_ref().unwrap_or(&Vec::new()))?;
|
let mut functions = Functions::init(app.visible_tools.as_deref())?;
|
||||||
if self.working_mode.is_repl() {
|
if self.working_mode.is_repl() {
|
||||||
functions.append_user_interaction_functions();
|
functions.append_user_interaction_functions();
|
||||||
}
|
}
|
||||||
@@ -4687,7 +4687,7 @@ impl RequestContext {
|
|||||||
|
|
||||||
pub fn exit_agent(&mut self, app: &AppConfig) -> Result<()> {
|
pub fn exit_agent(&mut self, app: &AppConfig) -> Result<()> {
|
||||||
self.exit_session()?;
|
self.exit_session()?;
|
||||||
let mut functions = Functions::init(app.visible_tools.as_ref().unwrap_or(&Vec::new()))?;
|
let mut functions = Functions::init(app.visible_tools.as_deref())?;
|
||||||
if self.working_mode.is_repl() {
|
if self.working_mode.is_repl() {
|
||||||
functions.append_user_interaction_functions();
|
functions.append_user_interaction_functions();
|
||||||
}
|
}
|
||||||
|
|||||||
+132
-34
@@ -214,6 +214,57 @@ fn tool_source_stems() -> Result<HashSet<String>> {
|
|||||||
Ok(stems)
|
Ok(stems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn all_tool_source_files() -> Result<Vec<String>> {
|
||||||
|
let tools_dir = paths::global_tools_dir();
|
||||||
|
if !tools_dir.exists() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_names = Vec::new();
|
||||||
|
for entry in fs::read_dir(&tools_dir)? {
|
||||||
|
let path = entry?.path();
|
||||||
|
if path.is_file()
|
||||||
|
&& let Some(name) = path.file_name().and_then(OsStr::to_str)
|
||||||
|
{
|
||||||
|
file_names.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(dedupe_tool_files_by_stem(file_names))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dedupe_tool_files_by_stem(file_names: Vec<String>) -> Vec<String> {
|
||||||
|
fn extension_rank(name: &str) -> Option<usize> {
|
||||||
|
let ext = Path::new(name).extension().and_then(OsStr::to_str)?;
|
||||||
|
match Language::from_extension(ext) {
|
||||||
|
Language::Bash => Some(0),
|
||||||
|
Language::Python => Some(1),
|
||||||
|
Language::TypeScript => Some(2),
|
||||||
|
Language::Unsupported => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut best: HashMap<String, (usize, String)> = HashMap::new();
|
||||||
|
for name in file_names {
|
||||||
|
let Some(rank) = extension_rank(&name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(stem) = Path::new(&name).file_stem().and_then(OsStr::to_str) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
match best.get(stem) {
|
||||||
|
Some((best_rank, _)) if *best_rank <= rank => {}
|
||||||
|
_ => {
|
||||||
|
best.insert(stem.to_string(), (rank, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut files: Vec<String> = best.into_values().map(|(_, name)| name).collect();
|
||||||
|
files.sort();
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
fn bin_entry_stem(file_name: &str) -> &str {
|
fn bin_entry_stem(file_name: &str) -> &str {
|
||||||
let name = file_name.strip_prefix("run-").unwrap_or(file_name);
|
let name = file_name.strip_prefix("run-").unwrap_or(file_name);
|
||||||
Path::new(name)
|
Path::new(name)
|
||||||
@@ -587,18 +638,23 @@ impl Functions {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(visible_tools: &[String]) -> Result<Self> {
|
pub fn init(visible_tools: Option<&[String]>) -> Result<Self> {
|
||||||
Self::remove_stale_global_function_binaries()?;
|
Self::remove_stale_global_function_binaries()?;
|
||||||
|
|
||||||
|
let (visible_tools, lenient) = match visible_tools {
|
||||||
|
Some(tools) => (tools.to_vec(), false),
|
||||||
|
None => (all_tool_source_files()?, true),
|
||||||
|
};
|
||||||
|
|
||||||
let declarations = Self {
|
let declarations = Self {
|
||||||
declarations: Self::build_global_tool_declarations(visible_tools)?,
|
declarations: Self::build_global_tool_declarations(&visible_tools, lenient)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Building global function binaries in {}",
|
"Building global function binaries in {}",
|
||||||
paths::functions_bin_dir().display()
|
paths::functions_bin_dir().display()
|
||||||
);
|
);
|
||||||
Self::build_global_function_binaries(visible_tools, None)?;
|
Self::build_global_function_binaries(&visible_tools, None, lenient)?;
|
||||||
|
|
||||||
Ok(declarations)
|
Ok(declarations)
|
||||||
}
|
}
|
||||||
@@ -608,13 +664,13 @@ impl Functions {
|
|||||||
|
|
||||||
let global_tools_declarations = if !global_tools.is_empty() {
|
let global_tools_declarations = if !global_tools.is_empty() {
|
||||||
info!("Loading global tools for agent: {name}: {global_tools:?}");
|
info!("Loading global tools for agent: {name}: {global_tools:?}");
|
||||||
let tools_declarations = Self::build_global_tool_declarations(global_tools)?;
|
let tools_declarations = Self::build_global_tool_declarations(global_tools, false)?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Building global function binaries required by agent: {name} in {}",
|
"Building global function binaries required by agent: {name} in {}",
|
||||||
paths::functions_bin_dir().display()
|
paths::functions_bin_dir().display()
|
||||||
);
|
);
|
||||||
Self::build_global_function_binaries(global_tools, Some(name))?;
|
Self::build_global_function_binaries(global_tools, Some(name), false)?;
|
||||||
tools_declarations
|
tools_declarations
|
||||||
} else {
|
} else {
|
||||||
debug!("No global tools found for agent: {}", name);
|
debug!("No global tools found for agent: {}", name);
|
||||||
@@ -964,13 +1020,17 @@ impl Functions {
|
|||||||
|
|
||||||
fn build_global_tool_declarations(
|
fn build_global_tool_declarations(
|
||||||
enabled_tools: &[String],
|
enabled_tools: &[String],
|
||||||
|
lenient: bool,
|
||||||
) -> Result<Vec<FunctionDeclaration>> {
|
) -> Result<Vec<FunctionDeclaration>> {
|
||||||
let global_tools_directory = paths::global_tools_dir();
|
let global_tools_directory = paths::global_tools_dir();
|
||||||
let mut function_declarations = Vec::new();
|
let mut function_declarations = Vec::new();
|
||||||
|
|
||||||
for tool in enabled_tools {
|
for tool in enabled_tools {
|
||||||
let declaration = Self::generate_declarations(&global_tools_directory.join(tool))?;
|
match Self::generate_declarations(&global_tools_directory.join(tool)) {
|
||||||
function_declarations.extend(declaration);
|
Ok(declaration) => function_declarations.extend(declaration),
|
||||||
|
Err(err) if lenient => warn!("Skipping tool {tool}: {err}"),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(function_declarations)
|
Ok(function_declarations)
|
||||||
@@ -1032,41 +1092,50 @@ impl Functions {
|
|||||||
fn build_global_function_binaries(
|
fn build_global_function_binaries(
|
||||||
enabled_tools: &[String],
|
enabled_tools: &[String],
|
||||||
agent_name: Option<&str>,
|
agent_name: Option<&str>,
|
||||||
|
lenient: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for tool in enabled_tools {
|
for tool in enabled_tools {
|
||||||
let language = Language::from(
|
match Self::build_global_function_binary(tool, agent_name) {
|
||||||
&Path::new(&tool)
|
Ok(()) => {}
|
||||||
.extension()
|
Err(err) if lenient => warn!("Skipping binary for tool {tool}: {err}"),
|
||||||
.and_then(OsStr::to_str)
|
Err(err) => return Err(err),
|
||||||
.map(|s| s.to_lowercase())
|
|
||||||
.ok_or_else(|| {
|
|
||||||
anyhow::format_err!("Unable to extract file extension from path: {tool:?}")
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
let binary_name = Path::new(&tool)
|
|
||||||
.file_stem()
|
|
||||||
.and_then(OsStr::to_str)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
anyhow::format_err!("Unable to extract file name from path: {tool:?}")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if language == Language::Unsupported {
|
|
||||||
bail!("Unsupported tool file extension: {}", language.as_ref());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let tool_path = paths::global_tools_dir().join(tool);
|
|
||||||
let custom_runtime = extract_shebang_runtime(&tool_path);
|
|
||||||
Self::build_binaries(
|
|
||||||
binary_name,
|
|
||||||
language,
|
|
||||||
BinaryType::Tool(agent_name),
|
|
||||||
custom_runtime.as_deref(),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_global_function_binary(tool: &str, agent_name: Option<&str>) -> Result<()> {
|
||||||
|
let language = Language::from(
|
||||||
|
&Path::new(tool)
|
||||||
|
.extension()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.map(|s| s.to_lowercase())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow::format_err!("Unable to extract file extension from path: {tool:?}")
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
let binary_name = Path::new(tool)
|
||||||
|
.file_stem()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow::format_err!("Unable to extract file name from path: {tool:?}")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if language == Language::Unsupported {
|
||||||
|
bail!("Unsupported tool file extension: {}", language.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
let tool_path = paths::global_tools_dir().join(tool);
|
||||||
|
let custom_runtime = extract_shebang_runtime(&tool_path);
|
||||||
|
Self::build_binaries(
|
||||||
|
binary_name,
|
||||||
|
language,
|
||||||
|
BinaryType::Tool(agent_name),
|
||||||
|
custom_runtime.as_deref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_stale_agent_bin_entries(name: &str) -> Result<()> {
|
fn remove_stale_agent_bin_entries(name: &str) -> Result<()> {
|
||||||
let agent_bin_directory = paths::agent_bin_dir(name);
|
let agent_bin_directory = paths::agent_bin_dir(name);
|
||||||
|
|
||||||
@@ -2603,6 +2672,35 @@ mod tests {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{mem, process};
|
use std::{mem, process};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dedupe_tool_files_prefers_sh_over_py_over_ts() {
|
||||||
|
let files = vec![
|
||||||
|
"get_current_weather.ts".to_string(),
|
||||||
|
"get_current_weather.py".to_string(),
|
||||||
|
"get_current_weather.sh".to_string(),
|
||||||
|
"fetch.ts".to_string(),
|
||||||
|
"fetch.py".to_string(),
|
||||||
|
"demo_ts.ts".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dedupe_tool_files_by_stem(files),
|
||||||
|
vec!["demo_ts.ts", "fetch.py", "get_current_weather.sh"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dedupe_tool_files_skips_unsupported_extensions() {
|
||||||
|
let files = vec![
|
||||||
|
"notes.md".to_string(),
|
||||||
|
"tool.sh".to_string(),
|
||||||
|
"README".to_string(),
|
||||||
|
"archive.tar.gz".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(dedupe_tool_files_by_stem(files), vec!["tool.sh"]);
|
||||||
|
}
|
||||||
|
|
||||||
fn call(name: &str, id: Option<&str>) -> ToolCall {
|
fn call(name: &str, id: Option<&str>) -> ToolCall {
|
||||||
ToolCall::new(name.to_string(), json!({}), id.map(|s| s.to_string()))
|
ToolCall::new(name.to_string(), json!({}), id.map(|s| s.to_string()))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user