@@ -2,7 +2,6 @@ package app
22
33import (
44 "context"
5- "errors"
65 "log"
76 "path/filepath"
87 "strings"
@@ -36,13 +35,6 @@ import (
3635
3736const utf8CodePage = 65001
3837
39- const (
40- // RuntimeModeLocal 表示继续使用进程内 runtime 直连模式。
41- RuntimeModeLocal = "local"
42- // RuntimeModeGateway 表示通过 Gateway JSON-RPC 转发 runtime 调用。
43- RuntimeModeGateway = "gateway"
44- )
45-
4638var (
4739 setConsoleOutputCodePage = platformSetConsoleOutputCodePage
4840 setConsoleInputCodePage = platformSetConsoleInputCodePage
@@ -60,19 +52,24 @@ var (
6052
6153// BootstrapOptions 描述应用启动时可注入的运行时选项。
6254type BootstrapOptions struct {
63- Workdir string
64- RuntimeMode string
55+ Workdir string
6556}
6657
6758type memoExtractorScheduler interface {
6859 ScheduleWithExtractor (sessionID string , messages []providertypes.Message , extractor memo.Extractor )
6960}
7061
7162type runtimeWithClose interface {
72- agentruntime .Runtime
63+ services .Runtime
7364 Close () error
7465}
7566
67+ type bootstrapSharedBundle struct {
68+ Config config.Config
69+ ConfigManager * config.Manager
70+ ProviderSelection * configstate.Service
71+ }
72+
7673func newMemoExtractorAdapter (
7774 factory agentruntime.ProviderFactory ,
7875 cm * config.Manager ,
@@ -129,35 +126,13 @@ func EnsureConsoleUTF8() {
129126 _ = setConsoleInputCodePage (utf8CodePage )
130127}
131128
132- // BuildRuntime 构建 CLI 与 TUI 共用的运行时依赖 。
133- func BuildRuntime (ctx context.Context , opts BootstrapOptions ) (RuntimeBundle , error ) {
134- runtimeMode , err := resolveBootstrapRuntimeMode ( opts . RuntimeMode )
129+ // BuildGatewayServerDeps 构建 Gateway 服务端运行时依赖,包含 runtime/tool/session 全栈能力 。
130+ func BuildGatewayServerDeps (ctx context.Context , opts BootstrapOptions ) (RuntimeBundle , error ) {
131+ sharedDeps , providerRegistry , modelCatalogs , err := BuildSharedConfigDeps ( ctx , opts )
135132 if err != nil {
136133 return RuntimeBundle {}, err
137134 }
138-
139- defaultCfg , err := bootstrapDefaultConfig (opts )
140- if err != nil {
141- return RuntimeBundle {}, err
142- }
143-
144- loader := config .NewLoader ("" , defaultCfg )
145- manager := config .NewManager (loader )
146- if _ , err := manager .Load (ctx ); err != nil {
147- return RuntimeBundle {}, err
148- }
149-
150- providerRegistry , err := builtin .NewRegistry ()
151- if err != nil {
152- return RuntimeBundle {}, err
153- }
154- modelCatalogs := providercatalog .NewService (manager .BaseDir (), providerRegistry , nil )
155- providerSelection := configstate .NewService (manager , providerRegistry , modelCatalogs )
156- if _ , err := providerSelection .EnsureSelection (ctx ); err != nil {
157- return RuntimeBundle {}, err
158- }
159-
160- cfg := manager .Get ()
135+ cfg := sharedDeps .Config
161136
162137 toolRegistry , toolsCleanup , err := buildToolRegistry (cfg )
163138 if err != nil {
@@ -184,7 +159,7 @@ func BuildRuntime(ctx context.Context, opts BootstrapOptions) (RuntimeBundle, er
184159
185160 // Session Store 绑定到启动时的 workdir 哈希分桶,整个应用生命周期内不可变。
186161 // 这意味着所有会话都归属到启动时指定的项目目录下,运行时不会因配置变更而迁移存储位置。
187- sessionStore = agentsession .NewStore (loader .BaseDir (), cfg .Workdir )
162+ sessionStore = agentsession .NewStore (sharedDeps . ConfigManager .BaseDir (), cfg .Workdir )
188163
189164 // 启动时自动清理过期会话,避免数据库无限膨胀。
190165 if _ , err := cleanupExpiredSessions (ctx , sessionStore , agentsession .DefaultSessionMaxAge ); err != nil {
@@ -197,7 +172,7 @@ func BuildRuntime(ctx context.Context, opts BootstrapOptions) (RuntimeBundle, er
197172 var contextBuilder agentcontext.Builder = agentcontext .NewBuilderWithToolPoliciesAndSummarizers (toolRegistry , toolRegistry )
198173 var memoSvc * memo.Service
199174 if cfg .Memo .Enabled {
200- memoStore := memo .NewFileStore (loader .BaseDir (), cfg .Workdir )
175+ memoStore := memo .NewFileStore (sharedDeps . ConfigManager .BaseDir (), cfg .Workdir )
201176 memoSource := memo .NewContextSource (memoStore )
202177 var sourceInvl func ()
203178 if invalidator , ok := memoSource .(interface { InvalidateCache () }); ok {
@@ -212,15 +187,15 @@ func BuildRuntime(ctx context.Context, opts BootstrapOptions) (RuntimeBundle, er
212187 }
213188
214189 runtimeSvc := agentruntime .NewWithFactory (
215- manager ,
190+ sharedDeps . ConfigManager ,
216191 toolManager ,
217192 sessionStore ,
218193 providerRegistry ,
219194 contextBuilder ,
220195 )
221196 runtimeSvc .SetSessionAssetStore (sessionStore )
222197 runtimeSvc .SetUserInputPreparer (agentruntime .NewSessionInputPreparer (sessionStore , sessionStore ))
223- runtimeSvc .SetSkillsRegistry (buildSkillsRegistry (ctx , loader .BaseDir ()))
198+ runtimeSvc .SetSkillsRegistry (buildSkillsRegistry (ctx , sharedDeps . ConfigManager .BaseDir ()))
224199 runtimeSvc .SetAutoCompactThresholdResolver (runtimeAutoCompactThresholdResolverFunc (
225200 func (ctx context.Context , cfg config.Config ) (int , error ) {
226201 resolution , err := configstate .ResolveAutoCompactThreshold (ctx , cfg , modelCatalogs )
@@ -235,55 +210,109 @@ func BuildRuntime(ctx context.Context, opts BootstrapOptions) (RuntimeBundle, er
235210 if memoSvc != nil && cfg .Memo .AutoExtract {
236211 runtimeSvc .SetMemoExtractor (newMemoExtractorAdapter (
237212 providerRegistry ,
238- manager ,
213+ sharedDeps . ConfigManager ,
239214 memo .NewAutoExtractor (nil , memoSvc , time .Duration (cfg .Memo .ExtractTimeoutSec )* time .Second ),
240215 ))
241216 }
242217
243218 runtimeImpl := agentruntime .Runtime (runtimeSvc )
244219 closeFns := []func () error {toolsCleanup , sessionStore .Close }
245- if runtimeMode == RuntimeModeGateway {
246- remoteRuntime , remoteErr := newRemoteRuntimeAdapter (services.RemoteRuntimeAdapterOptions {})
247- if remoteErr != nil {
248- return RuntimeBundle {}, remoteErr
249- }
250- runtimeImpl = remoteRuntime
251- closeFns = append ([]func () error {remoteRuntime .Close }, closeFns ... )
252- }
253220
254221 needCleanup = false
255222
256223 closeBundle := combineRuntimeClosers (closeFns ... )
257224
258225 return RuntimeBundle {
259226 Config : cfg ,
260- ConfigManager : manager ,
227+ ConfigManager : sharedDeps . ConfigManager ,
261228 Runtime : runtimeImpl ,
262- ProviderSelection : providerSelection ,
229+ ProviderSelection : sharedDeps . ProviderSelection ,
263230 MemoService : memoSvc ,
264231 Close : closeBundle ,
265232 }, nil
266233}
267234
235+ // BuildRuntime 兼容旧入口,内部转发到 BuildGatewayServerDeps。
236+ func BuildRuntime (ctx context.Context , opts BootstrapOptions ) (RuntimeBundle , error ) {
237+ return BuildGatewayServerDeps (ctx , opts )
238+ }
239+
268240// NewProgram 基于共享运行时依赖构建并返回 TUI 程序,同时返回退出时应调用的资源清理函数。
269241func NewProgram (ctx context.Context , opts BootstrapOptions ) (* tea.Program , func () error , error ) {
270- bundle , err := BuildRuntime (ctx , opts )
242+ bundle , err := BuildTUIClientDeps (ctx , opts )
271243 if err != nil {
272244 return nil , nil , err
273245 }
274246
275- tuiApp , err := newTUIWithMemo ( & bundle . Config , bundle . ConfigManager , bundle . Runtime , bundle . ProviderSelection , bundle . MemoService )
247+ tuiRuntime , err := newRemoteRuntimeAdapter (services. RemoteRuntimeAdapterOptions {} )
276248 if err != nil {
277249 if bundle .Close != nil {
278250 _ = bundle .Close ()
279251 }
280252 return nil , nil , err
281253 }
254+ cleanup := combineRuntimeClosers (tuiRuntime .Close , bundle .Close )
255+
256+ tuiApp , err := newTUIWithMemo (& bundle .Config , bundle .ConfigManager , tuiRuntime , bundle .ProviderSelection , bundle .MemoService )
257+ if err != nil {
258+ if cleanup != nil {
259+ _ = cleanup ()
260+ }
261+ return nil , nil , err
262+ }
282263 return tea .NewProgram (
283264 tuiApp ,
284265 tea .WithAltScreen (),
285266 tea .WithMouseCellMotion (),
286- ), bundle .Close , nil
267+ ), cleanup , nil
268+ }
269+
270+ // BuildSharedConfigDeps 统一构建共享配置依赖:配置、Provider 注册与当前选择服务。
271+ func BuildSharedConfigDeps (
272+ ctx context.Context ,
273+ opts BootstrapOptions ,
274+ ) (bootstrapSharedBundle , agentruntime.ProviderFactory , * providercatalog.Service , error ) {
275+ defaultCfg , err := bootstrapDefaultConfig (opts )
276+ if err != nil {
277+ return bootstrapSharedBundle {}, nil , nil , err
278+ }
279+
280+ loader := config .NewLoader ("" , defaultCfg )
281+ manager := config .NewManager (loader )
282+ if _ , err := manager .Load (ctx ); err != nil {
283+ return bootstrapSharedBundle {}, nil , nil , err
284+ }
285+
286+ providerRegistry , err := builtin .NewRegistry ()
287+ if err != nil {
288+ return bootstrapSharedBundle {}, nil , nil , err
289+ }
290+ modelCatalogs := providercatalog .NewService (manager .BaseDir (), providerRegistry , nil )
291+ providerSelection := configstate .NewService (manager , providerRegistry , modelCatalogs )
292+ if _ , err := providerSelection .EnsureSelection (ctx ); err != nil {
293+ return bootstrapSharedBundle {}, nil , nil , err
294+ }
295+
296+ return bootstrapSharedBundle {
297+ Config : manager .Get (),
298+ ConfigManager : manager ,
299+ ProviderSelection : providerSelection ,
300+ }, providerRegistry , modelCatalogs , nil
301+ }
302+
303+ // BuildTUIClientDeps 构建 TUI 客户端依赖,仅保留配置与 Provider 选择,不创建本地 runtime/tool 栈。
304+ func BuildTUIClientDeps (ctx context.Context , opts BootstrapOptions ) (RuntimeBundle , error ) {
305+ sharedDeps , _ , _ , err := BuildSharedConfigDeps (ctx , opts )
306+ if err != nil {
307+ return RuntimeBundle {}, err
308+ }
309+ return RuntimeBundle {
310+ Config : sharedDeps .Config ,
311+ ConfigManager : sharedDeps .ConfigManager ,
312+ ProviderSelection : sharedDeps .ProviderSelection ,
313+ MemoService : nil ,
314+ Close : nil ,
315+ }, nil
287316}
288317
289318// bootstrapDefaultConfig 负责计算本次启动应使用的默认配置快照。
@@ -307,20 +336,6 @@ func resolveBootstrapWorkdir(workdir string) (string, error) {
307336 return agentsession .ResolveExistingDir (workdir )
308337}
309338
310- // resolveBootstrapRuntimeMode 归一化并校验 runtime 运行模式。
311- func resolveBootstrapRuntimeMode (mode string ) (string , error ) {
312- normalized := strings .ToLower (strings .TrimSpace (mode ))
313- if normalized == "" {
314- return RuntimeModeLocal , nil
315- }
316- switch normalized {
317- case RuntimeModeLocal , RuntimeModeGateway :
318- return normalized , nil
319- default :
320- return "" , errors .New ("bootstrap: runtime mode must be local or gateway" )
321- }
322- }
323-
324339func buildToolRegistry (cfg config.Config ) (* tools.Registry , func () error , error ) {
325340 toolRegistry := tools .NewRegistry ()
326341 toolRegistry .Register (filesystem .New (cfg .Workdir ))
0 commit comments