Skip to content

Repository files navigation

LetCode

letcode is a terminal Agent written in Rust.

Test Changelog MIT License | Apache-2.0 License

中文 | English

letcode TUI

It provides an opencode-style TUI based on Ratatui, and also keeps a REPL CLI mode.

Technical documentation

Build and run

cargo build
cargo test
cargo fmt --check

Run the default TUI:

cargo run

Run the line-based CLI:

cargo run -- --cli

CLI mode can also be selected with cli or repl. TUI can be selected explicitly with --tui or tui.

Show the installed version and check for a newer GitHub release:

letcode --version
letcode update check

Update a release-installed binary after an interactive confirmation:

letcode update

The TUI supports English (en) and Simplified Chinese (zh-CN). Use /language or its /lang alias to switch languages at runtime.

External dependencies

Building from source requires the Rust toolchain. Some built-in tools also invoke the following external programs, which must be available on PATH:

Program Used by Requirement
git git__status, git__diff, git__log, and the TUI branch indicator Recommended; only Git-related capabilities are unavailable when missing
rg search__rg text search Recommended; the search tool is unavailable when missing
ast-grep code__ast_search and code__ast_replace_preview Optional; only AST tools are unavailable when missing

In addition, shell__exec and local MCP servers depend on the system commands they invoke, while web__fetch and remote MCP require network access.

Configuration

letcode loads configuration from:

~/.config/letcode/letcode.toml

Configuration example:

# Optional; defaults to the first provider in the file.
active_provider = "openai"
# Optional; defaults to false.
fast_mode = false

# Optional.
# tool_timeout_secs, sessions_dir, and log_file have defaults.
# max_iterations and max_tool_calls have no limit when omitted.
[global]
# max_iterations = 64
# max_tool_calls = 128
# tool_timeout_secs = 60
sessions_dir = "sessions"
log_file = "logs/combined.log"

# Optional; by default, recent context is preserved according to the active model's input budget.
[global.compaction]
# preserve_recent_tokens = 12000

# Optional; values below are the defaults.
[global.retry]
enabled = true
max_attempts = 50
max_recovery_attempts = 3
initial_delay_secs = 1
backoff_multiplier = 2.0
jitter_secs = 1

# Optional; values below are the defaults. Idle sessions older than
# older_than_days are compressed in the background; archived sessions stay
# listed alongside live ones and are restored automatically when resumed
# (the first resume of an archived session takes a little longer).
[global.session_archive]
enabled = true
older_than_days = 7

# Optional; defaults to default. Values: safe | default | auto | yolo.
[permissions]
mode = "default" # solo remains accepted as a yolo alias

# Experimental: auto mode asks Typesafe Jev instead of the chat reviewer when
# [agents.reviewer] routes to a provider marked reviewer = "jev". One review
# posts a single choice question to that provider's base URL with its credential
# and the route's model, and returns probabilities instead of prose.
# [providers.typesafe]
# protocol = "responses"
# flavor = "standard"
# default_model = "jev-latest"
# reviewer = "jev"
# [providers.typesafe.auth]
# type = "bearer"
# credential_env = "TYPESAFE_API_KEY"
# [providers.typesafe.endpoints]
# base_url = "https://api.typesafe.ai"
# [providers.typesafe.models."jev-latest"]
# display = "Jev"
#
# [agents.reviewer]
# provider = "typesafe"
# model = "jev-latest"

# Optional; choose a default route and per-invocation allowed routes for an expert.
# [agents.explorer]
# provider = "openai"
# model = "gpt-5.5"
# allowed_models = ["openai/gpt-5.5"]
# The same shape applies to fixer, oracle, designer, librarian, general, reviewer, and historian.
# reviewer and historian are internal system experts, so no agent__* tool targets them.
# Historian publishes a structured JSON contract: give it a route that declares
# capabilities.generation.structured_output = "json_schema", so the endpoint constrains the JSON
# syntax. A json_object route only states the intent there, and a malformed response is rejected whole.

# Optional; this can only narrow parallelism declared by a tool itself.
[tools.parallelism]
# "fs__read" = "parallel"
# "web__fetch" = "exclusive"

# Optional local MCP server.
# [mcp.example_local]
# type = "local"
# command = ["/path/to/mcp-server", "--stdio"]
# environment = { FOO = "bar" }
# enabled = true
# timeout = 5000

# Optional remote MCP server; OAuth is not currently supported.
# [mcp.example_remote]
# type = "remote"
# url = "https://example.com/mcp"
# headers = { Authorization = "Bearer ..." }
# enabled = true
# timeout = 10000

# Required: configure at least one provider with at least one model.
[providers.openai]
protocol = "responses" # responses | completions | anthropic
flavor = "standard" # standard | deepseek
# Required; must reference a model configured for this provider.
default_model = "gpt-5.5"

[providers.openai.auth]
type = "bearer" # bearer | header | query | none
credential_env = "OPENAI_API_KEY"
# credential = "YOUR_API_KEY" # use this instead of credential_env when appropriate

[providers.openai.endpoints]
base_url = "https://api.openai.com/v1"
[providers.openai.endpoints.responses]
path = "responses"

# Optional provider connection settings.
# [providers.openai.transport]
# connect_timeout_secs = 10
# no_proxy_loopback = true

# Required: each provider needs at least one model; every field inside the model is optional.
[providers.openai.models."gpt-5.5"]
display = "GPT-5.5"
# protocol = "completions" # overrides the provider protocol
# flavor = "standard" # overrides the provider flavor; deepseek selects the explicit DeepSeek profile
# strategy = "astra" # default | astra | deepseek; inferred from the model name when omitted
# context_window = 400000
# effective_input_limit_tokens = 256000

# Optional model transport; defaults to HTTP/SSE. Set websocket = true for normal Agent
# turns using Responses on a WebSocket-capable endpoint. Title generation and compaction
# one-shot requests remain on HTTP/SSE.
[providers.openai.models."gpt-5.5".transport]
websocket = false

# Capability flags default to false when omitted.
[providers.openai.models."gpt-5.5".capabilities]
tools = true
parallel_tool_calls = true
reasoning = true
input_images = false
tool_result_images = false
priority_service = false
[providers.openai.models."gpt-5.5".capabilities.generation]
temperature = true
top_p = true
max_output_tokens = true
reasoning = true
reasoning_summary = true
text_verbosity = true
parallel_tool_calls = true
# structured_output = "json_schema" # json_object | json_schema; omit when the endpoint enforces neither

[providers.openai.models."gpt-5.5".generation]
temperature = 0.2
top_p = 1.0
max_output_tokens = 128000
reasoning_effort = "medium"
# Optional; restricts selectable reasoning levels and the TUI cycle order.
reasoning_efforts = ["none", "low", "medium", "high", "max"]
reasoning_summary = "auto" # auto | concise | detailed
text_verbosity = "medium" # low | medium | high
parallel_tool_calls = true
# async_tools = ["web__fetch"] # tool names; Astra strategy with the responses protocol and capabilities.tools = true

[providers.openai.models."gpt-5.5".cache]
# enabled = true declares provider-native prompt cache hints and sends them.
enabled = false
# retention = "in_memory" # in_memory | 24h; set when cache is enabled
# namespace = "openai"

# Adapter-specific settings are validated by the selected protocol binding.
# Anthropic routes may set, for example:
# anthropic_thinking = { mode = "adaptive" } # required whenever reasoning_effort is configured
# anthropic_betas = ["context-1m-2025-08-07"]
[providers.openai.models."gpt-5.5".protocol_settings]

# Request-compatibility values; whether they are used is decided per session by /fake.
# [fake.identity]
# installation_id = "00000000-0000-4000-8000-000000000000" # generated and persisted when omitted
# agent_name = "Hypatia" # not reported when omitted
# [fake.clock]
# timezone = "Asia/Shanghai" # IANA name; read from the host when omitted
# date = "2026-09-12" # resolved from the local clock in that zone when omitted
# [fake.client]
# version = "0.153.4"
# originator = "Codex Desktop"
# os = "Mac OS 26.4.1"
# arch = "aarch64"
# terminal = "Apple_Terminal"
# beta_features = ["remote_compaction_v2"]
# [fake.environment]
# sandbox = "none"
# sandbox_mode = "danger-full-access"
# auto_review_enabled = false
# node_repl_auto_review_required = false
# node_repl_disabled = false
# cwd = "/Users/me/project"
# workspace = "/Users/me/project"
# shell = "zsh"
# git_commit_hash = "1f0c3a2"
# git_remote_url = "git@github.com:owner/repo.git"
# git_has_changes = false
# [fake.extra] # free-form; at most 16 entries
# custom_flag = "1"

Provider credentials can use credential_env or the default environment variable named from the provider, for example OPENAI_API_KEY; endpoint URLs and protocol-specific paths are configured under endpoints.

Relative sessions_dir and log_file paths are resolved relative to the config file directory.

Optional Langfuse/OpenTelemetry tracing is off by default. Enable it with LETCODE_LANGFUSE_ENABLED=true, and set LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, and optional LANGFUSE_HOST (or the same variables in a local .env). Missing credentials leave tracing disabled without stopping the agent.

Changelog

See CHANGELOG.md for release notes.

License

This project is dual-licensed under the MIT License OR the Apache License 2.0. You may choose either license when using, modifying, or redistributing this project.

About

A opencode-like Agent written in Rust.

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages