Skip to content

Commit 6aa1700

Browse files
authored
Merge pull request #41 from tcdent/claude/library-refactor-6QbUF
Add library support for using codey as a Rust dependency
2 parents 0ac6441 + 6cf2f6d commit 6aa1700

11 files changed

Lines changed: 526 additions & 90 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ description = "A terminal-based AI coding assistant"
77
license = "MIT"
88
repository = "https://github.com/tcdent/codey"
99

10+
[lib]
11+
name = "codey"
12+
path = "src/lib.rs"
13+
14+
[[bin]]
15+
name = "codey"
16+
path = "src/main.rs"
17+
1018
[dependencies]
1119
# TUI
1220
ratatui = { version = "0.30.0-beta.0", features = ["scrolling-regions"] }

LIBRARY.md

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# Using Codey as a Library
2+
3+
Codey can be used as a library to create AI agents with custom system prompts and tools in your Rust projects.
4+
5+
## Installation
6+
7+
Add codey to your `Cargo.toml`:
8+
9+
```toml
10+
[dependencies]
11+
codey = { git = "https://github.com/tcdent/codey" }
12+
tokio = { version = "1", features = ["full"] }
13+
serde_json = "1"
14+
```
15+
16+
### Patched Dependencies
17+
18+
Codey uses a patched version of the `genai` crate. To use Codey as a library, you'll need to apply the same patch in your project's `Cargo.toml`:
19+
20+
```toml
21+
[patch.crates-io]
22+
genai = { git = "https://github.com/tcdent/codey", branch = "main" }
23+
```
24+
25+
Alternatively, clone the codey repository and reference the patched genai locally:
26+
27+
```toml
28+
[patch.crates-io]
29+
genai = { path = "../codey/lib/genai" }
30+
```
31+
32+
Note: Run `make patch` in the codey repository first to download and patch the dependencies.
33+
34+
## Basic Usage (No Tools)
35+
36+
```rust
37+
use codey::{Agent, AgentRuntimeConfig, AgentStep, RequestMode, ToolRegistry};
38+
39+
#[tokio::main]
40+
async fn main() {
41+
let mut agent = Agent::new(
42+
AgentRuntimeConfig::default(),
43+
"You are a helpful assistant.",
44+
None, // uses ANTHROPIC_API_KEY env var
45+
ToolRegistry::empty(),
46+
);
47+
48+
agent.send_request("What is the capital of France?", RequestMode::Normal);
49+
50+
while let Some(step) = agent.next().await {
51+
match step {
52+
AgentStep::TextDelta(text) => print!("{}", text),
53+
AgentStep::Finished { .. } => break,
54+
AgentStep::Error(e) => {
55+
eprintln!("Error: {}", e);
56+
break;
57+
}
58+
_ => {}
59+
}
60+
}
61+
}
62+
```
63+
64+
## Custom Tools
65+
66+
You can define custom tools using `SimpleTool` and handle their execution yourself.
67+
68+
### Defining Tools
69+
70+
```rust
71+
use codey::{SimpleTool, ToolRegistry};
72+
use serde_json::json;
73+
use std::sync::Arc;
74+
75+
// Define a tool
76+
let weather_tool = SimpleTool::new(
77+
"get_weather",
78+
"Get the current weather for a location",
79+
json!({
80+
"type": "object",
81+
"properties": {
82+
"location": {
83+
"type": "string",
84+
"description": "City name, e.g. 'San Francisco'"
85+
}
86+
},
87+
"required": ["location"]
88+
}),
89+
);
90+
91+
// Register tools
92+
let mut tools = ToolRegistry::empty();
93+
tools.register(Arc::new(weather_tool));
94+
```
95+
96+
### Handling Tool Calls
97+
98+
When the LLM wants to use a tool, you'll receive an `AgentStep::ToolRequest`. You must execute the tool and submit the result back to the agent:
99+
100+
```rust
101+
use codey::{Agent, AgentRuntimeConfig, AgentStep, RequestMode, SimpleTool, ToolCall, ToolRegistry};
102+
use serde_json::json;
103+
use std::sync::Arc;
104+
105+
#[tokio::main]
106+
async fn main() {
107+
// Set up tools
108+
let weather_tool = SimpleTool::new(
109+
"get_weather",
110+
"Get the current weather for a location",
111+
json!({
112+
"type": "object",
113+
"properties": {
114+
"location": { "type": "string" }
115+
},
116+
"required": ["location"]
117+
}),
118+
);
119+
120+
let mut tools = ToolRegistry::empty();
121+
tools.register(Arc::new(weather_tool));
122+
123+
// Create agent with tools
124+
let mut agent = Agent::new(
125+
AgentRuntimeConfig::default(),
126+
"You are a helpful assistant with access to weather data.",
127+
None,
128+
tools,
129+
);
130+
131+
agent.send_request("What's the weather in Paris?", RequestMode::Normal);
132+
133+
loop {
134+
match agent.next().await {
135+
Some(AgentStep::TextDelta(text)) => print!("{}", text),
136+
137+
Some(AgentStep::ToolRequest(calls)) => {
138+
// Handle each tool call
139+
for call in calls {
140+
let result = execute_tool(&call);
141+
agent.submit_tool_result(&call.call_id, result);
142+
}
143+
// Continue processing after submitting results
144+
}
145+
146+
Some(AgentStep::Finished { .. }) => {
147+
println!();
148+
break;
149+
}
150+
151+
Some(AgentStep::Error(e)) => {
152+
eprintln!("Error: {}", e);
153+
break;
154+
}
155+
156+
None => break,
157+
_ => {}
158+
}
159+
}
160+
}
161+
162+
fn execute_tool(call: &ToolCall) -> String {
163+
match call.name.as_str() {
164+
"get_weather" => {
165+
let location = call.params["location"].as_str().unwrap_or("unknown");
166+
// Your actual implementation here
167+
format!("Weather in {}: Sunny, 22°C", location)
168+
}
169+
_ => format!("Unknown tool: {}", call.name),
170+
}
171+
}
172+
```
173+
174+
### `ToolCall` Structure
175+
176+
When you receive a tool request, each `ToolCall` contains:
177+
178+
```rust
179+
pub struct ToolCall {
180+
pub call_id: String, // Unique ID for this call (use with submit_tool_result)
181+
pub name: String, // Tool name
182+
pub params: serde_json::Value, // Parameters from the LLM
183+
// ... other fields
184+
}
185+
```
186+
187+
## Public API Reference
188+
189+
### `Agent`
190+
191+
The main agent type for conversations with Claude.
192+
193+
```rust
194+
let mut agent = Agent::new(config, system_prompt, oauth, tools);
195+
agent.send_request("Hello!", RequestMode::Normal);
196+
while let Some(step) = agent.next().await { /* ... */ }
197+
agent.submit_tool_result(&call_id, result);
198+
```
199+
200+
### `AgentRuntimeConfig`
201+
202+
```rust
203+
let config = AgentRuntimeConfig {
204+
model: "claude-sonnet-4-20250514".to_string(),
205+
max_tokens: 8192,
206+
thinking_budget: 2_000,
207+
max_retries: 5,
208+
compaction_thinking_budget: 8_000,
209+
};
210+
211+
// Or use defaults
212+
let config = AgentRuntimeConfig::default();
213+
```
214+
215+
### `AgentStep`
216+
217+
Events emitted during processing:
218+
219+
- `TextDelta(String)` - Streaming text output
220+
- `ThinkingDelta(String)` - Extended thinking output
221+
- `ToolRequest(Vec<ToolCall>)` - LLM wants to use tools
222+
- `Finished { usage: Usage }` - Processing complete
223+
- `Error(String)` - Error occurred
224+
- `Retrying { attempt, error }` - Retrying after error
225+
226+
### `SimpleTool`
227+
228+
Define a tool for the LLM to use:
229+
230+
```rust
231+
let tool = SimpleTool::new(
232+
"tool_name", // Name the LLM will use
233+
"Description of tool", // Help the LLM understand when to use it
234+
json!({ /* JSON Schema for parameters */ }),
235+
);
236+
```
237+
238+
### `ToolRegistry`
239+
240+
Manage available tools:
241+
242+
```rust
243+
let mut tools = ToolRegistry::empty();
244+
tools.register(Arc::new(my_tool));
245+
```
246+
247+
### `Usage`
248+
249+
Token usage statistics:
250+
251+
```rust
252+
pub struct Usage {
253+
pub output_tokens: u32,
254+
pub context_tokens: u32,
255+
pub cache_creation_tokens: u32,
256+
pub cache_read_tokens: u32,
257+
}
258+
```
259+
260+
## Authentication
261+
262+
Set the `ANTHROPIC_API_KEY` environment variable:
263+
264+
```bash
265+
export ANTHROPIC_API_KEY=sk-ant-...
266+
```

src/app.rs

Lines changed: 2 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ use ratatui::{
1717
use tokio::sync::oneshot;
1818

1919
use crate::commands::Command;
20-
use crate::config::{AgentRuntimeConfig, Config};
20+
use crate::config::{AgentRuntimeConfig, Config, CODEY_DIR};
2121
use crate::ide::{Ide, IdeEvent, Nvim};
2222
use crate::llm::{Agent, AgentId, AgentRegistry, AgentStep, RequestMode};
2323
#[cfg(feature = "profiling")]
2424
use crate::{profile_frame, profile_span};
25+
use crate::prompts::{COMPACTION_PROMPT, SYSTEM_MD_FILENAME, SYSTEM_PROMPT, WELCOME_MESSAGE};
2526
use crate::tool_filter::ToolFilters;
2627
use crate::tools::{
2728
init_agent_context, update_agent_oauth, Effect, ToolCall, ToolDecision, ToolEvent,
@@ -34,88 +35,6 @@ const MIN_FRAME_TIME: Duration = Duration::from_millis(16);
3435

3536
pub const APP_NAME: &str = "Codey";
3637
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
37-
pub const CODEY_DIR: &str = ".codey";
38-
pub const TRANSCRIPTS_DIR: &str = "transcripts";
39-
40-
const WELCOME_MESSAGE: &str =
41-
"Welcome to Codey! I'm your AI coding assistant. How can I help you today?";
42-
const SYSTEM_PROMPT: &str = r#"You are Codey, an AI coding assistant running in a terminal interface.
43-
44-
## Capabilities
45-
You have access to the following tools:
46-
- `read_file`: Read file contents, optionally with line ranges
47-
- `write_file`: Create new files
48-
- `edit_file`: Make precise edits using search/replace
49-
- `shell`: Execute bash commands
50-
- `fetch_url`: Fetch web content
51-
52-
## Guidelines
53-
54-
### Reading Files
55-
- Always read a file before editing it
56-
- Use line ranges for large files: `read_file(path, start_line=100, end_line=200)`
57-
- Use `shell("ls -la")` to explore directories
58-
- When reading files, be careful about reading large files in one-go. Use line ranges,
59-
or check the file stats with `shell("stat <file_path>")` first.
60-
- shell grep is a great way to get a line number to read a targeted section of a file
61-
62-
### Editing Files
63-
- Use `edit_file` for existing files, `write_file` only for new files
64-
- The `old_string` must match EXACTLY, including whitespace and indentation
65-
- If `old_string` appears multiple times, include more context to make it unique
66-
- Apply edits sequentially; each edit sees the result of previous edits
67-
- You can do multiple edits at once, but keep it under 1000 lines
68-
- Avoid the urge to completely rewrite files - make precise, minimal edits so the user can review them easily
69-
70-
### Shell Commands
71-
- Prefer `read_file` over `cat`, `head`, `tail`
72-
- Use `ls` for directory exploration
73-
- Use `grep` or `rg` for searching code
74-
75-
### Background Execution
76-
For long-running operations, you can execute tools in the background by adding `"background": true` to the tool call. This returns immediately with a task ID while the tool runs asynchronously.
77-
Use `list_background_tasks` to check status and `get_background_task` to retrieve results when complete.
78-
79-
### General
80-
- Be concise but thorough
81-
- Explain what you're doing before executing tools
82-
- If a tool fails, explain the error and suggest fixes
83-
- Ask for clarification if the request is ambiguous
84-
- If you feel like backing out of a path, always get confirmation before git resetting the work tree
85-
- Always get confirmation before making destructive changes (this includes building a release)
86-
"#;
87-
const COMPACTION_PROMPT: &str = r#"The conversation context is getting large and needs to be compacted.
88-
89-
Please provide a comprehensive summary of our conversation so far in markdown format. Include:
90-
91-
1. **What was accomplished** - Main tasks and changes completed as a bulleted list
92-
2. **What still needs to be done** - Remaining tasks or open areas of work as a bulleted list
93-
3. **Key project information** - Important facts about the project that the user has shared or that we're not immediately apparent
94-
4. **Relevant files** - Files most relevant to the current work with brief descriptions, line numbers, or method/variable names
95-
5. **Relevant documentation paths or URLs** - Links to docs or resources we will use to continue our work
96-
6. **Quotes and log snippets** - Any important quotes or logs that the user provided that we'll need later
97-
98-
Be thorough but concise - this summary will seed a fresh conversation context."#;
99-
100-
pub const SUB_AGENT_PROMPT: &str = r#"You are a background research agent. Your task is to investigate, explore, or analyze as directed.
101-
102-
## Capabilities
103-
You have read-only access to:
104-
- `read_file`: Read file contents
105-
- `shell`: Execute commands (for searching, exploring)
106-
- `fetch_url`: Fetch web content
107-
- `web_search`: Search the web
108-
- `open_file`: Signal a file to open in the IDE
109-
110-
## Guidelines
111-
- Focus on the specific task assigned to you
112-
- Be thorough but concise in your findings
113-
- Report back with structured, actionable information
114-
- You cannot modify files - only read and explore
115-
- If you need to suggest changes, describe them clearly for the primary agent to implement
116-
"#;
117-
118-
const SYSTEM_MD_FILENAME: &str = "SYSTEM.md";
11938

12039
/// Build the complete system prompt by appending content from SYSTEM.md files.
12140
/// Checks (in order):

0 commit comments

Comments
 (0)