Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ go run ./cmd/neocode

- **`internal/config`** — 配置管理、环境变量、YAML 加载
- **`internal/context`** — system prompt、消息裁剪与上下文构建
- **`internal/provider`** — 内建 LLM Provider 抽象、模型元数据归一化、动态发现与 catalog 缓存
- **`internal/provider`** — Provider 契约、驱动注册与通用领域类型
- **`internal/provider/catalog`** — 模型发现、catalog 缓存与后台刷新
- **`internal/provider/selection`** — provider/model 选择与配置同步
- **`internal/provider/builtin`** — 内建 driver 注册
- **`internal/runtime`** — ReAct 主循环、事件流、会话管理
- **`internal/tools`** — 工具注册表与具体工具实现
- **`internal/tui`** — 终端 UI、交互体验、事件桥接
Expand All @@ -108,7 +111,10 @@ go run ./cmd/neocode
│ ├── app # 应用装配
│ ├── config # 配置管理
│ ├── context # 上下文构建
│ ├── provider # Provider 抽象、模型发现与元数据缓存
│ ├── provider # Provider 契约与驱动注册
│ │ ├── builtin # 内建 driver 注册
│ │ ├── catalog # 模型发现与缓存
│ │ └── selection # provider/model 选择服务
│ ├── runtime # ReAct 循环与事件流
│ ├── tools # 工具系统
│ └── tui # 终端 UI
Expand Down
17 changes: 10 additions & 7 deletions internal/app/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (

"neo-code/internal/config"
agentcontext "neo-code/internal/context"
"neo-code/internal/provider"
"neo-code/internal/provider/builtin"
providercatalog "neo-code/internal/provider/catalog"
providerselection "neo-code/internal/provider/selection"
agentruntime "neo-code/internal/runtime"
"neo-code/internal/security"
"neo-code/internal/tools"
Expand All @@ -35,9 +36,10 @@ func ensureConsoleUTF8() {
}

func NewProgram(ctx context.Context) (*tea.Program, error) {
ensureConsoleUTF8()

loader := config.NewLoader("", builtin.DefaultConfig())
ensureConsoleUTF8()

loader := config.NewLoader("", config.DefaultConfig())
manager := config.NewManager(loader)
if _, err := manager.Load(ctx); err != nil {
return nil, err
Expand All @@ -47,8 +49,9 @@ func NewProgram(ctx context.Context) (*tea.Program, error) {
if err != nil {
return nil, err
}
providerService := provider.NewService(manager, providerRegistry, nil)
if _, err := providerService.EnsureSelection(ctx); err != nil {
modelCatalogs := providercatalog.NewService(manager.BaseDir(), providerRegistry, nil)
providerSelection := providerselection.NewService(manager, providerRegistry, modelCatalogs)
if _, err := providerSelection.EnsureSelection(ctx); err != nil {
return nil, err
}

Expand All @@ -65,11 +68,11 @@ func NewProgram(ctx context.Context) (*tea.Program, error) {
manager,
toolManager,
sessionStore,
providerService,
providerRegistry,
agentcontext.NewBuilder(),
)

tuiApp, err := tui.New(&cfg, manager, runtimeSvc, providerService)
tuiApp, err := tui.New(&cfg, manager, runtimeSvc, providerSelection)
if err != nil {
return nil, err
}
Expand Down
14 changes: 14 additions & 0 deletions internal/config/default_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package config

// DefaultConfig returns the application defaults with builtin provider metadata attached.
func DefaultConfig() *Config {
cfg := Default()
providers := DefaultProviders()
cfg.Providers = providers
if len(providers) == 0 {
return cfg
}
cfg.SelectedProvider = providers[0].Name
cfg.CurrentModel = providers[0].Model
return cfg
}
50 changes: 50 additions & 0 deletions internal/config/default_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package config

import "testing"

func TestDefaultConfigIncludesBuiltinProviders(t *testing.T) {
t.Parallel()

cfg := DefaultConfig()
if len(cfg.Providers) != 4 {
t.Fatalf("expected 4 builtin providers, got %d", len(cfg.Providers))
}
if cfg.Providers[0].Name != OpenAIName {
t.Fatalf("expected first provider %q, got %q", OpenAIName, cfg.Providers[0].Name)
}
if cfg.Providers[1].Name != GeminiName {
t.Fatalf("expected second provider %q, got %q", GeminiName, cfg.Providers[1].Name)
}
if cfg.Providers[2].Name != OpenLLName {
t.Fatalf("expected third provider %q, got %q", OpenLLName, cfg.Providers[2].Name)
}
if cfg.Providers[3].Name != QiniuName {
t.Fatalf("expected fourth provider %q, got %q", QiniuName, cfg.Providers[3].Name)
}
if cfg.SelectedProvider != OpenAIName {
t.Fatalf("expected selected provider %q, got %q", OpenAIName, cfg.SelectedProvider)
}
if cfg.CurrentModel != OpenAIDefaultModel {
t.Fatalf("expected current model %q, got %q", OpenAIDefaultModel, cfg.CurrentModel)
}
}

func TestDefaultConfigValidates(t *testing.T) {
t.Parallel()

cfg := DefaultConfig()
cfg.ApplyDefaultsFrom(*cfg)
if err := cfg.Validate(); err != nil {
t.Fatalf("expected default config to validate, got %v", err)
}
}

func TestDefaultConfigWorkdirIsAbsolute(t *testing.T) {
t.Parallel()

cfg := DefaultConfig()
cfg.ApplyDefaultsFrom(*cfg)
if cfg.Workdir == "" {
t.Fatal("expected workdir to be set")
}
}
4 changes: 2 additions & 2 deletions internal/context/test_helpers_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package context

import "neo-code/internal/provider/builtin"
import "neo-code/internal/config"

func testMetadata(workdir string) Metadata {
cfg := builtin.DefaultConfig()
cfg := config.DefaultConfig()
return Metadata{
Workdir: workdir,
Shell: cfg.Shell,
Expand Down
13 changes: 0 additions & 13 deletions internal/provider/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,10 @@ package builtin
import (
"errors"

"neo-code/internal/config"
"neo-code/internal/provider"
"neo-code/internal/provider/openai"
)

// DefaultConfig 返回默认配置,包含所有内置 provider。
func DefaultConfig() *config.Config {
cfg := config.Default()
providers := config.DefaultProviders()

cfg.Providers = providers
cfg.SelectedProvider = providers[0].Name
cfg.CurrentModel = providers[0].Model

return cfg
}

func NewRegistry() (*provider.Registry, error) {
registry := provider.NewRegistry()
if err := Register(registry); err != nil {
Expand Down
50 changes: 3 additions & 47 deletions internal/provider/builtin/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,9 @@ package builtin
import (
"testing"

"neo-code/internal/config"
"neo-code/internal/provider/openai"
)

func TestDefaultConfigIncludesBuiltinProviders(t *testing.T) {
t.Parallel()

cfg := DefaultConfig()
if len(cfg.Providers) != 4 {
t.Fatalf("expected 4 builtin providers, got %d", len(cfg.Providers))
}
if cfg.Providers[0].Name != config.OpenAIName {
t.Fatalf("expected first provider %q, got %q", config.OpenAIName, cfg.Providers[0].Name)
}
if cfg.Providers[1].Name != config.GeminiName {
t.Fatalf("expected second provider %q, got %q", config.GeminiName, cfg.Providers[1].Name)
}
if cfg.Providers[2].Name != config.OpenLLName {
t.Fatalf("expected third provider %q, got %q", config.OpenLLName, cfg.Providers[2].Name)
}
if cfg.Providers[3].Name != config.QiniuName {
t.Fatalf("expected fourth provider %q, got %q", config.QiniuName, cfg.Providers[3].Name)
}
if cfg.SelectedProvider != config.OpenAIName {
t.Fatalf("expected selected provider %q, got %q", config.OpenAIName, cfg.SelectedProvider)
}
if cfg.CurrentModel != config.OpenAIDefaultModel {
t.Fatalf("expected current model %q, got %q", config.OpenAIDefaultModel, cfg.CurrentModel)
}
}

func TestNewRegistry(t *testing.T) {
t.Parallel()

Expand All @@ -43,24 +16,7 @@ func TestNewRegistry(t *testing.T) {
if registry == nil {
t.Fatal("expected non-nil registry")
}
}

func TestDefaultConfigValidates(t *testing.T) {
t.Parallel()

cfg := DefaultConfig()
cfg.ApplyDefaultsFrom(*cfg)
if err := cfg.Validate(); err != nil {
t.Fatalf("expected default config to validate, got %v", err)
}
}

func TestDefaultConfigWorkdirIsAbsolute(t *testing.T) {
t.Parallel()

cfg := DefaultConfig()
cfg.ApplyDefaultsFrom(*cfg)
if cfg.Workdir == "" {
t.Fatal("expected workdir to be set")
if !registry.Supports(openai.DriverName) {
t.Fatalf("expected registry to include %q driver", openai.DriverName)
}
}
Loading
Loading