[DeepSeek Harness深度拆解-17]如何使用DSH的配置服务
发布时间:2026/9/29 20:09:49来源:尧图网络
deepseek-ai/dsh-settings包提供settings服务是DSH中用于管理运行时配置的底层核心服务专门负责系统参数的命名空间注册、分层解析、热更新持久化以及变更检测。deepseek-ai/dsh-settings-file进一步提供了基于文件的配置系统本篇文章将通过几个简单的实例来提供DSH基于配置的编程体验。1. DSH的settings服务DSH利用settings这个基础服务为所有的插件提供结构化配置该服务返回一个SettingsProvider对象。如下面的代码所示这是一个抽象类由deepseek-ai/dsh-settings包提供。派生于它的FileSettingsProvider实现了基于指定文件支持yaml和json的配置系统对应的包为deepseek-ai/dsh-settings-file。exportabstractclassSettingsProviderextendsService{constructor(ctx:Context){super(ctx,settings)}}exportclassFileSettingsProviderextendsSettingsProvider{constructor(ctx:Context,publicconfig:Config){super(ctx)...}}exportinterfaceConfig{path?:stringdshHome?:stringwatch?:booleandebounceMs?:number}和很多基础服务一样FileSettingsProvider以插件的方式自身注册为基础服务。插件对应的配置接口Config定义了如下的配置选项path设置文档的完整路径。若省略则使用默认的路径{dshHome}/settings.yaml;dshHome当path未提供时使用的harness 主目录默认取环境变量$DSH_HOME;watch是否监听配置文件并热发布外部修改默认true。开启后手动编辑文件也能让设置立即生效;debounceMs监听器的写入稳定窗口毫秒默认100。文件系统事件常会连续触发多次用这个窗口合并短时间内的多次写入避免频繁重载。2. 配置的注册register是SettingsProvider的核心方法插件通过它声明自己要用哪块配置、配置长什么样并拿回一个可读写该配置的作用域对象。泛型参数T表示配置对象的类型。exportabstractclassSettingsProviderextendsService{registerconstNamespaceextendsstring,T(ns:NamespaceSettingsNamespaceInputNamespace,schema:zT,options?:SettingsRegisterOptionsT,):SettingsScopeT}exportinterfaceSettingsRegisterOptionsT{base?:PartialTapplies?:SettingsApplies validate?:(value:T)void}exporttypeSettingsApplieslive|restartregister方法定义了如下三个参数ns一个字符串作为该插件配置在全局设置文档中的唯一标识schema一个由deepseek-ai/schemastery提供的zT对象描述配置的结构、字段类型与默认值。服务用它来校验写入数据、填充缺省值并推导配置对象的类型optionsSettingsRegisterOptionsT用于控制注册行为的额外选项包括提供的基础配置、生效或可见和验证操作。register方法返回的SettingsScopeT对象代表注册配置的作用域是插件读写自身配置的入口。四个方法覆盖了配置的读取、更新的监听、增量修改和整体替换四个操作。exportinterfaceSettingsScopeT{get():Twatch(callback:(next:T,prev:T)void|Promisevoid):()voidupdate(patch:object):Promisevoidreplace(section:object):Promisevoid}2.1 配置的读取我们通过如下的程序来演示如何调用上述的register方法基于指定的命名空间注册一组结构化的配置。简单起见我们的配置对象只包含foo和bar两个字符串类型的成员通过FoobarConfig接口来表示。声明为zFoobarConfig类型的Config通过调用z.object方法为它创建的Schema。我们在创建的根Context中注册了FileSettingsProvider插件并设置了配置文件的路径./explor/foobar.json。import{Context}fromdeepseek-ai/cordisimport{FileSettingsProvider}fromdeepseek-ai/dsh-settings-fileimportzfromdeepseek-ai/schemasteryinterfaceFoobarConfig{foo:stringbar:string}constConfig:zFoobarConfigz.object({foo:z.string(),bar:z.string(),})constctxnewContext()ctx.plugin(FileSettingsProvider,{path:./explor/foobar.json})ctx.inject([settings],ctx{constscopectx.settings.register(foobar,Config)constconfigscope.get()console.log(initial settings:${JSON.stringify(config)})})在另一个调用inject方法注册的插件中我们调用ctx.settings.register方法完成了针对Config的注册对应的命名空间被设置为foobar。在得到返回的SettingsScopeConfig对象后我们调用get方法得到配置对象并将其序列化成JSON后输出。配置文件./explor/foobar.json的内容如下{foobar:{foo:111,bar:111}}程序之后输出的配置对象将与配置文件的内容匹配initial settings: {foo:111,bar:111}2.2 配置的增量更新和替换下面的程序进一步演示了通过调用SettingsScopeT的update和replace方法对配置的增量更新和整体替换对应的操作最终修改配置文件的内容。如下面的代码所示我们在调用register方法并得到对应的SettingsScopeConfig对象后先后调用了update和replace方法随后读取并输出了配置文件的内容。import{Context}fromdeepseek-ai/cordisimport{FileSettingsProvider}fromdeepseek-ai/dsh-settings-fileimportzfromdeepseek-ai/schemasteryimport{readFile}fromnode:fs/promisesinterfaceFoobarConfig{foo:stringbar:string}constConfig:zFoobarConfigz.object({foo:z.string(),bar:z.string(),})constctxnewContext()ctx.plugin(FileSettingsProvider,{path:./explor/foobar.json})ctx.inject([settings],asyncctx{constscopectx.settings.register(foobar,Config)awaitscope.update({foo:222})console.log(awaitreadFile(./explor/foobar.json,utf-8))awaitscope.replace({foo:333,bar:333})console.log(awaitreadFile(./explor/foobar.json,utf-8))})输出的两端JSON{foobar:{foo:222,bar:111}}{foobar:{foo:333,bar:333}}2.3 监控配置文件的更新通过调用SettingsScopeT的watch方法可以监控配置文件的更新在重新加载配置对象之后会将更新前后的配置作为参数调用指定的回调函数。在如下的演示程序中我们在调用register方法并得到对应的SettingsScopeConfig对象后先后调用了watch注册了一个回调函数将检测到的配置更新输出来。为了让注册的插件不推出我们调用effect方法进行了长时间的等待。import{Context}fromdeepseek-ai/cordisimport{FileSettingsProvider}fromdeepseek-ai/dsh-settings-fileimportzfromdeepseek-ai/schemasteryinterfaceFoobarConfig{foo:stringbar:string}constConfig:zFoobarConfigz.object({foo:z.string(),bar:z.string(),})constctxnewContext()ctx.plugin(FileSettingsProvider,{path:./explor/foobar.json})ctx.inject([settings],asyncctx{constscopectx.settings.register(foobar,Config)scope.watch((next,prev){console.log(detect settings changes: previous:${JSON.stringify(prev)}next:${JSON.stringify(next)})})ctx.effect((){consttimersetInterval((){},1000*60*60)return()clearInterval(timer)})})程序启动后我们可以手工修改位置文件的内容我们的程序会实时将配置更新输出来detect settings changes: previous: {foo:333,bar:333} next: {foo:444,bar:333} detect settings changes: previous: {foo:444,bar:333} next: {foo:444,bar:555}3. 另一个更有用的方法其实我们使用settings服务很少会使用到register方法更多的时候使用的是它的installSection方法。如果我们需要注册一个持续、不间断工作的插件或者服务我们应该直接注入settings服务因为这样会导致settings服务不可用的时候自身也会被卸载掉。我们希望的配置消费形式是如果settings服务上线就使用它提供的配置否则使用预设的配置兜底。installSection方法定义如下。由于它内部也会调用register方法完成配置的注册所以也需要提供命名空间和Schema。owner对应的Context与作为消费者的插件或者服务绑定entry参数提供的就是那个兜底的配置。hooks提供的SettingsSectionHooksT对象包含三个构造函数分别用来提供配置、监控更新和验证配置。exportabstractclassSettingsProviderextendsService{installSectionconstNamespaceextendsstring,T(owner:Context,ns:NamespaceSettingsNamespaceInputNamespace,schema:zT,entry:T,hooks:SettingsSectionHooksT,):void}exportinterfaceSettingsSectionHooksT{setSource(current:()T):voidonChange():voidvalidate?:(value:T)void}在如下这个演示程序中我们注册的FoobarService用来模拟哪个不直接依赖于settings的服务它的getConfig方法用来提供当前的配置。它的构造函数利用fallback参数来提供兜底的配置并用它来初始化getConfig方法。我们在构造函数中通过调用inject方法注册了一个子插件并在其中调用settings服务的installSection方法最终的目的就是利用构造函数setSource得到用来提取配置的函数并用来作为FoobarService服务的getConfig方法。import{Context,Service}fromdeepseek-ai/cordisimport{FileSettingsProvider}fromdeepseek-ai/dsh-settings-fileimportzfromdeepseek-ai/schemasteryimport{writeFile}fromnode:fs/promisesinterfaceFoobarConfig{foo:stringbar:string}constConfig:zFoobarConfigz.object({foo:z.string(),bar:z.string(),})classFoobarServiceextendsService{getConfig:()FoobarConfigconstructor(ctx:Context,fallback:FoobarConfig){super(ctx,foobar)this.getConfig()fallback ctx.inject([settings],settingsCtx{settingsCtx.settings.installSection(ctx,foobar,Config,fallback,{setSource:currentthis.getConfigcurrent,onChange:(){}})})}}declaremoduledeepseek-ai/cordis{interfaceContext{foobar:FoobarService}}awaitwriteFile(./explor/foobar.json,JSON.stringify({foobar:{foo:111,bar:111}}),utf8)constctxnewContext()constsettingsFiberctx.plugin(FileSettingsProvider,{path:./explor/foobar.json})awaitsettingsFiber.await()awaitctx.plugin(FoobarService,{foo:000,bar:000}).await()ctx.inject([foobar],asyncctx{console.log(setings service is available:)console.log(ctx.foobar.getConfig())awaitsettingsFiber.dispose()console.log(setings service is not available:)console.log(ctx.foobar.getConfig())})程序开启执行的时候通过写入配置文件./explor/foobar.json的形式将配置设置为{ foo: 111, bar: 111 }然后以此配置文件的路径注册了FileSettingsProvider插件并得到对应的Fiber对象。在注册Foobarservice的时候我们将兜底的配置设置为{ foo: 000, bar: 000 }。我们在注册的插件中演示FoobarService针对配置的兜底功能。如代码所示我们在settings可用的条件下调用其getConfig方法并输出返回的配置。然后我们调用settings服务所在Fiber的dispose方法其目的是将settings服务卸载掉然后再次输出FoobarService的配置。可以看出两次输出是不同的。setings service is available: { foo: 111, bar: 111 } setings service is not available: { foo: 000, bar: 000 }
网站建设高端定制企业官网