插件配置与校验
插件通常需要读取部署环境相关的参数(例如超时阈值、服务地址或默认选项)。Tether 坚持配置大声报错(Fail Loud)原则:配置错误在加载阶段立即被拦截,绝不在运行时静默降级或吞掉错误。
配置模型定义
在 C# 版 Cordis 中,配置类使用标准的 C# 属性与 System.ComponentModel.DataAnnotations 特性定义校验规则;插件如何收到这个配置对象取决于它以内建还是树外方式接入(见下一节):
using System.ComponentModel.DataAnnotations;
namespace MyPlugins;
/// <summary>
/// 问候工具的配置选项
/// </summary>
public sealed class GreetConfig
{
[Required(ErrorMessage = "DefaultGreeting 是必填项")]
[StringLength(50, MinimumLength = 1, ErrorMessage = "问候语长度必须在 1 到 50 之间")]
public string DefaultGreeting { get; set; } = "Hello";
[Range(1, 300, ErrorMessage = "超时时间必须在 1 到 300 秒之间")]
public int TimeoutSeconds { get; set; } = 30;
public bool EnableTimestamp { get; set; } = false;
}
编写带配置的插件
配置对象是在插件实例化之前物化的:加载器先把 YAML 的 config 节点绑定到配置类型并跑完 DataAnnotations 校验,再把结果交给插件。因此内建插件的常见写法是构造函数接收配置,catalog.Register<TConfig> 的工厂把物化好的实例传进来:
using Cordis;
using Microsoft.Extensions.AI;
using Tether.Core;
namespace MyPlugins;
[Inject(typeof(IToolRegistry))]
public sealed class ConfigurableGreetPlugin(GreetConfig config) : Plugin
{
protected override void Apply(Context context)
{
var tools = context.Require<IToolRegistry>();
var greetFunc = AIFunctionFactory.Create(
(string name) =>
{
var timePrefix = config.EnableTimestamp
? $"[{DateTime.UtcNow:HH:mm:ss}] "
: string.Empty;
return $"{timePrefix}{config.DefaultGreeting}, {name}!";
},
name: "greet",
description: "根据当前配置的问候语向用户问好");
context.Effect(tools.Register(greetFunc).Dispose);
}
}
对应的 catalog 注册(src/Tether.Bundles/BundleCatalogs.cs 中 persistent-shell-tools 等行同此模式):
catalog.Register<GreetConfig>("custom-greet", config => new ConfigurableGreetPlugin(config));
树外插件包(由 Tether.PluginPackages 加载的独立程序集)没有 catalog 工厂,配置经另一条路径注入:插件要么实现 IConfigurablePlugin.AcceptConfig(object?)(无参构造 + 接收配置),要么提供恰好一个能接收配置类型的公共构造函数。约定不满足时加载直接失败。
在 YAML 中注入配置
在 cordis.yml 或组合文件中,将参数写入对应插件的 config 节点:
plugins:
- { id: tools, name: tools }
- id: custom-greet
name: custom-greet
config:
defaultGreeting: "欢迎来到 Tether"
timeoutSeconds: 60
enableTimestamp: true
行的合法键只有 id、name、disabled、config;name 对应 catalog 注册名(内建为 tools 这类固定名,树外插件包则是 packageId. 前缀的名字)。其它键(比如 package)会让加载器抛 CompositionException。
环境变量替换
YAML 配置支持通过 ${ENV_NAME} 语法引用环境变量:
plugins:
- id: custom-greet
name: custom-greet
config:
defaultGreeting: "${CUSTOM_GREETING_PREFIX:-欢迎}"
timeoutSeconds: 30
校验失败的处理机制
如果 YAML 中的配置违反了 DataAnnotations 规则(例如传入了负数超时或遗漏了必填字段):
plugins:
- id: custom-greet
name: custom-greet
config:
timeoutSeconds: -10 # 违反 [Range(1, 300)]
Cordis 加载器会立即中止加载,抛出 CompositionException,明确输出出错的字段名与校验错误信息:
Unhandled exception: Cordis.Loader.CompositionException:
Invalid config for plugin 'custom-greet' in row 'custom-greet':
- TimeoutSeconds: 超时时间必须在 1 到 300 秒之间
这种机制杜绝了由于拼写错误或非法值导致的隐蔽运行时缺陷。