released the mock
This commit is contained in:
+31
-21
@@ -117,6 +117,27 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
|
||||
headerHeight := 1
|
||||
inputHeight := 5 // Textarea height(3) + border/padding
|
||||
|
||||
vpWidth := msg.Width - 4 // Account for viewportStyle padding and border
|
||||
vpHeight := msg.Height - headerHeight - inputHeight - 2
|
||||
|
||||
if !m.ready {
|
||||
m.viewport = viewport.New(vpWidth, vpHeight)
|
||||
m.viewport.SetContent(strings.Join(m.messages, "\n\n"))
|
||||
m.ready = true
|
||||
} else {
|
||||
m.viewport.Width = vpWidth
|
||||
m.viewport.Height = vpHeight
|
||||
}
|
||||
|
||||
m.textarea.SetWidth(msg.Width - 4)
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.Type {
|
||||
case tea.KeyCtrlC, tea.KeyEsc:
|
||||
@@ -129,40 +150,29 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if strings.TrimSpace(v) == "" || m.isThinking {
|
||||
return m, nil
|
||||
}
|
||||
m.messages = append(m.messages, userStyle.Render("You")+"\n"+v)
|
||||
|
||||
wrappedUser := lipgloss.NewStyle().Width(m.viewport.Width).Render(userStyle.Render("You") + "\n" + v)
|
||||
m.messages = append(m.messages, wrappedUser)
|
||||
m.textarea.Reset()
|
||||
m.viewport.SetContent(strings.Join(m.messages, "\n\n"))
|
||||
m.viewport.GotoBottom()
|
||||
m.isThinking = true
|
||||
return m, m.runAgent(v)
|
||||
}
|
||||
|
||||
case agentResponseMsg:
|
||||
m.isThinking = false
|
||||
var newMsg string
|
||||
if msg.err != nil {
|
||||
m.messages = append(m.messages, sysStyle.Render(fmt.Sprintf("Error: %v", msg.err)))
|
||||
newMsg = sysStyle.Render(fmt.Sprintf("Error: %v", msg.err))
|
||||
} else {
|
||||
m.messages = append(m.messages, agentStyle.Render("Sidekick")+"\n"+msg.response)
|
||||
newMsg = agentStyle.Render("Sidekick") + "\n" + msg.response
|
||||
}
|
||||
wrappedMsg := lipgloss.NewStyle().Width(m.viewport.Width).Render(newMsg)
|
||||
m.messages = append(m.messages, wrappedMsg)
|
||||
m.viewport.SetContent(strings.Join(m.messages, "\n\n"))
|
||||
m.viewport.GotoBottom()
|
||||
return m, nil
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
|
||||
headerHeight := 1
|
||||
inputHeight := 5 // Textarea height(3) + border/padding
|
||||
|
||||
if !m.ready {
|
||||
m.viewport = viewport.New(msg.Width-2, msg.Height-headerHeight-inputHeight-2)
|
||||
m.viewport.SetContent(strings.Join(m.messages, "\n\n"))
|
||||
m.ready = true
|
||||
} else {
|
||||
m.viewport.Width = msg.Width - 2
|
||||
m.viewport.Height = msg.Height - headerHeight - inputHeight - 2
|
||||
}
|
||||
|
||||
m.textarea.SetWidth(msg.Width - 4)
|
||||
}
|
||||
|
||||
m.textarea, tiCmd = m.textarea.Update(msg)
|
||||
@@ -192,7 +202,7 @@ func (m model) View() string {
|
||||
statusStyle.Render(" "+status+" "),
|
||||
)
|
||||
|
||||
vpView := viewportStyle.Width(m.width - 2).Height(m.height - 10).Render(m.viewport.View())
|
||||
vpView := viewportStyle.Width(m.width - 2).Render(m.viewport.View())
|
||||
|
||||
taStyle := textareaStyle
|
||||
if m.textarea.Focused() {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
@@ -54,3 +56,18 @@ func TestModelUpdate(t *testing.T) {
|
||||
t.Error("expected message list to grow after response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageWrapping(t *testing.T) {
|
||||
m := initialModel()
|
||||
m.ready = true
|
||||
m.viewport = viewport.New(10, 5) // Very narrow
|
||||
|
||||
longMsg := "This is a very long message that should be wrapped."
|
||||
respModel, _ := m.Update(agentResponseMsg{response: longMsg})
|
||||
rm := respModel.(model)
|
||||
|
||||
lastMsg := rm.messages[len(rm.messages)-1]
|
||||
if !strings.Contains(lastMsg, "\n") {
|
||||
t.Errorf("expected long message to be wrapped, but no newline found")
|
||||
}
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
tool_response_threshold = 10000
|
||||
|
||||
[mcp_listener]
|
||||
transport = "http"
|
||||
port = 8080
|
||||
|
||||
[models]
|
||||
[models.default]
|
||||
model_id = "z-ai/glm5"
|
||||
endpoint = "https://integrate.api.nvidia.com/v1"
|
||||
api_key = "env:SK_NV_API_KEY"
|
||||
temperature = 0.0
|
||||
max_tokens = 4096
|
||||
timeout_secs = 120
|
||||
|
||||
[models.fast]
|
||||
model_id = "minimaxai/minimax-m2.5"
|
||||
endpoint = "https://integrate.api.nvidia.com/v1"
|
||||
api_key = "env:SK_NV_API_KEY"
|
||||
temperature = 0.1
|
||||
max_tokens = 1024
|
||||
timeout_secs = 60
|
||||
|
||||
[mcp_servers]
|
||||
[mcp_servers.filesystem]
|
||||
transport = "stdio"
|
||||
command = "npx"
|
||||
args = ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
||||
env = []
|
||||
|
||||
[mcp_servers.context7]
|
||||
transport = "http"
|
||||
url = "https://mcp.context7.com/mcp"
|
||||
|
||||
[mcp_servers.exa]
|
||||
transport = "stdio"
|
||||
command = "npx"
|
||||
args = ["-y", "@exa/mcp-server"]
|
||||
|
||||
[agents]
|
||||
[agents.coordinator]
|
||||
role = "COORDINATOR"
|
||||
model_id = "default"
|
||||
enable_shell_exec = true
|
||||
system_prompt = "You are the Lead Architect. You oversee the software development lifecycle, plan features, and delegate implementation to specialized subagents."
|
||||
description = "Lead Architect and project coordinator"
|
||||
max_iterations = 15
|
||||
subagents = ["coder", "reviewer", "tester"]
|
||||
toolsets = ["filesystem", "context7", "exa"]
|
||||
goals = [
|
||||
"Coordinate complex software engineering tasks",
|
||||
"Ensure high-quality code delivery by delegating to specialized agents",
|
||||
"Validate the final solution using shell execution and test results"
|
||||
]
|
||||
|
||||
[agents.coder]
|
||||
role = "SPECIALIST"
|
||||
model_id = "default"
|
||||
enable_shell_exec = true
|
||||
system_prompt = "You are a Senior Software Engineer specializing in implementation. Your goal is to write clean, efficient, and well-documented code based on the architect's instructions."
|
||||
description = "Senior Developer focused on implementation and refactoring"
|
||||
max_iterations = 10
|
||||
toolsets = ["filesystem", "context7"]
|
||||
goals = [
|
||||
"Implement features and bug fixes with precision",
|
||||
"Adhere to project-specific coding standards and best practices",
|
||||
"Verify changes by running builds or small code snippets"
|
||||
]
|
||||
|
||||
[agents.reviewer]
|
||||
role = "SPECIALIST"
|
||||
model_id = "fast"
|
||||
system_prompt = "You are a Quality Assurance Specialist and Code Reviewer. Your role is to analyze code for potential bugs, security vulnerabilities, and style violations."
|
||||
description = "Code Reviewer and Quality Assurance specialist"
|
||||
max_iterations = 8
|
||||
toolsets = ["filesystem", "context7"]
|
||||
goals = [
|
||||
"Perform thorough code reviews and identify edge cases",
|
||||
"Suggest optimizations and improvements",
|
||||
"Use search tools and context7 for deep library/best practice research"
|
||||
]
|
||||
|
||||
[agents.tester]
|
||||
role = "SPECIALIST"
|
||||
model_id = "fast"
|
||||
enable_shell_exec = true
|
||||
system_prompt = "You are a Test Engineer. Your primary responsibility is to write and execute tests to ensure software reliability and correctness."
|
||||
description = "Test Engineer focused on automated testing and verification"
|
||||
max_iterations = 10
|
||||
toolsets = ["filesystem"]
|
||||
goals = [
|
||||
"Develop comprehensive test suites (unit, integration, e2e)",
|
||||
"Run tests and analyze failures to provide actionable feedback",
|
||||
"Ensure 100% verification of implemented features"
|
||||
]
|
||||
@@ -1,7 +1,12 @@
|
||||
package sidekick
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// LLMClient represents an abstraction over the LLM provider
|
||||
@@ -9,7 +14,84 @@ type LLMClient interface {
|
||||
Call(ctx context.Context, model ModelConfig, messages []Message, tools map[string]ToolDefinition) (Message, error)
|
||||
}
|
||||
|
||||
var defaultLLMClient LLMClient = &mockLLMClient{}
|
||||
// standardLLMClient is an OpenAI-compatible implementation
|
||||
type standardLLMClient struct{}
|
||||
|
||||
func (s *standardLLMClient) Call(ctx context.Context, model ModelConfig, msgs []Message, ts map[string]ToolDefinition) (Message, error) {
|
||||
client := &http.Client{
|
||||
Timeout: model.Timeout,
|
||||
}
|
||||
|
||||
reqBody := map[string]interface{}{
|
||||
"model": model.ModelID,
|
||||
"messages": msgs,
|
||||
"temperature": model.Temperature,
|
||||
}
|
||||
if model.MaxTokens > 0 {
|
||||
reqBody["max_tokens"] = model.MaxTokens
|
||||
}
|
||||
|
||||
if len(ts) > 0 {
|
||||
var tools []map[string]interface{}
|
||||
for _, t := range ts {
|
||||
tools = append(tools, t.ConvertToOpenAITool())
|
||||
}
|
||||
reqBody["tools"] = tools
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return Message{}, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", model.Endpoint+"/chat/completions", bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
return Message{}, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+model.APIKey)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return Message{}, fmt.Errorf("HTTP request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return Message{}, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return Message{}, fmt.Errorf("API returned error (%d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var openAIResp struct {
|
||||
Choices []struct {
|
||||
Message Message `json:"message"`
|
||||
} `json:"choices"`
|
||||
Error *struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &openAIResp); err != nil {
|
||||
return Message{}, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if openAIResp.Error != nil {
|
||||
return Message{}, fmt.Errorf("API error: %s", openAIResp.Error.Message)
|
||||
}
|
||||
|
||||
if len(openAIResp.Choices) == 0 {
|
||||
return Message{}, fmt.Errorf("API returned no choices")
|
||||
}
|
||||
|
||||
return openAIResp.Choices[0].Message, nil
|
||||
}
|
||||
|
||||
var defaultLLMClient LLMClient = &standardLLMClient{}
|
||||
|
||||
// SetLLMClient allows overriding the LLM client (e.g. for testing)
|
||||
func SetLLMClient(client LLMClient) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Sidekick Multiline System Prompts Example
|
||||
# Use {{template "name"}} to include other prompts as subprompts.
|
||||
|
||||
base_identity = """
|
||||
You are a highly capable AI software engineer operating within the Sidekick framework.
|
||||
Your role is to assist the user by following instructions precisely and using
|
||||
the available tools or subagents effectively. Always be professional, concise,
|
||||
and technical.
|
||||
"""
|
||||
|
||||
base_formatting = """
|
||||
When using tools:
|
||||
1. State your reasoning for calling the tool.
|
||||
2. Formulate the JSON action with the correct parameters.
|
||||
3. If a tool result is buffered to a file, use read_file or grep_file to inspect it.
|
||||
|
||||
When responding to the user:
|
||||
- Use Markdown for formatting and code blocks with appropriate language tags.
|
||||
- Be direct, technical, and precise.
|
||||
"""
|
||||
|
||||
coding_standards = """
|
||||
Adhere to these universal coding standards:
|
||||
- Use clear, descriptive variable and function names.
|
||||
- Keep functions small and focused on a single responsibility.
|
||||
- Include meaningful comments for complex logic.
|
||||
- Ensure proper error handling and logging.
|
||||
"""
|
||||
|
||||
coordinator = """
|
||||
{{template "base_identity"}}
|
||||
{{template "base_formatting"}}
|
||||
|
||||
You are the COORDINATOR (Lead Architect). You are the primary point of contact for the user.
|
||||
Your workflow:
|
||||
1. Analyze the user's request and map out the necessary changes.
|
||||
2. Use the 'coder' subagent to implement the changes.
|
||||
3. Use the 'reviewer' subagent to verify the code quality.
|
||||
4. Use the 'tester' subagent to ensure everything works as expected.
|
||||
5. If any step fails, iterate with the relevant subagent.
|
||||
|
||||
You have full shell access to run builds and orchestrate the entire process.
|
||||
"""
|
||||
|
||||
coder = """
|
||||
{{template "base_identity"}}
|
||||
{{template "base_formatting"}}
|
||||
{{template "coding_standards"}}
|
||||
|
||||
You are the CODER specialist. Your task is to implement specific features or fixes
|
||||
as directed by the Architect. You should focus on writing the actual code and
|
||||
ensuring it compiles/runs. Use shell_exec sparingly to verify your changes if necessary.
|
||||
"""
|
||||
|
||||
reviewer = """
|
||||
{{template "base_identity"}}
|
||||
{{template "base_formatting"}}
|
||||
|
||||
You are the REVIEWER specialist. Your goal is to find flaws.
|
||||
Do not just look for syntax errors; look for logical bugs, inefficient algorithms,
|
||||
and potential security risks. Compare the implementation against the requested
|
||||
requirements and coding standards.
|
||||
"""
|
||||
|
||||
tester = """
|
||||
{{template "base_identity"}}
|
||||
{{template "base_formatting"}}
|
||||
|
||||
You are the TESTER specialist. You must ensure the software is bulletproof.
|
||||
Write test cases that cover both happy paths and edge cases.
|
||||
Use shell_exec to run the project's test suite and report results back
|
||||
to the Architect.
|
||||
"""
|
||||
Reference in New Issue
Block a user