botiasloop 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +343 -0
  3. data/bin/botiasloop +155 -0
  4. data/data/skills/skill-creator/SKILL.md +329 -0
  5. data/data/skills/skill-creator/assets/ruby_api_cli_template.rb +151 -0
  6. data/data/skills/skill-creator/references/specification.md +99 -0
  7. data/lib/botiasloop/agent.rb +112 -0
  8. data/lib/botiasloop/channels/base.rb +248 -0
  9. data/lib/botiasloop/channels/cli.rb +101 -0
  10. data/lib/botiasloop/channels/telegram.rb +348 -0
  11. data/lib/botiasloop/channels.rb +64 -0
  12. data/lib/botiasloop/channels_manager.rb +299 -0
  13. data/lib/botiasloop/commands/archive.rb +109 -0
  14. data/lib/botiasloop/commands/base.rb +54 -0
  15. data/lib/botiasloop/commands/compact.rb +78 -0
  16. data/lib/botiasloop/commands/context.rb +34 -0
  17. data/lib/botiasloop/commands/conversations.rb +40 -0
  18. data/lib/botiasloop/commands/help.rb +30 -0
  19. data/lib/botiasloop/commands/label.rb +64 -0
  20. data/lib/botiasloop/commands/new.rb +21 -0
  21. data/lib/botiasloop/commands/registry.rb +121 -0
  22. data/lib/botiasloop/commands/reset.rb +18 -0
  23. data/lib/botiasloop/commands/status.rb +32 -0
  24. data/lib/botiasloop/commands/switch.rb +76 -0
  25. data/lib/botiasloop/commands/system_prompt.rb +20 -0
  26. data/lib/botiasloop/commands.rb +22 -0
  27. data/lib/botiasloop/config.rb +58 -0
  28. data/lib/botiasloop/conversation.rb +189 -0
  29. data/lib/botiasloop/conversation_manager.rb +225 -0
  30. data/lib/botiasloop/database.rb +92 -0
  31. data/lib/botiasloop/loop.rb +115 -0
  32. data/lib/botiasloop/skills/loader.rb +58 -0
  33. data/lib/botiasloop/skills/registry.rb +42 -0
  34. data/lib/botiasloop/skills/skill.rb +75 -0
  35. data/lib/botiasloop/systemd_service.rb +300 -0
  36. data/lib/botiasloop/tool.rb +24 -0
  37. data/lib/botiasloop/tools/registry.rb +68 -0
  38. data/lib/botiasloop/tools/shell.rb +50 -0
  39. data/lib/botiasloop/tools/web_search.rb +64 -0
  40. data/lib/botiasloop/version.rb +5 -0
  41. data/lib/botiasloop.rb +45 -0
  42. metadata +250 -0
@@ -0,0 +1,329 @@
1
+ ---
2
+ name: skill-creator
3
+ description: Create new Agent Skills following the agentskills.io specification. Use when the user wants to create a skill for a specific task, domain, or API. This skill guides you through the complete skill creation process including directory structure, SKILL.md format, and API client creation when needed.
4
+ metadata:
5
+ author: botiasloop
6
+ version: "1.0"
7
+ ---
8
+
9
+ # Skill Creator
10
+
11
+ ## Purpose
12
+
13
+ You are a skill creator agent. Your job is to create well-structured, useful skills that help accomplish specific tasks. Skills live in `~/skills/<skill-name>/` directories and follow the agentskills.io specification.
14
+
15
+ ## When to Use
16
+
17
+ Activate this skill when:
18
+ - User explicitly asks to create a skill
19
+ - User mentions "skill for X" or "create a skill"
20
+ - User wants help with a recurring task that needs instructions
21
+ - User needs integration with an API or service
22
+
23
+ ## Critical Rules
24
+
25
+ 1. **ALWAYS source from official documentation** - Never guess or hallucinate API details
26
+ 2. **Follow the specification exactly** - Invalid skills won't work
27
+ 3. **Keep SKILL.md compact** - Move details to `references/`
28
+ 4. **Use templates when creating scripts** - Don't reinvent patterns
29
+
30
+ ## Step-by-Step Process
31
+
32
+ ### Step 1: Understand the Request
33
+
34
+ Ask clarifying questions:
35
+ - What specific task should this skill help with?
36
+ - Is this for an API integration? Which API?
37
+ - What are the common use cases?
38
+
39
+ ### Step 2: Research API Documentation (If Applicable)
40
+
41
+ If the skill involves an API:
42
+
43
+ 1. **Search for official documentation first**
44
+ - Use web_search to find official API docs
45
+ - Look for docs at: `https://api.service.com/docs`, `https://developer.service.com/`
46
+ - Check for OpenAPI specs, README files, or official guides
47
+
48
+ 2. **If official docs NOT found:**
49
+ - STOP immediately
50
+ - Tell the user: "I cannot find official documentation for [API name]. Creating a skill without proper documentation would result in incorrect or broken integration."
51
+ - Ask: "Do you have access to official documentation, API reference, or a specification document I can use?"
52
+ - Do NOT proceed without official docs
53
+
54
+ 3. **If alternative (unofficial) docs found:**
55
+ - Tell the user: "I found [alternative source] but could not locate official documentation."
56
+ - Ask: "Should I proceed with this unofficial reference, or do you have official docs?"
57
+ - Only proceed with user explicit permission
58
+
59
+ 4. **Once you have docs, READ them thoroughly:**
60
+ - Use Read tool on documentation files
61
+ - Extract: authentication methods, endpoints, request/response formats, error codes
62
+ - Note any code examples or SDK references
63
+
64
+ ### Step 3: Create the Skill Directory
65
+
66
+ Create directory structure:
67
+ ```
68
+ ~/skills/<skill-name>/
69
+ ├── SKILL.md (required)
70
+ ├── scripts/ (optional, for API clients)
71
+ ├── references/ (optional, for detailed docs)
72
+ └── assets/ (optional, for templates/data)
73
+ ```
74
+
75
+ **Naming the skill:**
76
+ - Max 64 characters
77
+ - Lowercase letters, numbers, hyphens only
78
+ - No leading/trailing hyphens
79
+ - No consecutive hyphens
80
+ - Must describe what it does: `github-api`, `pdf-processor`, `csv-analysis`
81
+
82
+ ### Step 4: Write SKILL.md
83
+
84
+ **Frontmatter (REQUIRED):**
85
+ ```yaml
86
+ ---
87
+ name: skill-name
88
+ description: Clear description of what this skill does and when to use it. Include keywords that help identify relevant tasks.
89
+ metadata:
90
+ author: user-name
91
+ version: "1.0"
92
+ ---
93
+ ```
94
+
95
+ **Body Content:**
96
+
97
+ Structure for progressive disclosure:
98
+
99
+ 1. **Quick Start** (2-3 sentences) - What this skill enables
100
+ 2. **When to Use** - Specific scenarios and keywords
101
+ 3. **Prerequisites** - Required tools, accounts, environment variables
102
+ 4. **Main Instructions** - Step-by-step guidance for the agent
103
+ 5. **Examples** - Common patterns, inputs/outputs
104
+ 6. **Error Handling** - Common issues and solutions
105
+ 7. **References** - Links to detailed docs in `references/`
106
+
107
+ **Content Guidelines:**
108
+ - Keep under 500 lines
109
+ - Be specific and actionable
110
+ - Include concrete examples
111
+ - Don't omit critical information
112
+ - Focus on what the agent should DO
113
+
114
+ ### Step 5: Create API Client Script (If API Skill)
115
+
116
+ If the skill uses an API, create a CLI script in `scripts/`:
117
+
118
+ **Location:** `~/skills/<skill-name>/scripts/<api-name>.rb`
119
+
120
+ **Process:**
121
+ 1. Copy the template from `assets/ruby_api_cli_template.rb`
122
+ 2. Customize for the specific API using docs you researched
123
+ 3. Implement key endpoints based on common use cases
124
+ 4. Add error handling for API responses
125
+ 5. Test logic mentally against docs
126
+
127
+ **Template location:** `data/skills/skill-creator/assets/ruby_api_cli_template.rb`
128
+
129
+ **Usage in skill instructions:**
130
+ ```markdown
131
+ To call the API, use the CLI script:
132
+ `API_KEY=xxx ~/skills/<skill-name>/scripts/<api-name>.rb [options]`
133
+ ```
134
+
135
+ ### Step 6: Add References (Optional)
136
+
137
+ Move detailed documentation to `references/`:
138
+ - `references/api-reference.md` - Full API docs
139
+ - `references/examples.md` - More examples
140
+ - `references/troubleshooting.md` - Common issues
141
+
142
+ Reference them in SKILL.md:
143
+ ```markdown
144
+ ## References
145
+
146
+ - [API Reference](references/api-reference.md) - Full endpoint documentation
147
+ ```
148
+
149
+ ### Step 7: Validate the Skill
150
+
151
+ Checklist before finishing:
152
+ - [ ] Frontmatter has name and description
153
+ - [ ] Name matches directory name
154
+ - [ ] Name follows format rules (lowercase, hyphens, max 64 chars)
155
+ - [ ] Description explains what AND when to use
156
+ - [ ] Body is under 500 lines
157
+ - [ ] Includes clear step-by-step instructions
158
+ - [ ] API skills have working CLI script (if applicable)
159
+ - [ ] All file references use relative paths
160
+
161
+ ### Step 8: Create the Skill
162
+
163
+ Use shell commands to:
164
+ 1. Create directory structure
165
+ 2. Write SKILL.md file
166
+ 3. Create script files (if applicable)
167
+ 4. Create reference files (if applicable)
168
+
169
+ **Example:**
170
+ ```bash
171
+ mkdir -p ~/skills/my-api-skill/scripts
172
+ mkdir -p ~/skills/my-api-skill/references
173
+
174
+ # Write SKILL.md
175
+ cat > ~/skills/my-api-skill/SKILL.md << 'SKILL_EOF'
176
+ ---
177
+ name: my-api-skill
178
+ description: Interact with MyAPI to fetch data and manage resources.
179
+ metadata:
180
+ author: user
181
+ version: "1.0"
182
+ ---
183
+ # MyAPI Skill
184
+
185
+ ## Quick Start
186
+
187
+ Use this skill to interact with MyAPI for fetching data and managing resources.
188
+
189
+ ## When to Use
190
+
191
+ - User mentions "MyAPI" or "my api"
192
+ - Need to fetch data from MyAPI
193
+ - Managing resources on MyAPI
194
+
195
+ ## Prerequisites
196
+
197
+ - API key in MYAPI_KEY environment variable
198
+
199
+ ## Instructions
200
+
201
+ 1. **Authentication**: Set MYAPI_KEY environment variable
202
+ 2. **Make requests**: Use the CLI script at `scripts/myapi.rb`
203
+ 3. **Handle errors**: Check exit codes and error messages
204
+
205
+ ## Examples
206
+
207
+ ### Fetch user data
208
+ ```bash
209
+ MYAPI_KEY=xxx ~/skills/my-api-skill/scripts/myapi.rb --endpoint users --method GET
210
+ ```
211
+
212
+ ## References
213
+
214
+ - [API Reference](references/api-reference.md)
215
+ SKILL_EOF
216
+ ```
217
+
218
+ ## Working with API Documentation
219
+
220
+ ### Finding Official Docs
221
+
222
+ **Search strategies:**
223
+ 1. `service.com/api/docs`
224
+ 2. `developer.service.com`
225
+ 3. `docs.service.com/api`
226
+ 4. GitHub repos: `service/api-docs`
227
+ 5. API specifications (OpenAPI/Swagger)
228
+
229
+ **What to extract:**
230
+ - Base URL
231
+ - Authentication method (API key, OAuth, token)
232
+ - Key endpoints for common operations
233
+ - Request/response formats (JSON schema)
234
+ - Rate limits
235
+ - Error codes
236
+
237
+ ### When Documentation is Incomplete
238
+
239
+ If official docs are missing key details:
240
+ 1. Look for SDK source code on GitHub
241
+ 2. Search for API examples in issues/PRs
242
+ 3. Use web search for "service API example"
243
+ 4. Ask user if they have internal docs
244
+
245
+ ### Never Proceed Without Docs
246
+
247
+ **Critical:** If you cannot find sufficient documentation to implement the API client correctly, you MUST:
248
+ 1. Stop and explain the situation
249
+ 2. Request documentation from the user
250
+ 3. Do not create a broken skill
251
+
252
+ ## Error Handling in Skills
253
+
254
+ Skills should anticipate and handle:
255
+ - Missing environment variables
256
+ - Invalid inputs
257
+ - API errors (4xx, 5xx responses)
258
+ - Rate limiting
259
+ - Network failures
260
+
261
+ Document error handling in SKILL.md body.
262
+
263
+ ## Examples
264
+
265
+ ### Example 1: Simple Task Skill
266
+
267
+ **Request:** "Create a skill for optimizing PNG images"
268
+
269
+ **Process:**
270
+ 1. No API research needed (uses local tools)
271
+ 2. Name: `png-optimizer`
272
+ 3. Directory: `~/skills/png-optimizer/`
273
+ 4. SKILL.md includes: pngquant/oxipng usage, quality options, batch processing
274
+
275
+ ### Example 2: API Integration Skill
276
+
277
+ **Request:** "Create a skill for the WeatherAPI"
278
+
279
+ **Process:**
280
+ 1. Search for "weatherapi.com documentation"
281
+ 2. Read docs at `https://www.weatherapi.com/docs/`
282
+ 3. Extract: endpoints, API key auth, response format
283
+ 4. Name: `weather-api`
284
+ 5. Create CLI script using template
285
+ 6. SKILL.md includes: authentication, common queries, response parsing
286
+
287
+ ### Example 3: Rejected (No Docs)
288
+
289
+ **Request:** "Create a skill for SomeCorp's internal API"
290
+
291
+ **Response:**
292
+ "I cannot find official documentation for SomeCorp's API. Creating a skill without proper documentation would result in incorrect or potentially harmful integration.
293
+
294
+ Do you have:
295
+ 1. Official API documentation (docs site, PDF, README)?
296
+ 2. API specification (OpenAPI/Swagger)?
297
+ 3. Example code or SDK?
298
+
299
+ Please provide documentation and I'll create a complete skill for you."
300
+
301
+ ## Template Usage
302
+
303
+ When creating API client scripts, always use the Ruby CLI template:
304
+
305
+ **Template path:** `data/skills/skill-creator/assets/ruby_api_cli_template.rb`
306
+
307
+ **To use:**
308
+ 1. Read the template
309
+ 2. Copy to `~/skills/<skill-name>/scripts/<api-name>.rb`
310
+ 3. Customize based on API documentation
311
+ 4. Ensure error handling matches API error responses
312
+
313
+ ## Important Reminders
314
+
315
+ 1. **Always verify with official docs** - Never trust memory or assumptions
316
+ 2. **Be transparent about documentation gaps** - Tell user when you can't find docs
317
+ 3. **Don't create broken skills** - Better to wait for docs than ship broken code
318
+ 4. **Test mentally** - Walk through the skill instructions as if you were executing them
319
+ 5. **Follow the spec** - Invalid skills won't be recognized by the system
320
+
321
+ ## Success Criteria
322
+
323
+ A skill is complete when:
324
+ - Directory structure is correct
325
+ - SKILL.md has valid frontmatter and body
326
+ - Name matches directory and follows format rules
327
+ - Instructions are clear and actionable
328
+ - API clients are based on official documentation
329
+ - User has been informed if documentation was limited
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # API Client Template
5
+ # Copy this template when creating API client scripts for skills
6
+ # Customize based on specific API documentation
7
+
8
+ require "optparse"
9
+ require "json"
10
+ require "net/http"
11
+ require "uri"
12
+
13
+ # Configuration
14
+ BASE_URL = ENV.fetch("API_BASE_URL", "https://api.example.com")
15
+ API_KEY = ENV["API_KEY"]
16
+
17
+ # Command-line options
18
+ options = {
19
+ verbose: false,
20
+ endpoint: nil,
21
+ method: "GET",
22
+ data: nil,
23
+ params: {}
24
+ }
25
+
26
+ parser = OptionParser.new do |opts|
27
+ opts.banner = "Usage: api_client.rb [options]"
28
+ opts.separator ""
29
+ opts.separator "Options:"
30
+
31
+ opts.on("-e", "--endpoint ENDPOINT", "API endpoint path (e.g., /users)") do |e|
32
+ options[:endpoint] = e
33
+ end
34
+
35
+ opts.on("-m", "--method METHOD", "HTTP method (GET, POST, PUT, DELETE)") do |m|
36
+ options[:method] = m.upcase
37
+ end
38
+
39
+ opts.on("-d", "--data DATA", "JSON data for POST/PUT requests") do |d|
40
+ options[:data] = d
41
+ end
42
+
43
+ opts.on("-p", "--param KEY=VALUE", "Query parameters (can be used multiple times)") do |param|
44
+ key, value = param.split("=", 2)
45
+ options[:params][key] = value
46
+ end
47
+
48
+ opts.on("-v", "--verbose", "Enable verbose output") do
49
+ options[:verbose] = true
50
+ end
51
+
52
+ opts.on("-h", "--help", "Show this help") do
53
+ puts opts
54
+ exit 0
55
+ end
56
+ end
57
+
58
+ begin
59
+ parser.parse!
60
+ rescue OptionParser::InvalidOption => e
61
+ warn "Error: #{e.message}"
62
+ warn "Use --help for usage information"
63
+ exit 1
64
+ end
65
+
66
+ # Validate required options
67
+ unless options[:endpoint]
68
+ warn "Error: --endpoint is required"
69
+ warn "Use --help for usage information"
70
+ exit 1
71
+ end
72
+
73
+ # Validate API key
74
+ unless API_KEY
75
+ warn "Error: API_KEY environment variable is required"
76
+ warn "Example: API_KEY=xxx ./api_client.rb --endpoint /users"
77
+ exit 1
78
+ end
79
+
80
+ # Build URI
81
+ uri = URI.parse("#{BASE_URL}#{options[:endpoint]}")
82
+ uri.query = URI.encode_www_form(options[:params]) unless options[:params].empty?
83
+
84
+ # Create HTTP request
85
+ request = case options[:method]
86
+ when "GET"
87
+ Net::HTTP::Get.new(uri)
88
+ when "POST"
89
+ req = Net::HTTP::Post.new(uri)
90
+ req.body = options[:data] if options[:data]
91
+ req["Content-Type"] = "application/json" if options[:data]
92
+ req
93
+ when "PUT"
94
+ req = Net::HTTP::Put.new(uri)
95
+ req.body = options[:data] if options[:data]
96
+ req["Content-Type"] = "application/json" if options[:data]
97
+ req
98
+ when "DELETE"
99
+ Net::HTTP::Delete.new(uri)
100
+ else
101
+ warn "Error: Unsupported HTTP method: #{options[:method]}"
102
+ exit 1
103
+ end
104
+
105
+ # Set headers
106
+ request["Authorization"] = "Bearer #{API_KEY}"
107
+ request["Accept"] = "application/json"
108
+ request["User-Agent"] = "BotiasLoop-Skill/1.0"
109
+
110
+ # Debug output
111
+ if options[:verbose]
112
+ warn "Request: #{options[:method]} #{uri}"
113
+ warn "Headers:"
114
+ request.each_header { |k, v| warn " #{k}: #{v}" }
115
+ warn "Body: #{options[:data]}" if options[:data]
116
+ warn ""
117
+ end
118
+
119
+ # Execute request
120
+ begin
121
+ http = Net::HTTP.new(uri.host, uri.port)
122
+ http.use_ssl = uri.scheme == "https"
123
+ http.open_timeout = 10
124
+ http.read_timeout = 30
125
+
126
+ response = http.request(request)
127
+
128
+ # Debug response
129
+ if options[:verbose]
130
+ warn "Response: #{response.code} #{response.message}"
131
+ warn "Headers:"
132
+ response.each_header { |k, v| warn " #{k}: #{v}" }
133
+ warn ""
134
+ end
135
+
136
+ # Output response body
137
+ puts response.body
138
+
139
+ # Exit with non-zero code on HTTP errors
140
+ exit 1 unless response.is_a?(Net::HTTPSuccess)
141
+ rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
142
+ warn "Error: Network error - #{e.message}"
143
+ exit 1
144
+ rescue JSON::ParserError => e
145
+ warn "Error: Failed to parse response as JSON - #{e.message}"
146
+ puts response.body # Still output raw body
147
+ exit 1
148
+ rescue => e
149
+ warn "Error: #{e.class} - #{e.message}"
150
+ exit 1
151
+ end
@@ -0,0 +1,99 @@
1
+ # Agent Skills Specification
2
+
3
+ Source: https://agentskills.io/specification.md
4
+
5
+ This document defines the Agent Skills format used by botiasloop.
6
+
7
+ ## Directory structure
8
+
9
+ A skill is a directory containing at minimum a `SKILL.md` file:
10
+
11
+ ```
12
+ skill-name/
13
+ └── SKILL.md # Required
14
+ ```
15
+
16
+ Optionally include additional directories:
17
+ - `scripts/` - Executable code
18
+ - `references/` - Additional documentation
19
+ - `assets/` - Static resources (templates, data files)
20
+
21
+ ## SKILL.md format
22
+
23
+ The `SKILL.md` file must contain YAML frontmatter followed by Markdown content.
24
+
25
+ ### Frontmatter (required)
26
+
27
+ ```yaml
28
+ ---
29
+ name: skill-name
30
+ description: A description of what this skill does and when to use it.
31
+ ---
32
+ ```
33
+
34
+ With optional fields:
35
+
36
+ ```yaml
37
+ ---
38
+ name: pdf-processing
39
+ description: Extract text and tables from PDF files, fill forms, merge documents.
40
+ license: Apache-2.0
41
+ metadata:
42
+ author: example-org
43
+ version: "1.0"
44
+ ---
45
+ ```
46
+
47
+ ### Field Reference
48
+
49
+ | Field | Required | Constraints |
50
+ | --------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
51
+ | `name` | Yes | Max 64 characters. Lowercase letters, numbers, and hyphens only. Must not start or end with a hyphen. |
52
+ | `description` | Yes | Max 1024 characters. Non-empty. Describes what the skill does and when to use it. |
53
+ | `license` | No | License name or reference to a bundled license file. |
54
+ | `compatibility` | No | Max 500 characters. Indicates environment requirements. |
55
+ | `metadata` | No | Arbitrary key-value mapping for additional metadata. |
56
+ | `allowed-tools` | No | Space-delimited list of pre-approved tools the skill may use. (Experimental) |
57
+
58
+ ### Name Field Rules
59
+
60
+ - Must be 1-64 characters
61
+ - May only contain lowercase alphanumeric characters and hyphens (`a-z` and `-`)
62
+ - Must not start or end with `-`
63
+ - Must not contain consecutive hyphens (`--`)
64
+ - Must match the parent directory name
65
+
66
+ Valid: `pdf-processing`, `data-analysis`, `code-review`
67
+ Invalid: `PDF-Processing` (uppercase), `-pdf` (leading hyphen), `pdf--processing` (consecutive hyphens)
68
+
69
+ ### Body content
70
+
71
+ The Markdown body after the frontmatter contains the skill instructions. There are no format restrictions.
72
+
73
+ Recommended sections:
74
+ - Step-by-step instructions
75
+ - Examples of inputs and outputs
76
+ - Common edge cases
77
+
78
+ Keep under 500 lines. Move detailed content to `references/`.
79
+
80
+ ## Progressive Disclosure
81
+
82
+ Skills are structured for efficient use of context:
83
+
84
+ 1. **Metadata** (~100 tokens): The `name` and `description` fields are loaded at startup for all skills
85
+ 2. **Instructions** (< 5000 tokens recommended): The full `SKILL.md` body is loaded when the skill is activated
86
+ 3. **Resources** (as needed): Files in `scripts/`, `references/`, or `assets/` are loaded only when required
87
+
88
+ ## File References
89
+
90
+ When referencing other files in your skill, use relative paths from the skill root:
91
+
92
+ ```markdown
93
+ See [the reference guide](references/REFERENCE.md) for details.
94
+
95
+ Run the extraction script:
96
+ scripts/extract.py
97
+ ```
98
+
99
+ Keep file references one level deep from `SKILL.md`. Avoid deeply nested reference chains.
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_llm"
4
+ require "logger"
5
+
6
+ module Botiasloop
7
+ class Agent
8
+ # Initialize the agent
9
+ #
10
+ # @param config [Config, nil] Configuration instance (loads default if nil)
11
+ def initialize(config = nil)
12
+ @config = config || Config.new
13
+ @logger = Logger.new($stderr)
14
+ setup_ruby_llm
15
+ end
16
+
17
+ # Send a message and get a response
18
+ #
19
+ # @param message [String] User message
20
+ # @param conversation [Conversation, nil] Existing conversation
21
+ # @return [String] Assistant response
22
+ def chat(message, conversation: nil)
23
+ conversation ||= Conversation.new
24
+
25
+ registry = create_registry
26
+ provider, model = create_provider_and_model
27
+ loop = Loop.new(provider, model, registry, max_iterations: @config.max_iterations)
28
+
29
+ loop.run(conversation, message)
30
+ rescue MaxIterationsExceeded => e
31
+ e.message
32
+ end
33
+
34
+ private
35
+
36
+ def setup_ruby_llm
37
+ provider_name, provider_config = @config.active_provider
38
+
39
+ RubyLLM.configure do |config|
40
+ configure_provider(config, provider_name, provider_config)
41
+ end
42
+ end
43
+
44
+ def configure_provider(config, provider_name, provider_config)
45
+ case provider_name
46
+ when "openai"
47
+ config.openai_api_key = provider_config["api_key"]
48
+ config.openai_organization_id = provider_config["organization_id"] if provider_config["organization_id"]
49
+ config.openai_project_id = provider_config["project_id"] if provider_config["project_id"]
50
+ config.openai_api_base = provider_config["api_base"] if provider_config["api_base"]
51
+ when "anthropic"
52
+ config.anthropic_api_key = provider_config["api_key"]
53
+ when "gemini"
54
+ config.gemini_api_key = provider_config["api_key"]
55
+ config.gemini_api_base = provider_config["api_base"] if provider_config["api_base"]
56
+ when "vertexai"
57
+ config.vertexai_project_id = provider_config["project_id"]
58
+ config.vertexai_location = provider_config["location"] if provider_config["location"]
59
+ when "deepseek"
60
+ config.deepseek_api_key = provider_config["api_key"]
61
+ when "mistral"
62
+ config.mistral_api_key = provider_config["api_key"]
63
+ when "perplexity"
64
+ config.perplexity_api_key = provider_config["api_key"]
65
+ when "openrouter"
66
+ config.openrouter_api_key = provider_config["api_key"]
67
+ when "ollama"
68
+ config.ollama_api_base = provider_config["api_base"] || "http://localhost:11434/v1"
69
+ when "gpustack"
70
+ config.gpustack_api_base = provider_config["api_base"]
71
+ config.gpustack_api_key = provider_config["api_key"] if provider_config["api_key"]
72
+ when "bedrock"
73
+ config.bedrock_api_key = provider_config["api_key"] if provider_config["api_key"]
74
+ config.bedrock_secret_key = provider_config["secret_key"] if provider_config["secret_key"]
75
+ config.bedrock_region = provider_config["region"] if provider_config["region"]
76
+ config.bedrock_session_token = provider_config["session_token"] if provider_config["session_token"]
77
+ when "azure"
78
+ config.azure_api_base = provider_config["api_base"]
79
+ if provider_config["api_key"]
80
+ config.azure_api_key = provider_config["api_key"]
81
+ elsif provider_config["ai_auth_token"]
82
+ config.azure_ai_auth_token = provider_config["ai_auth_token"]
83
+ end
84
+ end
85
+ end
86
+
87
+ def create_provider_and_model
88
+ _provider_name, provider_config = @config.active_provider
89
+ model_id = provider_config["model"]
90
+ model = RubyLLM::Models.find(model_id)
91
+ provider_class = RubyLLM::Provider.for(model_id)
92
+ provider = provider_class.new(RubyLLM.config)
93
+ [provider, model]
94
+ end
95
+
96
+ def create_registry
97
+ registry = Tools::Registry.new
98
+ registry.register(Tools::Shell)
99
+ registry.register(Tools::WebSearch, searxng_url: web_search_url) if web_search_configured?
100
+ registry
101
+ end
102
+
103
+ def web_search_configured?
104
+ url = web_search_url
105
+ url && !url.empty?
106
+ end
107
+
108
+ def web_search_url
109
+ @config.tools["web_search"]["searxng_url"]
110
+ end
111
+ end
112
+ end