@dcode-dev/dcode-cli 1.0.0
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.
- package/NPM_README.md +78 -0
- package/README.md +341 -0
- package/bin/dcode-bin +0 -0
- package/bin/dcode.js +44 -0
- package/cmd/agent_v2.go +448 -0
- package/cmd/analyze.go +97 -0
- package/cmd/auth.go +338 -0
- package/cmd/compose.go +284 -0
- package/cmd/context.go +111 -0
- package/cmd/edit.go +116 -0
- package/cmd/env.go +10 -0
- package/cmd/fix.go +145 -0
- package/cmd/gemini.go +20 -0
- package/cmd/generate.go +47 -0
- package/cmd/interactive.go +33 -0
- package/cmd/mcp.go +196 -0
- package/cmd/patch.go +19 -0
- package/cmd/providers.go +67 -0
- package/cmd/root.go +41 -0
- package/cmd/search.go +61 -0
- package/cmd/server.go +36 -0
- package/cmd/switch.go +122 -0
- package/cmd/terminal.go +277 -0
- package/go.mod +42 -0
- package/go.sum +86 -0
- package/internal/agent/agent.go +332 -0
- package/internal/agent/parse.go +25 -0
- package/internal/agents/base.go +154 -0
- package/internal/agents/documenter.go +77 -0
- package/internal/agents/generalist.go +266 -0
- package/internal/agents/investigator.go +60 -0
- package/internal/agents/registry.go +34 -0
- package/internal/agents/reviewer.go +67 -0
- package/internal/agents/tester.go +73 -0
- package/internal/ai/client.go +634 -0
- package/internal/ai/tools.go +332 -0
- package/internal/auth/adc.go +108 -0
- package/internal/auth/apikey.go +67 -0
- package/internal/auth/factory.go +145 -0
- package/internal/auth/oauth2.go +227 -0
- package/internal/auth/store.go +216 -0
- package/internal/auth/types.go +79 -0
- package/internal/auth/vertex.go +138 -0
- package/internal/config/config.go +428 -0
- package/internal/config/policy.go +251 -0
- package/internal/context/builder.go +312 -0
- package/internal/detector/detector.go +204 -0
- package/internal/diffutil/diffutil.go +30 -0
- package/internal/fsutil/fsutil.go +35 -0
- package/internal/mcp/client.go +314 -0
- package/internal/mcp/manager.go +221 -0
- package/internal/policy/policy.go +89 -0
- package/internal/prompt/interactive.go +338 -0
- package/internal/registry/agent.go +201 -0
- package/internal/registry/tool.go +181 -0
- package/internal/scheduler/scheduler.go +250 -0
- package/internal/server/server.go +167 -0
- package/internal/tools/file.go +183 -0
- package/internal/tools/filesystem.go +286 -0
- package/internal/tools/git.go +355 -0
- package/internal/tools/memory.go +269 -0
- package/internal/tools/registry.go +49 -0
- package/internal/tools/search.go +230 -0
- package/internal/tools/shell.go +84 -0
- package/internal/websearch/search.go +40 -0
- package/internal/websearch/tavily.go +79 -0
- package/main.go +19 -0
- package/package.json +57 -0
- package/scripts/install.js +59 -0
- package/scripts/uninstall.js +28 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
package prompt
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
"github.com/charmbracelet/bubbles/textinput"
|
|
8
|
+
tea "github.com/charmbracelet/bubbletea"
|
|
9
|
+
"github.com/charmbracelet/lipgloss"
|
|
10
|
+
"github.com/ddhanush1/dcode/internal/ai"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
type slashCommand struct {
|
|
14
|
+
Name string
|
|
15
|
+
Description string
|
|
16
|
+
Category string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var availableCommands = []slashCommand{
|
|
20
|
+
{Name: "/analyze", Description: "Analyze project structure and dependencies", Category: "Project"},
|
|
21
|
+
{Name: "/edit", Description: "AI-powered code editing", Category: "Code"},
|
|
22
|
+
{Name: "/fix", Description: "Detect and fix errors in code", Category: "Code"},
|
|
23
|
+
{Name: "/generate", Description: "Generate code from prompts", Category: "Code"},
|
|
24
|
+
{Name: "/search", Description: "Search the web for information", Category: "Web"},
|
|
25
|
+
{Name: "/providers", Description: "List available AI providers and models", Category: "Config"},
|
|
26
|
+
{Name: "/help", Description: "Show available commands", Category: "Help"},
|
|
27
|
+
{Name: "/exit", Description: "Exit the interactive session", Category: "System"},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type model struct {
|
|
31
|
+
textInput textinput.Model
|
|
32
|
+
suggestions []slashCommand
|
|
33
|
+
selectedIndex int
|
|
34
|
+
showSuggestions bool
|
|
35
|
+
quitting bool
|
|
36
|
+
aiClient *ai.Client
|
|
37
|
+
chatHistory []ai.Message
|
|
38
|
+
lastResponse string
|
|
39
|
+
isProcessing bool
|
|
40
|
+
errorMsg string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type aiResponseMsg struct {
|
|
44
|
+
response string
|
|
45
|
+
err error
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func callAI(client *ai.Client, messages []ai.Message) tea.Cmd {
|
|
49
|
+
return func() tea.Msg {
|
|
50
|
+
response, err := client.Chat(messages)
|
|
51
|
+
return aiResponseMsg{response: response, err: err}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func initialModel() model {
|
|
56
|
+
ti := textinput.New()
|
|
57
|
+
ti.Placeholder = "Type your prompt or / for commands..."
|
|
58
|
+
ti.Focus()
|
|
59
|
+
ti.CharLimit = 500
|
|
60
|
+
ti.Width = 80
|
|
61
|
+
|
|
62
|
+
// Initialize AI client
|
|
63
|
+
client, err := ai.NewClient()
|
|
64
|
+
if err != nil {
|
|
65
|
+
fmt.Printf("⚠️ Warning: Could not initialize AI client: %v\n", err)
|
|
66
|
+
fmt.Println("Please set your API keys in .env file")
|
|
67
|
+
fmt.Println()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return model{
|
|
71
|
+
textInput: ti,
|
|
72
|
+
suggestions: []slashCommand{},
|
|
73
|
+
selectedIndex: 0,
|
|
74
|
+
showSuggestions: false,
|
|
75
|
+
quitting: false,
|
|
76
|
+
aiClient: client,
|
|
77
|
+
chatHistory: []ai.Message{},
|
|
78
|
+
lastResponse: "",
|
|
79
|
+
isProcessing: false,
|
|
80
|
+
errorMsg: "",
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func (m model) Init() tea.Cmd {
|
|
85
|
+
return textinput.Blink
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
89
|
+
var cmd tea.Cmd
|
|
90
|
+
|
|
91
|
+
switch msg := msg.(type) {
|
|
92
|
+
case aiResponseMsg:
|
|
93
|
+
m.isProcessing = false
|
|
94
|
+
if msg.err != nil {
|
|
95
|
+
m.errorMsg = fmt.Sprintf("Error: %v", msg.err)
|
|
96
|
+
} else {
|
|
97
|
+
m.lastResponse = msg.response
|
|
98
|
+
m.chatHistory = append(m.chatHistory, ai.Message{
|
|
99
|
+
Role: "assistant",
|
|
100
|
+
Content: msg.response,
|
|
101
|
+
})
|
|
102
|
+
m.errorMsg = ""
|
|
103
|
+
}
|
|
104
|
+
return m, nil
|
|
105
|
+
|
|
106
|
+
case tea.KeyMsg:
|
|
107
|
+
switch msg.String() {
|
|
108
|
+
case "ctrl+c":
|
|
109
|
+
m.quitting = true
|
|
110
|
+
return m, tea.Quit
|
|
111
|
+
|
|
112
|
+
case "esc":
|
|
113
|
+
if m.showSuggestions {
|
|
114
|
+
m.showSuggestions = false
|
|
115
|
+
m.suggestions = []slashCommand{}
|
|
116
|
+
} else {
|
|
117
|
+
m.quitting = true
|
|
118
|
+
return m, tea.Quit
|
|
119
|
+
}
|
|
120
|
+
return m, nil
|
|
121
|
+
|
|
122
|
+
case "enter":
|
|
123
|
+
if m.showSuggestions && len(m.suggestions) > 0 {
|
|
124
|
+
// Select the highlighted suggestion
|
|
125
|
+
selected := m.suggestions[m.selectedIndex]
|
|
126
|
+
m.textInput.SetValue(selected.Name + " ")
|
|
127
|
+
m.textInput.CursorEnd()
|
|
128
|
+
m.showSuggestions = false
|
|
129
|
+
m.suggestions = []slashCommand{}
|
|
130
|
+
} else {
|
|
131
|
+
// Process the input
|
|
132
|
+
value := m.textInput.Value()
|
|
133
|
+
if value != "" {
|
|
134
|
+
if value == "/exit" {
|
|
135
|
+
m.quitting = true
|
|
136
|
+
return m, tea.Quit
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Handle slash commands
|
|
140
|
+
if strings.HasPrefix(value, "/") {
|
|
141
|
+
m.lastResponse = fmt.Sprintf("Command '%s' not yet implemented", value)
|
|
142
|
+
m.textInput.SetValue("")
|
|
143
|
+
return m, nil
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Send to AI
|
|
147
|
+
if m.aiClient != nil && !m.isProcessing {
|
|
148
|
+
m.isProcessing = true
|
|
149
|
+
m.errorMsg = ""
|
|
150
|
+
m.lastResponse = ""
|
|
151
|
+
|
|
152
|
+
// Add user message to history
|
|
153
|
+
m.chatHistory = append(m.chatHistory, ai.Message{
|
|
154
|
+
Role: "user",
|
|
155
|
+
Content: value,
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
m.textInput.SetValue("")
|
|
159
|
+
return m, callAI(m.aiClient, m.chatHistory)
|
|
160
|
+
} else if m.aiClient == nil {
|
|
161
|
+
m.errorMsg = "AI client not initialized. Please check your API keys."
|
|
162
|
+
m.textInput.SetValue("")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return m, nil
|
|
167
|
+
|
|
168
|
+
case "tab":
|
|
169
|
+
if m.showSuggestions && len(m.suggestions) > 0 {
|
|
170
|
+
selected := m.suggestions[m.selectedIndex]
|
|
171
|
+
m.textInput.SetValue(selected.Name + " ")
|
|
172
|
+
m.textInput.CursorEnd()
|
|
173
|
+
m.showSuggestions = false
|
|
174
|
+
m.suggestions = []slashCommand{}
|
|
175
|
+
}
|
|
176
|
+
return m, nil
|
|
177
|
+
|
|
178
|
+
case "up":
|
|
179
|
+
if m.showSuggestions && len(m.suggestions) > 0 {
|
|
180
|
+
if m.selectedIndex > 0 {
|
|
181
|
+
m.selectedIndex--
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return m, nil
|
|
185
|
+
|
|
186
|
+
case "down":
|
|
187
|
+
if m.showSuggestions && len(m.suggestions) > 0 {
|
|
188
|
+
if m.selectedIndex < len(m.suggestions)-1 {
|
|
189
|
+
m.selectedIndex++
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return m, nil
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
m.textInput, cmd = m.textInput.Update(msg)
|
|
197
|
+
|
|
198
|
+
// Check if we should show suggestions
|
|
199
|
+
value := m.textInput.Value()
|
|
200
|
+
if strings.HasPrefix(value, "/") && len(value) > 0 {
|
|
201
|
+
filtered := filterCommands(value)
|
|
202
|
+
if len(filtered) > 0 {
|
|
203
|
+
m.showSuggestions = true
|
|
204
|
+
m.suggestions = filtered
|
|
205
|
+
m.selectedIndex = 0
|
|
206
|
+
} else {
|
|
207
|
+
m.showSuggestions = false
|
|
208
|
+
m.suggestions = []slashCommand{}
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
m.showSuggestions = false
|
|
212
|
+
m.suggestions = []slashCommand{}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return m, cmd
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
func (m model) View() string {
|
|
219
|
+
if m.quitting {
|
|
220
|
+
return "Goodbye! 👋\n"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
var b strings.Builder
|
|
224
|
+
|
|
225
|
+
// Title with provider info
|
|
226
|
+
titleStyle := lipgloss.NewStyle().
|
|
227
|
+
Bold(true).
|
|
228
|
+
Foreground(lipgloss.Color("#7D56F4")).
|
|
229
|
+
MarginBottom(1)
|
|
230
|
+
|
|
231
|
+
providerInfo := ""
|
|
232
|
+
if m.aiClient != nil {
|
|
233
|
+
providerInfo = fmt.Sprintf(" (%s - %s)", m.aiClient.Provider, m.aiClient.Model)
|
|
234
|
+
}
|
|
235
|
+
b.WriteString(titleStyle.Render("🤖 dcode-cli - AI Coding Assistant" + providerInfo))
|
|
236
|
+
b.WriteString("\n\n")
|
|
237
|
+
|
|
238
|
+
// Show last response if available
|
|
239
|
+
if m.lastResponse != "" {
|
|
240
|
+
responseStyle := lipgloss.NewStyle().
|
|
241
|
+
Foreground(lipgloss.Color("#FAFAFA")).
|
|
242
|
+
Background(lipgloss.Color("#1e1e1e")).
|
|
243
|
+
Padding(1, 2).
|
|
244
|
+
MarginBottom(1).
|
|
245
|
+
Width(80)
|
|
246
|
+
|
|
247
|
+
b.WriteString(responseStyle.Render("🤖 AI: " + m.lastResponse))
|
|
248
|
+
b.WriteString("\n\n")
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Show error if any
|
|
252
|
+
if m.errorMsg != "" {
|
|
253
|
+
errorStyle := lipgloss.NewStyle().
|
|
254
|
+
Foreground(lipgloss.Color("#FF0000")).
|
|
255
|
+
Bold(true)
|
|
256
|
+
b.WriteString(errorStyle.Render("⚠️ " + m.errorMsg))
|
|
257
|
+
b.WriteString("\n\n")
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Show processing indicator
|
|
261
|
+
if m.isProcessing {
|
|
262
|
+
processingStyle := lipgloss.NewStyle().
|
|
263
|
+
Foreground(lipgloss.Color("#FFA500")).
|
|
264
|
+
Italic(true)
|
|
265
|
+
b.WriteString(processingStyle.Render("⏳ Processing..."))
|
|
266
|
+
b.WriteString("\n\n")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Input box
|
|
270
|
+
b.WriteString(m.textInput.View())
|
|
271
|
+
b.WriteString("\n")
|
|
272
|
+
|
|
273
|
+
// Show suggestions if slash command is typed
|
|
274
|
+
if m.showSuggestions && len(m.suggestions) > 0 {
|
|
275
|
+
b.WriteString("\n")
|
|
276
|
+
|
|
277
|
+
suggestionStyle := lipgloss.NewStyle().
|
|
278
|
+
Border(lipgloss.RoundedBorder()).
|
|
279
|
+
BorderForeground(lipgloss.Color("#874BFD")).
|
|
280
|
+
Padding(1, 2)
|
|
281
|
+
|
|
282
|
+
selectedStyle := lipgloss.NewStyle().
|
|
283
|
+
Foreground(lipgloss.Color("#FAFAFA")).
|
|
284
|
+
Background(lipgloss.Color("#7D56F4")).
|
|
285
|
+
Bold(true)
|
|
286
|
+
|
|
287
|
+
normalStyle := lipgloss.NewStyle().
|
|
288
|
+
Foreground(lipgloss.Color("#FAFAFA"))
|
|
289
|
+
|
|
290
|
+
descStyle := lipgloss.NewStyle().
|
|
291
|
+
Foreground(lipgloss.Color("#626262")).
|
|
292
|
+
Italic(true)
|
|
293
|
+
|
|
294
|
+
var suggestions strings.Builder
|
|
295
|
+
suggestions.WriteString("Available Commands:\n\n")
|
|
296
|
+
|
|
297
|
+
for i, cmd := range m.suggestions {
|
|
298
|
+
style := normalStyle
|
|
299
|
+
if i == m.selectedIndex {
|
|
300
|
+
style = selectedStyle
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
suggestions.WriteString(style.Render(fmt.Sprintf("%-15s", cmd.Name)))
|
|
304
|
+
suggestions.WriteString(" ")
|
|
305
|
+
suggestions.WriteString(descStyle.Render(cmd.Description))
|
|
306
|
+
suggestions.WriteString("\n")
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
b.WriteString(suggestionStyle.Render(suggestions.String()))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Help text
|
|
313
|
+
b.WriteString("\n\n")
|
|
314
|
+
helpStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#626262"))
|
|
315
|
+
b.WriteString(helpStyle.Render("Press / for commands • Tab/Enter to select • ↑↓ to navigate • Ctrl+C to exit"))
|
|
316
|
+
|
|
317
|
+
return b.String()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
func filterCommands(input string) []slashCommand {
|
|
321
|
+
input = strings.ToLower(input)
|
|
322
|
+
var filtered []slashCommand
|
|
323
|
+
|
|
324
|
+
for _, cmd := range availableCommands {
|
|
325
|
+
if strings.HasPrefix(strings.ToLower(cmd.Name), input) {
|
|
326
|
+
filtered = append(filtered, cmd)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return filtered
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// StartInteractive starts the interactive prompt interface
|
|
334
|
+
func StartInteractive() error {
|
|
335
|
+
p := tea.NewProgram(initialModel())
|
|
336
|
+
_, err := p.Run()
|
|
337
|
+
return err
|
|
338
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
package registry
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"sync"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// AgentDefinition defines metadata and capabilities of an agent
|
|
9
|
+
type AgentDefinition struct {
|
|
10
|
+
// Unique identifier for the agent
|
|
11
|
+
ID string `json:"id"`
|
|
12
|
+
|
|
13
|
+
// Human-readable name
|
|
14
|
+
Name string `json:"name"`
|
|
15
|
+
|
|
16
|
+
// Description of what the agent does
|
|
17
|
+
Description string `json:"description"`
|
|
18
|
+
|
|
19
|
+
// Version of the agent
|
|
20
|
+
Version string `json:"version"`
|
|
21
|
+
|
|
22
|
+
// Tools this agent can use
|
|
23
|
+
Tools []string `json:"tools"`
|
|
24
|
+
|
|
25
|
+
// System prompt for the agent
|
|
26
|
+
SystemPrompt string `json:"system_prompt"`
|
|
27
|
+
|
|
28
|
+
// Whether this agent can invoke sub-agents
|
|
29
|
+
AllowSubAgents bool `json:"allow_sub_agents"`
|
|
30
|
+
|
|
31
|
+
// Maximum conversation turns
|
|
32
|
+
MaxTurns int `json:"max_turns"`
|
|
33
|
+
|
|
34
|
+
// Agent-specific configuration
|
|
35
|
+
Config map[string]interface{} `json:"config"`
|
|
36
|
+
|
|
37
|
+
// Executor function
|
|
38
|
+
Executor AgentExecutor `json:"-"`
|
|
39
|
+
|
|
40
|
+
// Instance - optional reference to actual agent instance (for streaming, etc.)
|
|
41
|
+
Instance interface{} `json:"-"`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// AgentExecutor is a function that executes an agent
|
|
45
|
+
type AgentExecutor func(input *AgentInput) (*AgentOutput, error)
|
|
46
|
+
|
|
47
|
+
// AgentInput represents input to an agent
|
|
48
|
+
type AgentInput struct {
|
|
49
|
+
// User query or instruction
|
|
50
|
+
Query string
|
|
51
|
+
|
|
52
|
+
// Structured input parameters
|
|
53
|
+
Parameters map[string]interface{}
|
|
54
|
+
|
|
55
|
+
// Conversation history
|
|
56
|
+
History []Message
|
|
57
|
+
|
|
58
|
+
// Available tools
|
|
59
|
+
Tools []string
|
|
60
|
+
|
|
61
|
+
// Tool registry for function calling
|
|
62
|
+
ToolRegistry *ToolRegistry
|
|
63
|
+
|
|
64
|
+
// System prompt override
|
|
65
|
+
SystemPrompt string
|
|
66
|
+
|
|
67
|
+
// Context information
|
|
68
|
+
Context map[string]interface{}
|
|
69
|
+
|
|
70
|
+
// Agent configuration
|
|
71
|
+
Config map[string]interface{}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// AgentOutput represents output from an agent
|
|
75
|
+
type AgentOutput struct {
|
|
76
|
+
// Response message
|
|
77
|
+
Message string
|
|
78
|
+
|
|
79
|
+
// Tool calls made by the agent
|
|
80
|
+
ToolCalls []ToolCall
|
|
81
|
+
|
|
82
|
+
// Structured output
|
|
83
|
+
Data map[string]interface{}
|
|
84
|
+
|
|
85
|
+
// Termination reason
|
|
86
|
+
TerminationReason string
|
|
87
|
+
|
|
88
|
+
// Metadata
|
|
89
|
+
Metadata map[string]interface{}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Message represents a conversation message
|
|
93
|
+
type Message struct {
|
|
94
|
+
Role string `json:"role"`
|
|
95
|
+
Content string `json:"content"`
|
|
96
|
+
Data map[string]interface{} `json:"data,omitempty"`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ToolCall represents a tool invocation
|
|
100
|
+
type ToolCall struct {
|
|
101
|
+
ID string `json:"id"`
|
|
102
|
+
ToolName string `json:"tool_name"`
|
|
103
|
+
Parameters map[string]interface{} `json:"parameters"` // Changed from Arguments
|
|
104
|
+
Result interface{} `json:"result,omitempty"`
|
|
105
|
+
Error string `json:"error,omitempty"`
|
|
106
|
+
Status string `json:"status,omitempty"` // Added status field
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// AgentRegistry manages agent registration and discovery
|
|
110
|
+
type AgentRegistry struct {
|
|
111
|
+
agents map[string]*AgentDefinition
|
|
112
|
+
mu sync.RWMutex
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// NewAgentRegistry creates a new agent registry
|
|
116
|
+
func NewAgentRegistry() *AgentRegistry {
|
|
117
|
+
return &AgentRegistry{
|
|
118
|
+
agents: make(map[string]*AgentDefinition),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Register registers an agent
|
|
123
|
+
func (r *AgentRegistry) Register(agent *AgentDefinition) error {
|
|
124
|
+
r.mu.Lock()
|
|
125
|
+
defer r.mu.Unlock()
|
|
126
|
+
|
|
127
|
+
if agent.ID == "" {
|
|
128
|
+
return fmt.Errorf("agent ID is required")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if agent.Name == "" {
|
|
132
|
+
return fmt.Errorf("agent name is required")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if agent.Executor == nil {
|
|
136
|
+
return fmt.Errorf("agent executor is required")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
r.agents[agent.ID] = agent
|
|
140
|
+
return nil
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Get retrieves an agent by ID
|
|
144
|
+
func (r *AgentRegistry) Get(id string) (*AgentDefinition, error) {
|
|
145
|
+
r.mu.RLock()
|
|
146
|
+
defer r.mu.RUnlock()
|
|
147
|
+
|
|
148
|
+
agent, ok := r.agents[id]
|
|
149
|
+
if !ok {
|
|
150
|
+
return nil, fmt.Errorf("agent not found: %s", id)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return agent, nil
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// List returns all registered agents
|
|
157
|
+
func (r *AgentRegistry) List() []*AgentDefinition {
|
|
158
|
+
r.mu.RLock()
|
|
159
|
+
defer r.mu.RUnlock()
|
|
160
|
+
|
|
161
|
+
agents := make([]*AgentDefinition, 0, len(r.agents))
|
|
162
|
+
for _, agent := range r.agents {
|
|
163
|
+
agents = append(agents, agent)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return agents
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Unregister removes an agent from the registry
|
|
170
|
+
func (r *AgentRegistry) Unregister(id string) error {
|
|
171
|
+
r.mu.Lock()
|
|
172
|
+
defer r.mu.Unlock()
|
|
173
|
+
|
|
174
|
+
if _, ok := r.agents[id]; !ok {
|
|
175
|
+
return fmt.Errorf("agent not found: %s", id)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
delete(r.agents, id)
|
|
179
|
+
return nil
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Has checks if an agent exists
|
|
183
|
+
func (r *AgentRegistry) Has(id string) bool {
|
|
184
|
+
r.mu.RLock()
|
|
185
|
+
defer r.mu.RUnlock()
|
|
186
|
+
|
|
187
|
+
_, ok := r.agents[id]
|
|
188
|
+
return ok
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Global registry instance
|
|
192
|
+
var globalRegistry *AgentRegistry
|
|
193
|
+
var globalRegistryOnce sync.Once
|
|
194
|
+
|
|
195
|
+
// Global returns the global agent registry
|
|
196
|
+
func Global() *AgentRegistry {
|
|
197
|
+
globalRegistryOnce.Do(func() {
|
|
198
|
+
globalRegistry = NewAgentRegistry()
|
|
199
|
+
})
|
|
200
|
+
return globalRegistry
|
|
201
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
package registry
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"sync"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// ToolDefinition defines metadata and capabilities of a tool
|
|
9
|
+
type ToolDefinition struct {
|
|
10
|
+
// Unique identifier for the tool
|
|
11
|
+
ID string `json:"id"`
|
|
12
|
+
|
|
13
|
+
// Human-readable name
|
|
14
|
+
Name string `json:"name"`
|
|
15
|
+
|
|
16
|
+
// Description of what the tool does
|
|
17
|
+
Description string `json:"description"`
|
|
18
|
+
|
|
19
|
+
// Input schema (JSON schema)
|
|
20
|
+
InputSchema map[string]interface{} `json:"input_schema"`
|
|
21
|
+
|
|
22
|
+
// Output schema (JSON schema)
|
|
23
|
+
OutputSchema map[string]interface{} `json:"output_schema"`
|
|
24
|
+
|
|
25
|
+
// Whether this tool requires confirmation
|
|
26
|
+
RequiresConfirmation bool `json:"requires_confirmation"`
|
|
27
|
+
|
|
28
|
+
// Risk level: "low", "medium", "high"
|
|
29
|
+
RiskLevel string `json:"risk_level"`
|
|
30
|
+
|
|
31
|
+
// Tool category: "file", "shell", "web", "git", etc.
|
|
32
|
+
Category string `json:"category"`
|
|
33
|
+
|
|
34
|
+
// Tool-specific configuration
|
|
35
|
+
Config map[string]interface{} `json:"config"`
|
|
36
|
+
|
|
37
|
+
// Executor function
|
|
38
|
+
Executor ToolExecutor `json:"-"`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ToolExecutor is a function that executes a tool
|
|
42
|
+
type ToolExecutor func(input *ToolInput) (*ToolOutput, error)
|
|
43
|
+
|
|
44
|
+
// ToolInput represents input to a tool
|
|
45
|
+
type ToolInput struct {
|
|
46
|
+
// Tool name
|
|
47
|
+
ToolName string
|
|
48
|
+
|
|
49
|
+
// Input parameters
|
|
50
|
+
Parameters map[string]interface{}
|
|
51
|
+
|
|
52
|
+
// Context information
|
|
53
|
+
Context map[string]interface{}
|
|
54
|
+
|
|
55
|
+
// Tool configuration
|
|
56
|
+
Config map[string]interface{}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ToolOutput represents output from a tool
|
|
60
|
+
type ToolOutput struct {
|
|
61
|
+
// Success status
|
|
62
|
+
Success bool
|
|
63
|
+
|
|
64
|
+
// Output data
|
|
65
|
+
Data interface{}
|
|
66
|
+
|
|
67
|
+
// Error message if any
|
|
68
|
+
Error string
|
|
69
|
+
|
|
70
|
+
// Metadata
|
|
71
|
+
Metadata map[string]interface{}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ToolRegistry manages tool registration and discovery
|
|
75
|
+
type ToolRegistry struct {
|
|
76
|
+
tools map[string]*ToolDefinition
|
|
77
|
+
mu sync.RWMutex
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// NewToolRegistry creates a new tool registry
|
|
81
|
+
func NewToolRegistry() *ToolRegistry {
|
|
82
|
+
return &ToolRegistry{
|
|
83
|
+
tools: make(map[string]*ToolDefinition),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Register registers a tool
|
|
88
|
+
func (r *ToolRegistry) Register(tool *ToolDefinition) error {
|
|
89
|
+
r.mu.Lock()
|
|
90
|
+
defer r.mu.Unlock()
|
|
91
|
+
|
|
92
|
+
if tool.ID == "" {
|
|
93
|
+
return fmt.Errorf("tool ID is required")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if tool.Name == "" {
|
|
97
|
+
return fmt.Errorf("tool name is required")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if tool.Executor == nil {
|
|
101
|
+
return fmt.Errorf("tool executor is required")
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
r.tools[tool.ID] = tool
|
|
105
|
+
return nil
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get retrieves a tool by ID
|
|
109
|
+
func (r *ToolRegistry) Get(id string) (*ToolDefinition, error) {
|
|
110
|
+
r.mu.RLock()
|
|
111
|
+
defer r.mu.RUnlock()
|
|
112
|
+
|
|
113
|
+
tool, ok := r.tools[id]
|
|
114
|
+
if !ok {
|
|
115
|
+
return nil, fmt.Errorf("tool not found: %s", id)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return tool, nil
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// List returns all registered tools
|
|
122
|
+
func (r *ToolRegistry) List() []*ToolDefinition {
|
|
123
|
+
r.mu.RLock()
|
|
124
|
+
defer r.mu.RUnlock()
|
|
125
|
+
|
|
126
|
+
tools := make([]*ToolDefinition, 0, len(r.tools))
|
|
127
|
+
for _, tool := range r.tools {
|
|
128
|
+
tools = append(tools, tool)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return tools
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ListByCategory returns tools in a specific category
|
|
135
|
+
func (r *ToolRegistry) ListByCategory(category string) []*ToolDefinition {
|
|
136
|
+
r.mu.RLock()
|
|
137
|
+
defer r.mu.RUnlock()
|
|
138
|
+
|
|
139
|
+
tools := make([]*ToolDefinition, 0)
|
|
140
|
+
for _, tool := range r.tools {
|
|
141
|
+
if tool.Category == category {
|
|
142
|
+
tools = append(tools, tool)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return tools
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Unregister removes a tool from the registry
|
|
150
|
+
func (r *ToolRegistry) Unregister(id string) error {
|
|
151
|
+
r.mu.Lock()
|
|
152
|
+
defer r.mu.Unlock()
|
|
153
|
+
|
|
154
|
+
if _, ok := r.tools[id]; !ok {
|
|
155
|
+
return fmt.Errorf("tool not found: %s", id)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
delete(r.tools, id)
|
|
159
|
+
return nil
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Has checks if a tool exists
|
|
163
|
+
func (r *ToolRegistry) Has(id string) bool {
|
|
164
|
+
r.mu.RLock()
|
|
165
|
+
defer r.mu.RUnlock()
|
|
166
|
+
|
|
167
|
+
_, ok := r.tools[id]
|
|
168
|
+
return ok
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Global tool registry instance
|
|
172
|
+
var globalToolRegistry *ToolRegistry
|
|
173
|
+
var globalToolRegistryOnce sync.Once
|
|
174
|
+
|
|
175
|
+
// GlobalTools returns the global tool registry
|
|
176
|
+
func GlobalTools() *ToolRegistry {
|
|
177
|
+
globalToolRegistryOnce.Do(func() {
|
|
178
|
+
globalToolRegistry = NewToolRegistry()
|
|
179
|
+
})
|
|
180
|
+
return globalToolRegistry
|
|
181
|
+
}
|