SmartComplete is a standalone Go library that provides AI-powered code completion functionality for the Storm multi-project LLM chat application. It integrates seamlessly with Storm's existing architecture with minimal code changes.
- Fill-in-Middle (FIM) Completions: Context-aware code generation using prefix/suffix
- Project-Aware: Leverages Storm's project structure (authorized files, AGENTS.md, discussion context)
- Manual Trigger: Button/hotkey-based completion (not real-time)
- Caching: Intelligent caching with TTL and file hash validation
- Rate Limiting: Per-project request throttling (10/min, 50/hour defaults)
- Multi-LLM Support: Works with any grokker-compatible LLM
- Minimal Integration: Storm only needs to implement one interface
go get github.com/yourusername/smartcompleteimport "github.com/yourusername/smartcomplete"
// Load or create config
config := smartcomplete.DefaultConfig()
// Or load from file:
// config, err := smartcomplete.LoadConfig("completion.yaml")
// Create completion service
completionSvc, err := smartcomplete.NewCompletionService(config)
if err != nil {
log.Fatalf("Failed to initialize completion service: %v", err)
}Storm needs to implement this simple interface:
type StormProjectGetter struct {
projects *Projects // Storm's existing projects registry
}
func (g *StormProjectGetter) GetProjectBaseDir(projectID string) (string, error) {
project, err := g.projects.Get(projectID)
if err != nil {
return "", err
}
return project.BaseDir, nil
}
func (g *StormProjectGetter) GetProjectAuthorizedFiles(projectID string) ([]string, error) {
project, err := g.projects.Get(projectID)
if err != nil {
return nil, err
}
return project.AuthorizedFiles, nil
}
func (g *StormProjectGetter) GetProjectDiscussionFile(projectID string) (string, error) {
project, err := g.projects.Get(projectID)
if err != nil {
return "", err
}
return project.MarkdownFile, nil
}
func (g *StormProjectGetter) ReadFile(absolutePath string) ([]byte, error) {
return ioutil.ReadFile(absolutePath)
}// Handle new "codeCompletion" message type
case "codeCompletion":
var req smartcomplete.CompletionRequest
if err := mapstructure.Decode(msg, &req); err != nil {
sendError(conn, "Invalid completion request", err)
return
}
// Create project getter
getter := &StormProjectGetter{projects: projects}
// Call completion service
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resp, err := completionSvc.Complete(ctx, req, getter)
if err != nil {
sendError(conn, "Completion failed", err)
return
}
// Send response back
response := map[string]interface{}{
"type": "completionResponse",
"queryID": msg["queryID"],
"completion": resp.Completion,
"latencyMs": resp.LatencyMs,
"model": resp.Model,
"tokensUsed": resp.TokensUsed,
"cachedResult": resp.CachedResult,
}
conn.WriteJSON(response)// In project.html - add completion button to file editor
function requestCompletion() {
const fileContent = editor.getValue();
const cursor = editor.getCursor();
const message = {
type: 'codeCompletion',
queryID: generateQueryID(),
projectId: currentProjectID,
filePath: currentFile,
cursorLine: cursor.line,
cursorColumn: cursor.ch,
};
ws.send(JSON.stringify(message));
showLoadingIndicator('Generating code...');
}
// Handle completion response
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'completionResponse') {
hideLoadingIndicator();
showCompletionPreview(msg.completion, msg.queryID);
}
};
// Show completion with accept/reject buttons
function showCompletionPreview(completion, queryID) {
const modal = document.getElementById('completion-modal');
document.getElementById('completion-text').textContent = completion;
modal.style.display = 'block';
document.getElementById('accept-btn').onclick = () => {
editor.replaceSelection(completion);
modal.style.display = 'none';
};
document.getElementById('reject-btn').onclick = () => {
modal.style.display = 'none';
};
}config := smartcomplete.DefaultConfig()Provides:
- LLM: sonar-deep-research
- Max Tokens: 500
- Temperature: 0.2
- Context Tokens: 10,000
- Cache TTL: 5 minutes
- Rate Limits: 10/min, 50/hour
config := &smartcomplete.Config{
DefaultLLM: "sonar-deep-research",
MaxTokens: 500,
Temperature: 0.2,
RequestTimeout: 30 * time.Second,
MaxContextTokens: 10000,
IncludeAgentsFile: true,
IncludeDiscussion: true,
MaxDiscussionRounds: 3,
EnableCache: true,
CacheTTL: 5 * time.Minute,
MaxCacheSize: 100 * 1024 * 1024, // 100MB
MaxRequestsPerMinute: 10,
MaxRequestsPerHour: 50,
}Create completion.yaml:
default_llm: "sonar-deep-research"
max_tokens: 500
temperature: 0.2
request_timeout: 30s
# Context gathering
max_context_tokens: 10000
include_agents_file: true
include_discussion: true
max_discussion_rounds: 3
# Caching
enable_cache: true
cache_ttl: 5m
max_cache_size: 104857600 # 100MB
# Rate limiting
max_requests_per_minute: 10
max_requests_per_hour: 50Load it:
config, err := smartcomplete.LoadConfig("completion.yaml")type CompletionRequest struct {
ProjectID string `json:"projectId"` // Required
FilePath string `json:"filePath"` // Required, relative to BaseDir
CursorLine int `json:"cursorLine"` // Required, 0-indexed
CursorColumn int `json:"cursorColumn"` // Required, 0-indexed
LLM string `json:"llm,omitempty"` // Optional, uses default if empty
MaxTokens int `json:"maxTokens,omitempty"`
ContextFiles []string `json:"contextFiles,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
}type CompletionResponse struct {
Completion string `json:"completion"` // Generated code
LatencyMs int64 `json:"latencyMs"` // Request duration
Model string `json:"model"` // LLM used
TokensUsed int `json:"tokensUsed"` // Tokens consumed
CachedResult bool `json:"cachedResult"` // Was cached?
Timestamp time.Time `json:"timestamp"` // When generated
}type ProjectGetter interface {
GetProjectBaseDir(projectID string) (string, error)
GetProjectAuthorizedFiles(projectID string) ([]string, error)
GetProjectDiscussionFile(projectID string) (string, error)
ReadFile(absolutePath string) ([]byte, error)
}{
"type": "codeCompletion",
"queryID": "uuid-1234",
"projectId": "my-project",
"filePath": "src/main.go",
"cursorLine": 42,
"cursorColumn": 8,
"llm": "sonar-deep-research",
"maxTokens": 500
}{
"type": "completionResponse",
"queryID": "uuid-1234",
"completion": "result := a + b\n fmt.Println(result)",
"latencyMs": 1847,
"model": "sonar-deep-research",
"tokensUsed": 234,
"cachedResult": false
}{
"type": "error",
"queryID": "uuid-1234",
"error": "File not authorized",
"message": "File src/secret.go is not in project authorized files list"
}SmartComplete intelligently gathers context for completions:
- Target File: Extracts prefix (before cursor) and suffix (after cursor)
- AGENTS.md: Walks up directory tree to find project instructions
- Discussion: Extracts last N rounds from project's markdown file
- Additional Files: Can include imports or related files
- Token Budget: Prioritizes target file → AGENTS → discussion → related files
The library constructs a structured prompt:
You are an expert {language} programmer. Complete the code below.
PROJECT INSTRUCTIONS:
{AGENTS.md content}
RECENT PROJECT DISCUSSION:
{Last 3 discussion rounds}
RELATED FILES:
{Additional context files}
CODE BEFORE CURSOR:
{Prefix}
CODE AFTER CURSOR:
{Suffix}
INSTRUCTIONS:
Complete only the middle section between the cursor position.
Provide syntactically correct, idiomatic {language} code.
Do not repeat the prefix or suffix.
Output only the completion, nothing else.
Completions are cached with:
- Key:
{projectID}:{filePath}:{cursorLine}:{cursorColumn}:{llm} - Validation: File content hash must match
- TTL: 5 minutes (configurable)
- Eviction: Simple oldest-first when cache exceeds max size
Cache hits avoid LLM calls entirely, reducing latency to <1ms.
Per-project rate limiting prevents abuse:
- Minute Window: 10 requests/minute (default)
- Hour Window: 50 requests/hour (default)
- Reset: Windows reset on a rolling basis
All errors are wrapped with context:
resp, err := completionSvc.Complete(ctx, req, getter)
if err != nil {
var completionErr *smartcomplete.CompletionError
if errors.As(err, &completionErr) {
log.Printf("Code: %s, Message: %s", completionErr.Code, completionErr.Message)
// Handle specific error codes
switch completionErr.Code {
case smartcomplete.CodeRateLimit:
// Handle rate limit
case smartcomplete.CodeFileAccess:
// Handle file access error
default:
// Handle generic error
}
}
}VALIDATION_ERROR: Invalid request parametersRATE_LIMIT: Rate limit exceededFILE_ACCESS: File read/authorization errorPROJECT_ACCESS: Project not found or inaccessibleCONTEXT_ERROR: Context gathering failedLLM_ERROR: LLM request failedCACHE_ERROR: Cache operation failedTIMEOUT: Request timeoutINTERNAL_ERROR: Internal service error
- Cache Hit: <1ms
- Cache Miss + LLM: 1-15 seconds (depends on LLM)
- Context Gathering: ~50ms (for typical project)
- Validation: <10ms
- Memory: ~5-10MB per active project
- Cache: Up to 100MB (configurable)
- Network: Only during LLM calls
Run tests:
go test ./...Run with coverage:
go test -cover ./...Storm (imports smartcomplete)
├─ Implements ProjectGetter interface
├─ Calls completionSvc.Complete()
└─ Handles WebSocket integration
SmartComplete Library
├─ completion.go # Main service
├─ config.go # Configuration
├─ context.go # Context gathering
├─ fim.go # FIM prompt formatting
├─ cache.go # Response caching
├─ errors.go # Error types
└─ ratelimit.go # Rate limiting
- ✅ Basic FIM completions
- ✅ Project-aware context
- ✅ Caching
- ✅ Rate limiting
- Streaming responses
- Multiple completion options
- Edit suggestions (not just FIM)
- Smart import handling
- Test generation
- Documentation generation
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
MIT License - see LICENSE file for details
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Full docs
- Basic FIM completions
- Project-aware context gathering
- AGENTS.md integration
- Discussion context extraction
- Response caching with TTL
- Per-project rate limiting
- Comprehensive error handling