feat: agent concurrency, conversation summaries, AI tools config, UI polish
Some checks failed
Stable Release / stable (push) Failing after 33s

- Agent slot limiter for concurrent tool execution
- Conversation summarization with soft-delete (MarkSummarized)
- ANSI stripping in terminal tool output
- Configurable crush-run timeout (default 600s, max 900s)
- Starship theme refactor, AI tools config grid, system update UI
- Streaming segments refactor, summarized messages block in feed
- CSS: headings, scrollbars, tool cards, summary block styles
- i18n additions (en+fr) for tools, updates, config

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
Augustin
2026-04-27 00:01:22 +02:00
parent d98110ce8a
commit 3740454201
23 changed files with 1028 additions and 556 deletions

View File

@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"regexp"
"strings"
@@ -17,6 +16,14 @@ import (
)
var thinkRegex = regexp.MustCompile(`(?s)<[Tt]hink[^>]*>.*?</[Tt]hink>`)
var providerToolBlockRegex = regexp.MustCompile(`(?s)<[a-zA-Z][a-zA-Z0-9]*:tool_call[^>]*>.*?</[a-zA-Z][a-zA-Z0-9]*:tool_call>`)
var providerTagRegex = regexp.MustCompile(`(?s)</?[a-zA-Z][a-zA-Z0-9]*:[a-zA-Z_]+[^>]*>`)
var xmlToolTagRegex = regexp.MustCompile(`(?s)</?(invoke|parameter|tool_call|tool_result)[^>]*>`)
var bracketToolCallRegex = regexp.MustCompile(`(?m)^\[(?:terminal|shell|bash|command|execute)\]\s*\{[^}]*\}\s*$`)
var streamBlockStartRegex = regexp.MustCompile(`<[a-zA-Z][a-zA-Z0-9]*:tool_call`)
var streamXmlStartRegex = regexp.MustCompile(`<(?:invoke|parameter|tool_call|tool_result)[\s>]`)
var streamBracketStartRegex = regexp.MustCompile(`\[(?:terminal|shell|bash|command|execute)\]\s*\{`)
const maxHistorySize = 100
@@ -197,7 +204,7 @@ func (o *Orchestrator) Send(userMessage string) (string, error) {
return "", err
}
content := cleanAIResponse(chatResp.Choices[0].Message.Content)
content := CleanAIResponse(chatResp.Choices[0].Message.Content)
o.histMu.Lock()
o.history = append(o.history, Message{
Role: "assistant",
@@ -297,7 +304,7 @@ func (o *Orchestrator) SendStream(userMessage string, onChunk func(string)) (str
return fullContent.String(), fmt.Errorf("read stream: %w", err)
}
content := cleanAIResponse(fullContent.String())
content := CleanAIResponse(fullContent.String())
o.histMu.Lock()
o.history = append(o.history, Message{
Role: "assistant",
@@ -388,6 +395,7 @@ func (o *Orchestrator) SendWithToolsStream(messages []Message, onChunk ChunkCall
var fullContent strings.Builder
var accumulatedToolCalls []ToolCallMsg
var totalTokens int
var insideToolBlock bool
scanner := bufio.NewScanner(resp.Body)
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
@@ -411,7 +419,10 @@ func (o *Orchestrator) SendWithToolsStream(messages []Message, onChunk ChunkCall
chunk := chatResp.Choices[0].Delta.Content
if chunk != "" {
fullContent.WriteString(chunk)
onChunk(chunk, nil)
cleanedChunk := CleanStreamChunk(chunk, &insideToolBlock)
if cleanedChunk != "" {
onChunk(cleanedChunk, nil)
}
}
// Handle delta tool calls
@@ -463,15 +474,19 @@ func (o *Orchestrator) SendWithToolsStream(messages []Message, onChunk ChunkCall
}{},
}
finalContent := cleanAIResponse(fullContent.String())
finalContent := CleanAIResponse(fullContent.String())
finalResp.Choices[0].Message.Content = finalContent
finalResp.Choices[0].Message.ToolCalls = accumulatedToolCalls
return finalResp, nil
}
func cleanAIResponse(content string) string {
func CleanAIResponse(content string) string {
content = thinkRegex.ReplaceAllString(content, "")
content = providerToolBlockRegex.ReplaceAllString(content, "")
content = providerTagRegex.ReplaceAllString(content, "")
content = xmlToolTagRegex.ReplaceAllString(content, "")
content = bracketToolCallRegex.ReplaceAllString(content, "")
lines := strings.Split(content, "\n")
var clean []string
inBlock := false
@@ -494,6 +509,35 @@ func cleanAIResponse(content string) string {
return result
}
// CleanStreamChunk applies lightweight cleaning to individual streaming chunks.
// It tracks state via a bool pointer to suppress content inside tool-call blocks.
func CleanStreamChunk(chunk string, insideBlock *bool) string {
if *insideBlock {
// Check for closing tag
if strings.Contains(chunk, ":tool_call>") {
*insideBlock = false
}
return ""
}
// Check for opening tool_call block
if streamBlockStartRegex.MatchString(chunk) {
*insideBlock = true
// If closing tag also in same chunk, emit nothing
if strings.Contains(chunk, ":tool_call>") {
*insideBlock = false
}
return ""
}
// Clean individual tags and bracket calls
cleaned := providerTagRegex.ReplaceAllString(chunk, "")
cleaned = xmlToolTagRegex.ReplaceAllString(cleaned, "")
cleaned = bracketToolCallRegex.ReplaceAllString(cleaned, "")
return cleaned
}
func getProviderBaseURL(name string) string {
switch name {
case "minimax":
@@ -616,6 +660,5 @@ func (o *Orchestrator) sendWithFallback(reqBody ChatRequest, baseURLOverride str
return &chatResp, prov.Name, nil
}
log.Printf("[orchestrator] fallback from %v to next provider", triedProviders)
return nil, "", lastErr
}