release: 0.0.41

- 修复内存泄漏问题,该可能导致内存异常占用
- 支持俄语增加翻译范围
- 修复qwen-3.8-max中断问题(mimo也应该属于同一类问题)
- 修复claude模型可能无法识别图片问题 @GGHansome
- 支持自定义Openai端点 @Sxuan-Coder
- WebSearch 接入百度搜索,DuckDuckGo 作为兜底 @杨超
- 修复一些兼容性问题 @kael-odin
- 修复window上一些表现问题 @philau2512

🔔 如何让AI自动拉模型配置? (以下为提示词,把地址和密钥换为你的)
我的模型配置在 ~/.cursor-local-assistant-v2/config.yaml,我的API地址是:https://xxx 密钥是xxx,帮我拉所有模型配置进去,不要影响已有模型,根据models标准接口拉取。
This commit is contained in:
leookun
2026-07-27 00:55:02 +08:00
parent e401303f64
commit 0f6b0f9484
28 changed files with 808 additions and 441 deletions
+11
View File
@@ -294,6 +294,8 @@ func Run(resources EmbeddedResources) error {
statusItem.SetLabel("Status: Running")
} else if locale == "ja-JP" {
statusItem.SetLabel("状態:実行中")
} else if locale == "ru-RU" {
statusItem.SetLabel("Статус: запущено")
} else {
statusItem.SetLabel("状态:运行中")
}
@@ -302,6 +304,8 @@ func Run(resources EmbeddedResources) error {
statusItem.SetLabel("Status: Not Started")
} else if locale == "ja-JP" {
statusItem.SetLabel("状態:未起動")
} else if locale == "ru-RU" {
statusItem.SetLabel("Статус: не запущено")
} else {
statusItem.SetLabel("状态:未启动")
}
@@ -321,6 +325,13 @@ func Run(resources EmbeddedResources) error {
showItem.SetLabel("ウィンドウを表示")
hideItem.SetLabel("ウィンドウを非表示")
quitItem.SetLabel("終了")
} else if locale == "ru-RU" {
startItem.SetLabel("Запустить сервис")
stopItem.SetLabel("Остановить сервис")
updateItem.SetLabel("Проверить обновления")
showItem.SetLabel("Показать окно")
hideItem.SetLabel("Скрыть окно")
quitItem.SetLabel("Выход")
} else {
startItem.SetLabel("启动服务")
stopItem.SetLabel("停止服务")
+1 -1
View File
@@ -249,7 +249,7 @@ func canMergeProviderAssistantToolCalls(last Message, current Message) bool {
if strings.TrimSpace(last.Role) != "assistant" || strings.TrimSpace(current.Role) != "assistant" {
return false
}
if len(last.ToolCalls) == 0 || len(current.ToolCalls) == 0 {
if len(current.ToolCalls) == 0 {
return false
}
if strings.TrimSpace(last.ToolCallID) != "" || strings.TrimSpace(last.Name) != "" {
@@ -0,0 +1,58 @@
package modeladapter
import (
"reflect"
"testing"
)
func TestSanitizeProviderMessagesMergesLegacyAssistantTextAndToolCallTurnsIdempotently(t *testing.T) {
input := []Message{
{
Role: "assistant",
Content: "Now let me pass stream.Mode in service.go.",
ReasoningContent: "I need to update service.go.",
},
{
Role: "assistant",
Content: "",
ReasoningContent: "I need to update service.go.",
ToolCalls: []ToolCallDescriptor{
{
ID: "call_1",
Type: "function",
Function: ToolCallFunctionShape{
Name: "PatchEdit",
Arguments: `{"path":"/workspace/service.go"}`,
},
},
},
},
{
Role: "tool",
Content: `{"success":{"path":"/workspace/service.go"}}`,
ToolCallID: "call_1",
Name: "PatchEdit",
},
}
first := sanitizeProviderMessages(input)
if len(first) != 2 {
t.Fatalf("message count = %d, want 2: %#v", len(first), first)
}
assistant := first[0]
if assistant.Content != input[0].Content {
t.Fatalf("assistant content = %q", assistant.Content)
}
if assistant.ReasoningContent != input[0].ReasoningContent {
t.Fatalf("assistant reasoning = %q, want one copy of %q", assistant.ReasoningContent, input[0].ReasoningContent)
}
if len(assistant.ToolCalls) != 1 || assistant.ToolCalls[0].ID != "call_1" {
t.Fatalf("assistant tool calls = %#v", assistant.ToolCalls)
}
second := sanitizeProviderMessages(first)
if !reflect.DeepEqual(second, first) {
t.Fatalf("sanitizing normalized messages changed them:\nfirst: %#v\nsecond: %#v", first, second)
}
}
+4 -23
View File
@@ -897,8 +897,8 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
reasoningSignature := ""
pendingToolCalls := make([]ToolCallDescriptor, 0, len(payload.Content))
pendingToolResults := make([]Message, 0, len(payload.Content))
flushAssistantText := func(forceReasoningOnly bool) {
if len(textParts) == 0 && (!forceReasoningOnly || len(reasoningParts) == 0) {
flushAssistantTurn := func() {
if len(textParts) == 0 && len(reasoningParts) == 0 && len(pendingToolCalls) == 0 {
return
}
messages = append(messages, Message{
@@ -906,23 +906,10 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
Content: formatMessageText(strings.Join(textParts, "")),
ReasoningContent: strings.Join(reasoningParts, ""),
ReasoningSignature: reasoningSignature,
})
textParts = textParts[:0]
reasoningParts = reasoningParts[:0]
reasoningSignature = ""
}
flushToolCalls := func() {
if len(pendingToolCalls) == 0 {
return
}
messages = append(messages, Message{
Role: "assistant",
Content: "",
ReasoningContent: strings.Join(reasoningParts, ""),
ReasoningSignature: reasoningSignature,
ToolCalls: append([]ToolCallDescriptor(nil), pendingToolCalls...),
})
messages = append(messages, pendingToolResults...)
textParts = textParts[:0]
reasoningParts = reasoningParts[:0]
reasoningSignature = ""
pendingToolCalls = pendingToolCalls[:0]
@@ -932,8 +919,6 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
for _, item := range payload.Content {
switch strings.TrimSpace(item.Type) {
case "reasoning":
flushToolCalls()
flushAssistantText(false)
if item.Text != "" {
reasoningParts = append(reasoningParts, item.Text)
}
@@ -941,12 +926,10 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
reasoningSignature = strings.TrimSpace(item.Signature)
}
case "text":
flushToolCalls()
if item.Text != "" {
textParts = append(textParts, item.Text)
}
case "tool-call":
flushAssistantText(false)
pendingToolCalls = append(pendingToolCalls, ToolCallDescriptor{
ID: strings.TrimSpace(item.ToolCallID),
Index: len(pendingToolCalls),
@@ -966,9 +949,7 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
}
}
}
flushAssistantText(false)
flushToolCalls()
flushAssistantText(true)
flushAssistantTurn()
return messages
}
@@ -0,0 +1,53 @@
package promptengine
import (
"reflect"
"testing"
)
func TestBuildReplayMessagesFromPendingAssistantOutputsKeepsTextAndToolCallInOneAssistantTurn(t *testing.T) {
raw := `{
"id":"1",
"role":"assistant",
"content":[
{"type":"reasoning","text":"I need to update service.go.","signature":"reasoning-signature"},
{"type":"text","text":"Now let me pass stream.Mode in service.go."},
{"type":"tool-call","toolCallId":"call_1","toolName":"PatchEdit","args":{"path":"/workspace/service.go","old_string":"old","new_string":"new"},"result":{"success":{"path":"/workspace/service.go"}}}
]
}`
first := BuildReplayMessagesFromPendingAssistantOutputs([]string{raw})
if len(first) != 2 {
t.Fatalf("message count = %d, want 2 (assistant tool-call turn plus tool result): %#v", len(first), first)
}
assistant := first[0]
if assistant.Role != "assistant" {
t.Fatalf("assistant role = %q, want assistant", assistant.Role)
}
if assistant.Content != "Now let me pass stream.Mode in service.go." {
t.Fatalf("assistant content = %q", assistant.Content)
}
if assistant.ReasoningContent != "I need to update service.go." {
t.Fatalf("assistant reasoning = %q", assistant.ReasoningContent)
}
if assistant.ReasoningSignature != "reasoning-signature" {
t.Fatalf("assistant reasoning signature = %q", assistant.ReasoningSignature)
}
if len(assistant.ToolCalls) != 1 {
t.Fatalf("assistant tool calls = %d, want 1", len(assistant.ToolCalls))
}
if assistant.ToolCalls[0].ID != "call_1" || assistant.ToolCalls[0].Function.Name != "PatchEdit" {
t.Fatalf("assistant tool call = %#v", assistant.ToolCalls[0])
}
toolResult := first[1]
if toolResult.Role != "tool" || toolResult.ToolCallID != "call_1" || toolResult.Name != "PatchEdit" {
t.Fatalf("tool result = %#v", toolResult)
}
second := BuildReplayMessagesFromPendingAssistantOutputs([]string{raw})
if !reflect.DeepEqual(second, first) {
t.Fatalf("rebuilding the same pending output changed messages:\nfirst: %#v\nsecond: %#v", first, second)
}
}