mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
refactor: move webui.listen_addr from core.daemon to webui category
This commit is contained in:
@ -42,7 +42,7 @@ import (
|
||||
|
||||
func main() {
|
||||
dataDir := flag.String("data", "", "data directory (default: auto-detect next to binary)")
|
||||
httpAddr := flag.String("webui", ":8080", "webui listen address")
|
||||
httpAddr := flag.String("webui", "", "webui listen address (default: webui.listen_addr from config)")
|
||||
cliSocket := flag.String("socket", "", "cli unix socket path (default: <data>/cli.sock)")
|
||||
flag.Parse()
|
||||
|
||||
@ -395,7 +395,11 @@ func main() {
|
||||
// 为内置插件注入内核依赖(各插件通过 init() 自注册工厂)
|
||||
cli.DefaultSocket = *cliSocket
|
||||
openclaw.SkillsDir = filepath.Join(cfg.Daemon.DataDir, "skills")
|
||||
webui.Configure(*httpAddr,
|
||||
webuiListenAddr := *httpAddr
|
||||
if webuiListenAddr == "" {
|
||||
webuiListenAddr = cfgReg.GetString("webui.listen_addr", ":8080")
|
||||
}
|
||||
webui.Configure(webuiListenAddr,
|
||||
sup, memDB, skMgr, luaVM, cfg, iom, textMem, ks, trk, cfgReg, pluginReg, evBus, agent,
|
||||
providerMgr, baseAPIKey,
|
||||
)
|
||||
|
||||
@ -299,6 +299,37 @@ s.LLM().ListSources() []SourceInfo
|
||||
s.LLM().SetSource(name string) error
|
||||
```
|
||||
|
||||
#### Event Subscription (built-in plugins)
|
||||
|
||||
```go
|
||||
// Subscribe to system events, returns unsubscribe function
|
||||
unsub := s.Subscribe("tool_call", func(evt *events.Event) {
|
||||
log.Printf("Tool was called: %v", evt.Payload)
|
||||
})
|
||||
defer unsub()
|
||||
|
||||
// Publish event
|
||||
s.Publish(&events.Event{
|
||||
Type: "custom_event",
|
||||
Payload: map[string]interface{}{"key": "value"},
|
||||
})
|
||||
```
|
||||
|
||||
#### IO Channel Management (built-in plugins)
|
||||
|
||||
```go
|
||||
// Register a channel (bind device driver)
|
||||
s.RegisterChannel("mydevice", deviceImpl)
|
||||
|
||||
// Unregister a channel
|
||||
s.UnregisterChannel("mydevice")
|
||||
|
||||
// List all channels
|
||||
channels := s.ListChannels()
|
||||
```
|
||||
|
||||
> **Note**: `Subscribe`, `Publish`, `RegisterChannel`, `UnregisterChannel`, `ListChannels`, `InjectInput`, `InjectInputSync`, `InjectInterrupt`, `InjectTextSync`, `InjectTextSyncNoMemory`, `OutputChan` are only available in built-in plugins (`internal/sdk` package). External dynamic plugins should use the public APIs: `InjectText`, `InjectInterruptText`, `InjectTextNoMemory`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Lua Plugin Development in Detail
|
||||
@ -367,6 +398,8 @@ When running inside the kernel, `sdk.*` global variables are injected by the Go
|
||||
| `sdk.http.get(url)` | HTTP GET request (`-- !impl`) |
|
||||
| `sdk.http.post(url, body, content_type)` | HTTP POST request (`-- !impl`) |
|
||||
|
||||
> **Note**: Lua plugin's `sdk.register_stage` callback currently only receives `raw_message`, `user_id`, `phase` fields. The functionality is limited. For complex stage handling logic, use Go plugins.
|
||||
|
||||
---
|
||||
|
||||
## 4. Built-in Plugins
|
||||
|
||||
@ -300,6 +300,37 @@ s.LLM().ListSources() []SourceInfo
|
||||
s.LLM().SetSource(name string) error
|
||||
```
|
||||
|
||||
#### 事件订阅(内置插件)
|
||||
|
||||
```go
|
||||
// 订阅系统事件,返回取消订阅函数
|
||||
unsub := s.Subscribe("tool_call", func(evt *events.Event) {
|
||||
log.Printf("工具被调用: %v", evt.Payload)
|
||||
})
|
||||
defer unsub()
|
||||
|
||||
// 发布事件
|
||||
s.Publish(&events.Event{
|
||||
Type: "custom_event",
|
||||
Payload: map[string]interface{}{"key": "value"},
|
||||
})
|
||||
```
|
||||
|
||||
#### IO 通道管理(内置插件)
|
||||
|
||||
```go
|
||||
// 注册通道(绑定设备驱动)
|
||||
s.RegisterChannel("mydevice", deviceImpl)
|
||||
|
||||
// 注销通道
|
||||
s.UnregisterChannel("mydevice")
|
||||
|
||||
// 列出所有通道
|
||||
channels := s.ListChannels()
|
||||
```
|
||||
|
||||
> **注意**:`Subscribe`、`Publish`、`RegisterChannel`、`UnregisterChannel`、`ListChannels`、`InjectInput`、`InjectInputSync`、`InjectInterrupt`、`InjectTextSync`、`InjectTextSyncNoMemory`、`OutputChan` 这些方法仅在内置插件中可用(`internal/sdk` 包),外部动态插件无法访问。外部插件请使用 `InjectText`、`InjectInterruptText`、`InjectTextNoMemory` 等公共 API。
|
||||
|
||||
---
|
||||
|
||||
## 三、Lua 插件开发详解
|
||||
@ -368,6 +399,8 @@ lua main.lua
|
||||
| `sdk.http.get(url)` | HTTP GET 请求(`-- !impl`) |
|
||||
| `sdk.http.post(url, body, content_type)` | HTTP POST 请求(`-- !impl`) |
|
||||
|
||||
> **注意**:Lua 插件的 `sdk.register_stage` 阶段回调目前仅传递 `raw_message`、`user_id`、`phase` 三个字段,功能受限。复杂的阶段处理逻辑建议使用 Go 插件。
|
||||
|
||||
---
|
||||
|
||||
## 四、内置插件
|
||||
|
||||
2
go.mod
2
go.mod
@ -11,6 +11,8 @@ require (
|
||||
|
||||
require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0-20260708004841-e9bdcf9304b0 // direct
|
||||
|
||||
replace gitcode.com/JianFeeeee/homeagent-sdk => ../homeagentsdk
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
|
||||
@ -1522,6 +1522,9 @@ func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
||||
prompt += "\n\n" + a.indexer.BuildToolPrompt()
|
||||
}
|
||||
|
||||
// 动态工具目录
|
||||
prompt += a.buildToolCatalog()
|
||||
|
||||
return prompt
|
||||
}
|
||||
|
||||
@ -1549,6 +1552,41 @@ func cleanParams(params map[string]interface{}) map[string]interface{} {
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func (a *Agent) buildToolCatalog() string {
|
||||
defs := a.buildToolDefs()
|
||||
if len(defs) == 0 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString("\n\n【可用工具列表】")
|
||||
seen := make(map[string]bool)
|
||||
for _, d := range defs {
|
||||
t, ok := d.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fn, ok := t["function"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name, _ := fn["name"].(string)
|
||||
if name == "" || seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
desc, _ := fn["description"].(string)
|
||||
sb.WriteString(fmt.Sprintf("\n- %s", name))
|
||||
if desc != "" {
|
||||
// only first 80 chars of description
|
||||
if len(desc) > 80 {
|
||||
desc = desc[:80] + "..."
|
||||
}
|
||||
sb.WriteString(": " + desc)
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (a *Agent) buildToolDefs() []interface{} {
|
||||
var tools []interface{}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||
@ -85,6 +86,7 @@ func inferToolPlugin(name string) string {
|
||||
// - 只读操作先调用 ctx.RLock() / defer ctx.RUnlock()
|
||||
// - 写操作(如设置 ctx.Response)先调用 ctx.Lock() / defer ctx.Unlock()
|
||||
// 如果任意 handler 设置了 Response,后续 handler 可通过 ctx.IsResponded() 判断后提前返回。
|
||||
// handler 返回的 error 会被收集到 ctx.Errors 中并记录日志,不会中断其他 handler 的执行。
|
||||
func (h *StageHost) RunStage(stage sdk.Stage, ctx *sdk.StageContext) {
|
||||
h.mu.RLock()
|
||||
handlers := h.stages[stage]
|
||||
@ -93,14 +95,29 @@ func (h *StageHost) RunStage(stage sdk.Stage, ctx *sdk.StageContext) {
|
||||
return
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
errCh := make(chan error, len(handlers))
|
||||
for _, handler := range handlers {
|
||||
wg.Add(1)
|
||||
go func(fn sdk.StageHandler) {
|
||||
defer wg.Done()
|
||||
fn(ctx)
|
||||
if err := fn(ctx); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}(handler)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
|
||||
var errs []string
|
||||
for err := range errCh {
|
||||
errs = append(errs, err.Error())
|
||||
log.Printf("[stage] %s handler error: %v", stage, err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
ctx.Lock()
|
||||
ctx.Errors = append(ctx.Errors, errs...)
|
||||
ctx.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *StageHost) ToolCount() int {
|
||||
|
||||
@ -68,32 +68,32 @@ func channelInfoFromIO(ch agentIO.ChannelInfo) ChannelInfo {
|
||||
}
|
||||
|
||||
type MemoryStatus struct {
|
||||
Available bool `json:"available"`
|
||||
EntityCount int `json:"entity_count,omitempty"`
|
||||
RelationCount int `json:"relation_count,omitempty"`
|
||||
EntityTypes int `json:"entity_types,omitempty"`
|
||||
Available bool `json:"available"`
|
||||
EntityCount int `json:"entity_count"`
|
||||
RelationCount int `json:"relation_count"`
|
||||
EntityTypes int `json:"entity_types"`
|
||||
}
|
||||
|
||||
type KnowledgeStatus struct {
|
||||
Available bool `json:"available"`
|
||||
ItemCount int `json:"item_count,omitempty"`
|
||||
ItemCount int `json:"item_count"`
|
||||
Items []string `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type DocumentStatus struct {
|
||||
Available bool `json:"available"`
|
||||
DocCount int `json:"doc_count,omitempty"`
|
||||
VectorCount int `json:"vector_count,omitempty"`
|
||||
DocCount int `json:"doc_count"`
|
||||
VectorCount int `json:"vector_count"`
|
||||
}
|
||||
|
||||
type TextMemoryStatus struct {
|
||||
Available bool `json:"available"`
|
||||
FileCount int `json:"file_count,omitempty"`
|
||||
FileCount int `json:"file_count"`
|
||||
}
|
||||
|
||||
type SocialStatus struct {
|
||||
Available bool `json:"available"`
|
||||
PersonCount int `json:"person_count,omitempty"`
|
||||
PersonCount int `json:"person_count"`
|
||||
}
|
||||
|
||||
type SkillsStatus struct {
|
||||
|
||||
@ -298,7 +298,7 @@ func (r *ConfigRegistry) seedDBValues(dataDir string) {
|
||||
|
||||
set := func(k, v string) { stmt.Exec(k, v) }
|
||||
|
||||
set("core.daemon.listen_addr", ":8080")
|
||||
set("webui.listen_addr", ":8080")
|
||||
set("core.daemon.data_dir", dataDir)
|
||||
set("core.daemon.heartbeat_interval", "15s")
|
||||
set("core.daemon.check_interval", "30s")
|
||||
@ -349,26 +349,11 @@ func (r *ConfigRegistry) seedDBValues(dataDir string) {
|
||||
set("core.agent.distill_interval", "30m")
|
||||
set("core.agent.workdir", "")
|
||||
set("core.agent.system_prompt", `你是 HomeAgent,一个持续运行的个人管家。
|
||||
你的每次回复会自动发送到当前输出通道(默认=输入源),无需额外工具。
|
||||
如需切换回复通道,使用 output_set_channel。
|
||||
如需异步发送消息或通知,使用 output_send 指定通道和内容。
|
||||
使用 output_list_channels 查看可用通道及其能力。
|
||||
你的回复默认发送到用户的输入来源,无需额外工具。
|
||||
如需异步发送消息到其他通道,使用 output_send。
|
||||
使用 output_list_channels 查看可用通道。
|
||||
|
||||
可用工具列表会由系统自动传入,按需使用即可。以下是你尤其需要关注的几类工具:
|
||||
- memory_* — 图记忆(长期记忆,记录和查询个人信息/事实)
|
||||
- knowledge_* — 知识库(查阅预设知识文档)
|
||||
- doc_* — 文档记忆(近期对话的存档,查询后自动清除)
|
||||
- person_* — 人物特质与社交关系网
|
||||
- llm_* — LLM 源管理(列出/切换模型提供商)
|
||||
- output_* — 输出通道管理(切换/发送消息)
|
||||
- timer_set — 设置定时提醒
|
||||
- plgreload — 热重载插件
|
||||
- spawn_child — 生成子 Agent 执行独立任务
|
||||
- describe_image — 描述用户上传的图片
|
||||
- transcribe_audio — 转写用户上传的音频
|
||||
- ocr_image — 识别图片中的文字
|
||||
|
||||
当用户上传图片或音频时,系统会自动附着媒体内容。如果模型不支持直接处理多媒体,请使用上述工具。
|
||||
当用户上传图片或音频时,系统会自动附着媒体内容。如果模型不支持直接处理多媒体,请调用对应的媒体处理工具。
|
||||
|
||||
回复你的真实想法,用自然语言与用户交流。`)
|
||||
|
||||
@ -387,7 +372,7 @@ func (r *ConfigRegistry) seedDBValues(dataDir string) {
|
||||
func (r *ConfigRegistry) seedCoreDefs(dataDir string) {
|
||||
reg := func(d ConfigDef) { r.defs[d.Key] = &d }
|
||||
|
||||
reg(ConfigDef{Key: "core.daemon.listen_addr", Default: ":8080", Type: "string", DisplayName: "监听地址", Description: "WebUI HTTP 监听地址", Category: "daemon"})
|
||||
reg(ConfigDef{Key: "webui.listen_addr", Default: ":8080", Type: "string", DisplayName: "监听地址", Description: "WebUI HTTP 监听地址", Category: "webui"})
|
||||
reg(ConfigDef{Key: "core.daemon.data_dir", Default: dataDir, Type: "string", DisplayName: "数据目录", Description: "数据存储根目录", Category: "daemon"})
|
||||
reg(ConfigDef{Key: "core.daemon.heartbeat_interval", Default: "15s", Type: "duration", DisplayName: "心跳间隔", Description: "Agent 心跳检查间隔", Category: "daemon"})
|
||||
reg(ConfigDef{Key: "core.daemon.check_interval", Default: "30s", Type: "duration", DisplayName: "检查间隔", Description: "网络状态检查间隔", Category: "daemon"})
|
||||
@ -568,7 +553,6 @@ func (r *ConfigRegistry) ToConfig() *types.Config {
|
||||
return b
|
||||
}
|
||||
|
||||
cfg.Daemon.ListenAddr = read("core.daemon.listen_addr", cfg.Daemon.ListenAddr)
|
||||
cfg.Daemon.DataDir = read("core.daemon.data_dir", cfg.Daemon.DataDir)
|
||||
cfg.Daemon.HeartbeatInterval = readDur("core.daemon.heartbeat_interval", cfg.Daemon.HeartbeatInterval)
|
||||
cfg.Daemon.CheckInterval = readDur("core.daemon.check_interval", cfg.Daemon.CheckInterval)
|
||||
|
||||
@ -168,8 +168,8 @@ func TestSeedDefaultsToConfig(t *testing.T) {
|
||||
// Reconstruct config from DB
|
||||
cfg2 := r.ToConfig()
|
||||
|
||||
if cfg2.Daemon.ListenAddr != ":8080" {
|
||||
t.Fatalf("expected :8080, got %s", cfg2.Daemon.ListenAddr)
|
||||
if v := r.GetString("webui.listen_addr", ""); v != ":8080" {
|
||||
t.Fatalf("expected :8080, got %s", v)
|
||||
}
|
||||
if cfg2.LLM.Provider != "deepseek" {
|
||||
t.Fatalf("expected deepseek, got %s", cfg2.LLM.Provider)
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@ -324,42 +323,24 @@ func (v *VM) GetAdapterHeaders(name string) map[string]string {
|
||||
}
|
||||
|
||||
func (v *VM) writeBundledAdapters() error {
|
||||
// Try multiple paths for compatibility
|
||||
tryPaths := []string{"adapters", ".", "lua/adapters"}
|
||||
var entries []fs.DirEntry
|
||||
var err error
|
||||
for _, p := range tryPaths {
|
||||
entries, err = bundledAdapters.ReadDir(p)
|
||||
if err == nil && len(entries) > 0 {
|
||||
break
|
||||
}
|
||||
known := []string{
|
||||
"openai", "anthropic", "deepseek", "gemini",
|
||||
"github", "groq", "mistral", "ollama",
|
||||
}
|
||||
if err != nil || len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if filepath.Ext(entry.Name()) != ".lua" {
|
||||
continue
|
||||
}
|
||||
dstPath := filepath.Join(v.adapterDir, entry.Name())
|
||||
for _, name := range known {
|
||||
srcPath := "adapters/" + name + ".lua"
|
||||
dstPath := filepath.Join(v.adapterDir, name+".lua")
|
||||
if _, err := os.Stat(dstPath); err == nil {
|
||||
continue
|
||||
}
|
||||
data, err := bundledAdapters.ReadFile(filepath.Join("adapters", entry.Name()))
|
||||
data, err := bundledAdapters.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
// try alternative paths
|
||||
data, err = bundledAdapters.ReadFile(entry.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := os.WriteFile(dstPath, data, 0644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", entry.Name(), err)
|
||||
return fmt.Errorf("write %s: %w", name+".lua", err)
|
||||
}
|
||||
fmt.Printf("[lua] installed bundled adapter: %s\n", entry.Name())
|
||||
fmt.Printf("[lua] installed bundled adapter: %s\n", name+".lua")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -13,6 +14,9 @@ import (
|
||||
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||
)
|
||||
|
||||
// isWindows 缓存运行时检测结果
|
||||
var isWindows = runtime.GOOS == "windows"
|
||||
|
||||
// shellUnquote 拆解命令字符串,处理单引号/双引号包裹的参数
|
||||
func shellUnquote(s string) []string {
|
||||
var args []string
|
||||
@ -152,7 +156,12 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
parts := shellUnquote(command)
|
||||
// Windows 上预置 chcp 65001 确保控制台输出为 UTF-8,避免 GBK 乱码
|
||||
execCmd := command
|
||||
if isWindows {
|
||||
execCmd = "chcp 65001>nul & " + command
|
||||
}
|
||||
parts := shellUnquote(execCmd)
|
||||
if len(parts) == 0 {
|
||||
return map[string]interface{}{"error": "command is required"}, nil
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -950,7 +950,7 @@ func (h *Handler) handleChatEvents(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
subTypes := []string{"agent_output", "reasoning", "agent_error"}
|
||||
subTypes := []string{"agent_output", "reasoning", "agent_error", "tool_call"}
|
||||
for _, t := range subTypes {
|
||||
t2 := t
|
||||
_ = h.eventBus.Subscribe(events.EventType(t2), func(evt *events.Event) {
|
||||
|
||||
@ -34,7 +34,7 @@ func newTestHandler(t *testing.T) (*Handler, *supervisor.Daemon) {
|
||||
sup := supervisor.New(cfg)
|
||||
sup.Start()
|
||||
|
||||
return NewHandler(sup, nil, nil, nil, cfg, nil, nil, nil, nil, nil, nil, events.NewBus(), nil), sup
|
||||
return NewHandler(sup, nil, nil, nil, cfg, nil, nil, nil, nil, nil, nil, events.NewBus(), nil, nil, ""), sup
|
||||
}
|
||||
|
||||
func TestAuthMiddleware(t *testing.T) {
|
||||
@ -194,7 +194,7 @@ func TestHandleKnowledgeSearch(t *testing.T) {
|
||||
sup.Start()
|
||||
defer sup.Shutdown()
|
||||
|
||||
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, ks, nil, nil, nil, events.NewBus(), nil)
|
||||
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, ks, nil, nil, nil, events.NewBus(), nil, nil, "")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/knowledge?q=test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@ -226,7 +226,7 @@ func TestHandleKnowledgeCreate(t *testing.T) {
|
||||
sup.Start()
|
||||
defer sup.Shutdown()
|
||||
|
||||
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, ks, nil, nil, nil, events.NewBus(), nil)
|
||||
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, ks, nil, nil, nil, events.NewBus(), nil, nil, "")
|
||||
|
||||
body := `{"name":"new_doc","content":"fresh content"}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/knowledge", strings.NewReader(body))
|
||||
@ -291,7 +291,7 @@ func TestHandleTrackerStats(t *testing.T) {
|
||||
sup.Start()
|
||||
defer sup.Shutdown()
|
||||
|
||||
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, nil, tr, nil, nil, events.NewBus(), nil)
|
||||
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, nil, tr, nil, nil, events.NewBus(), nil, nil, "")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/tracker", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@ -458,7 +458,7 @@ func TestSettingsAPIFlow(t *testing.T) {
|
||||
cfgReg := internalConfig.NewConfigRegistry("")
|
||||
cfgReg.Register("core.llm.model", "deepseek-v4-flash")
|
||||
cfgReg.Register("core.llm.base_url", "https://api.deepseek.com")
|
||||
cfgReg.Register("core.daemon.listen_addr", ":8080")
|
||||
cfgReg.Register("webui.listen_addr", ":8080")
|
||||
cfgReg.Register("plugin.qq.access_token", "secret123")
|
||||
|
||||
sup := supervisor.New(&types.Config{
|
||||
@ -471,7 +471,7 @@ func TestSettingsAPIFlow(t *testing.T) {
|
||||
defer sup.Shutdown()
|
||||
|
||||
pluginReg := plugin.NewRegistry()
|
||||
h := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, cfgReg, pluginReg, events.NewBus(), nil)
|
||||
h := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, cfgReg, pluginReg, events.NewBus(), nil, nil, "")
|
||||
|
||||
t.Run("GET_settings_lists_keys_and_plugins", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/settings", nil)
|
||||
@ -505,7 +505,7 @@ func TestSettingsAPIFlow(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("GET_settings_with_prefix", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/settings?prefix=core.daemon", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/settings?prefix=webui", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.handleSettings(w, req)
|
||||
|
||||
@ -513,11 +513,11 @@ func TestSettingsAPIFlow(t *testing.T) {
|
||||
json.NewDecoder(w.Body).Decode(&resp)
|
||||
settings := resp["settings"].(map[string]interface{})
|
||||
|
||||
if _, ok := settings["core.daemon.listen_addr"]; !ok {
|
||||
t.Fatal("expected core.daemon.listen_addr in filtered results")
|
||||
if _, ok := settings["webui.listen_addr"]; !ok {
|
||||
t.Fatal("expected webui.listen_addr in filtered results")
|
||||
}
|
||||
if _, ok := settings["core.llm.model"]; ok {
|
||||
t.Fatal("core.llm.model should not be in core.daemon filtered results")
|
||||
t.Fatal("core.llm.model should not be in webui filtered results")
|
||||
}
|
||||
})
|
||||
|
||||
@ -578,7 +578,7 @@ func TestSettingsAPIFlow(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("settings_not_available_without_registry", func(t *testing.T) {
|
||||
h2 := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, nil, nil, events.NewBus(), nil)
|
||||
h2 := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, nil, nil, events.NewBus(), nil, nil, "")
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/settings", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h2.handleSettings(w, req)
|
||||
@ -604,7 +604,7 @@ func TestSettingsWithPluginRegistry(t *testing.T) {
|
||||
defer sup.Shutdown()
|
||||
|
||||
pluginReg := plugin.NewRegistry()
|
||||
h := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, cfgReg, pluginReg, events.NewBus(), nil)
|
||||
h := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, cfgReg, pluginReg, events.NewBus(), nil, nil, "")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/settings", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@ -677,7 +677,7 @@ func TestHandleCompletionsEndToEnd(t *testing.T) {
|
||||
sup.Start()
|
||||
defer sup.Shutdown()
|
||||
|
||||
h := NewHandler(sup, nil, nil, nil, &types.Config{}, iom, nil, nil, nil, nil, nil, events.NewBus(), nil)
|
||||
h := NewHandler(sup, nil, nil, nil, &types.Config{}, iom, nil, nil, nil, nil, nil, events.NewBus(), nil, nil, "")
|
||||
|
||||
t.Run("POST_chat_completions_returns_echo", func(t *testing.T) {
|
||||
body := `{"model":"test","messages":[{"role":"user","content":"你好"}]}`
|
||||
@ -710,7 +710,7 @@ func TestHandleCompletionsEndToEnd(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("POST_chat_completions_no_iom_returns_503", func(t *testing.T) {
|
||||
h2 := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, nil, nil, events.NewBus(), nil)
|
||||
h2 := NewHandler(sup, nil, nil, nil, &types.Config{}, nil, nil, nil, nil, nil, nil, events.NewBus(), nil, nil, "")
|
||||
body := `{"messages":[{"role":"user","content":"hi"}]}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
@ -150,7 +150,6 @@ type Config struct {
|
||||
}
|
||||
|
||||
type DaemonConfig struct {
|
||||
ListenAddr string `json:"listen_addr"`
|
||||
DataDir string `json:"data_dir"`
|
||||
HeartbeatInterval time.Duration `json:"heartbeat_interval"`
|
||||
CheckInterval time.Duration `json:"check_interval"`
|
||||
|
||||
Reference in New Issue
Block a user