[DeepSeek Harness插件内核-09]服务的多种定义的形式
2026/9/3 17:14:05 网站建设 项目流程

DeepSeek Harness插件内核-02:服务的注册与消费中,我们简单体验了一下如何在Cordis中定义、注册和使用服务,在DeepSeek Harness插件内核-08:利用RefectService以反射方式扩展Context中介绍的ReflectService的核心作用还是集中在服务的注册上。为插件提供复用功能的服务在Corids中具有重要的意思,而且服务本身也可以作为插件形式被注册,本篇文章将对服务展开全面介绍。

1. 使用常规类型

虽然Cordis为服务定义了名为Service的基类,但是却没有要求注册的服务强制继承该基类,原则任何一个对象都可以注册为服务。为了更加深刻地理解服务,在正是介绍Service这个基类之前,我们选在将服务定义成一个普通的类型。

比如我们定义了如这个普通类型Logger作为用来记录日志的基础服务(类似于现有的LoggerService)。为了让它具有自动跟踪当前Context的能力,我们必须在symbols.tracker属性上设置一个Tracker对象,并利用property设置绑定当前Context的属性名。如果不需要携带在构造函数中提供的原始Context,我们最好将noShadow属性设置为true。由于构造函数将指定的Context绑定到了私有属性ctx中,所以用来记录日志的write方法就能使用这个属性得到当前Context的信息,这里我利用它得到当前插件的名称,并将其输出到日志中。

import{Context,symbols,Tracker}from'@deepseek-ai/cordis'declaremodule'@deepseek-ai/cordis'{interfaceContext{log:Logger}}classLogger{[symbols.tracker]:Tracker={property:"ctx",noShadow:true}write(message:string,severity:"trace"|"debug"|"info"|"warn"|"error"){console.log(`[${severity}][${this.ctx.fiber.runtime?.name}]${message}`);}constructor(privatectx:Context){}}constctx=newContext();ctx.provide("log",newLogger(ctx));ctx.plugin({name:"Plugin A",apply:ctx=>ctx.log.write("Plugin A starts","info")});ctx.plugin(ctx=>ctx.log.write("Plugin B starts","info"));process.stdin.resume();

输出:

[info][Plugin A]Plugin A starts[info][]Plugin B starts

从输出可以看出,由于插件具有三种定义形式,对于直接以函数注册的插件其实是没有名称的,所以针对第二个插件的日志中将会显式该部分的内容。

2. 将配置分离出来

将服务的执行逻辑和配置分离,有配置动态控制服务的执行行为在很多情况下都是必要,同时也是推荐的做法。通过DeepSeek Harness插件内核-06:Context全面解析的介绍,我们知道服务的配置被专门存储在Conextsymbols.intercepts属性中,我们只需要调用Contextintercept方法通过指定的服务名称和配置对象创建一个子Context就行。所以我们选择将上面定义的Logger服务添加一个结构为{source:string}的配置,并使用插件名称作为兜底选项。由于第二个插件是在我们创建的子Context上注册的,创建它针对intercept方法的调用中为log服务指定了正确的配置。

import{Context,symbols,Tracker}from'@deepseek-ai/cordis'declaremodule'@deepseek-ai/cordis'{interfaceContext{log:Logger}}classLogger{[symbols.tracker]:Tracker={property:"ctx",noShadow:true}write(message:string,severity:"trace"|"debug"|"info"|"warn"|"error"){constconfig=(this.ctx[symbols.intercept]asany|undefined)?.log;constsource=(configas{source:string})?.source??this.ctx.fiber.runtime?.name;console.log(`[${severity}][${source}]${message}`);}constructor(privatectx:Context){}}constctx=newContext();ctx.provide("log",newLogger(ctx));ctx.plugin({name:"Plugin A",apply:ctx=>ctx.log.write("Plugin A starts","info")});constsubContext=ctx.intercept("log",{source:"Plugin B"});subContext.plugin(ctx=>ctx.log.write("Plugin B starts","info"));

输出:

[info][Plugin A]Plugin A starts[info][Plugin B]Plugin B starts

3. 继承基类Service

在大部分情况下,我们推荐将服务定义为继承自Service基类的形式,因为这样确实可以减少很多重复的代码。Service基类在构造函数中将Context绑定到了ctx属性上,并且在symbols.tracker属性上设置了一个Tracker对象,property属性为ctxnoShadow属性为true,所以我们可以直接使用ctx属性来访问当前Context。上面定义的Logger服务可以直接继承Service基类,并在构造函数中调用super(ctx, "log")来完成服务的注册。

import{Context,symbols,Service}from'@deepseek-ai/cordis'declaremodule'@deepseek-ai/cordis'{interfaceContext{log:Logger}}classLoggerextendsService{write(message:string,severity:"trace"|"debug"|"info"|"warn"|"error"){constconfig=(this.ctx[symbols.intercept]asany|undefined)?.log;constsource=(configas{source:string})?.source??this.ctx.fiber.runtime?.name;console.log(`[${severity}][${source}]${message}`);}constructor(ctx:Context){super(ctx,"log");}}constctx=newContext();newLogger(ctx);ctx.plugin({name:"Plugin A",apply:ctx=>ctx.log.write("Plugin A starts","info")});constsubContext=ctx.intercept("log",{source:"Plugin B"});subContext.plugin(ctx=>ctx.log.write("Plugin B starts","info"));

输出:

[info][Plugin A]Plugin A starts[info][Plugin B]Plugin B starts

4. 让服务实例变成可同时调用的函数

如果我们为服务类型定义一个通过symbols.invoke属性标记的方法,我们就可以将服务实例当作一个函数来调用,这样就可以让服务的使用更加简洁。我们在Logger服务中定义了一个symbols.invoke属性标记的write方法,这样就可以直接通过log服务实例来调用write方法。

import{Context,symbols,Service}from'@deepseek-ai/cordis'declaremodule'@deepseek-ai/cordis'{interfaceContext{log:Logger&((message:string,severity:"trace"|"debug"|"info"|"warn"|"error")=>void)}}classLoggerextendsService{write(message:string,severity:"trace"|"debug"|"info"|"warn"|"error"){constconfig=(this.ctx[symbols.intercept]asany|undefined)?.log;constsource=(configas{source:string})?.source??this.ctx.fiber.runtime?.name;console.log(`[${severity}][${source}]${message}`);}constructor(ctx:Context){super(ctx,"log");}[symbols.invoke](message:string,severity:"trace"|"debug"|"info"|"warn"|"error"){this.write(message,severity);}}constctx=newContext();newLogger(ctx);ctx.plugin({name:"Plugin A",apply:ctx=>ctx.log("Plugin A starts","info")});constsubContext=ctx.intercept("log",{source:"Plugin B"});subContext.plugin(ctx=>ctx.log("Plugin B starts","info"));

输出

[info][Plugin A]Plugin A starts[info][Plugin B]Plugin B starts

5. 基类Service的定义

我们最后来看看作为基类的Service的定义。这是一个泛型类,类型参数T代表服务的配置类型。Service基类在构造函数中将Context绑定到了ctx属性上,并且在symbols.tracker属性上设置了一个Tracker对象,property属性为ctxnoShadow属性为true,所以我们可以直接使用ctx属性来访问当前Context。由于构造函数内部会自动调用Contextprovide方法将服务注册到当前Context中,所以我们不需要在服务的构造函数中显式调用provide方法。我们可以调用构造函数的第二个参数来指定服务的名称,如果不指定则会使用provide静态属性作为服务的名称。

exportabstractclassService<outT=never>{declare[symbols.config]:Tpublicname!:stringconstructor(protectedctx:Context,name:string){name??=this.constructor['provide']asstringletself=thisconsttracker:Tracker={associate:name,property:'ctx',}if(self[symbols.invoke]){self=createCallable(name,joinPrototype(Object.getPrototypeOf(this),Function.prototype),tracker)}self.ctx=ctx self.name=namedefineProperty(self,symbols.tracker,tracker)self.ctx.reflect.provide(name,self,this[symbols.check])returnself}}

Servicesymbols.invoke属性标记的方法作为服务实例的默认调用方法,实现在针对createCallable函数的调用上。下面的代码提供了针对createCallable函数的实现,可以看出整个实现很简单:它将服务实例包装成一个可调用的函数,并在调用时创建一个跟踪当前Context的代理对象。

exportfunctioncreateCallable(name:string,proto:{},tracker:Tracker){constself=function(...args:any[]){constproxy=createTraceable(self['ctx'],self,tracker)returnapplyTraceable(proxy,self,this,args)}defineProperty(self,'name',name)returnObject.setPrototypeOf(self,proto)}functionapplyTraceable(proxy:any,value:any,thisArg:any,args:any[]){if(!value[symbols.invoke])returnReflect.apply(value,thisArg,args)returnvalue[symbols.invoke].apply(proxy,args)}

Service还定义了如下这些利用预定义Symbol命名的受保护方法供继承它的类型使用。

exportabstractclassService<outT=never>{protected[symbols.filter](ctx:Context){returnctx[symbols.isolate][this.name]===this.ctx[symbols.isolate][this.name]}protected[symbols.extend](props?:any){letself:anyif(this[Service.invoke]){self=createCallable(this.name,this,this[symbols.tracker])}else{self=Object.create(this)}returnObject.assign(self,props)}[symbols.resolveConfig](base?:T,head?:T):T{letintercept=this.ctx[Context.intercept]constconfigs:any[]=[]while(this.nameinintercept){if(Object.hasOwn(intercept,this.name)){configs.unshift(intercept[this.name])}intercept=Object.getPrototypeOf(intercept)}if(base)configs.unshift(base)if(head)configs.push(head)if(this['Config']?.merge){returnthis['Config'].merge(...configs)}else{returnObject.assign({},...configs)}}}

具体的方法说明如下:

  • [symbols.filter]:用于判断当前Context是否与服务实例的原始Context相同,如果相同则返回true,否则返回false。这个方法主要用于在服务实例被继承时,判断当前Context是否与服务实例的原始Context相同,从而决定是否需要创建一个新的服务实例;
  • [symbols.extend]:用于创建一个新的服务实例,并将当前服务实例的属性复制到新的服务实例上。这个方法主要用于在服务实例被继承时,创建一个新的服务实例,从而实现服务的复用;
  • [symbols.resolveConfig]:用于解析服务的配置,返回一个合并后的配置对象。这个方法主要用于在服务实例被继承时,解析服务的配置,从而实现服务的复用。

除此以外,Service还定义了一个通过Symbol.hasInstance属性标记的静态方法,用于判断一个对象是否是指定服务类型的实例。从代码可以看出,该方法通过判断对象的原型链上是否存在指定服务类型的构造函数来确定对象是否是指定服务类型的实例。

exportabstractclassService<outT=never>{static[Symbol.hasInstance](instance:any){if(!instance)returnfalseletconstructor=instance.constructorwhile(constructor){constructor=constructor.prototype?.constructorif(constructor===this)returntrueconstructor&&=Object.getPrototypeOf(constructor)}returnfalse}}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询