ServiceContainer — 服务容器
服务容器实现同名多实现的 IoC 查找。
源码: packages/core/src/service.ts
核心概念
- 一个服务名可有多个提供者(如
llm有 DeepSeek 和 OpenAI 两个实现) - 每个提供者声明优先级(priority);可选偏好(preference)覆盖优先级
- 服务选择走 「偏好 > 优先级 > 注册顺序」:
get()返回当前胜者实例 - 领域级筛选(如按 LLM 模型能力路由)由各
-api自理,不在内核 DI
ServiceEntry 结构
interface ServiceEntry {
instance: unknown; // 服务实例
priority: number; // 优先级(越高越优先)
contextId: string; // 注册者 Context ID
label?: string; // 可选展示标签(如 "OpenAI / gpt-4o")
}关键方法
register(name, instance, priority?, contextId?, label?)
注册服务,返回刚插入的 ServiceEntry。同名服务按优先级降序排列(稳定排序:同优先级先注册者在前)。可用返回的 entry 引用调用 unregisterEntry 精确删除这一条。
get<T>(name)
返回当前胜者实例,解析顺序为 「偏好 > 优先级 > 注册顺序」:先看是否有偏好的提供者(且仍存在),否则取优先级最高、最先注册者。无提供者返回 undefined。
has(name)
检查是否存在提供者(等价于 get(name) !== undefined)。
hasByContext(name, contextId)
检查指定 contextId 是否注册了某服务。"拥有" 语义同时匹配 contextId === ownerId 和以 ownerId + '/' 为前缀的 per-entry 子 entry(如 @aalis/plugin-ollama:main/llama3)。
getEntries(name) / getAll<T>(name)
枚举某服务的所有提供者(给 API/管控视图暴露用)。返回顺序遵循「偏好 > 优先级 > 注册顺序」。getAll 附带 contextId / label 提供者信息。
getServiceNames()
列出所有已注册的服务名。
unregisterEntry(name, entry)
按 entry 引用精确删除某个提供者(推荐),避免 "同一 contextId 多次 register" 时按 contextId 删除命中错误条目的 footgun。返回是否成功删除。
unregisterByContext(contextId)
移除指定 Context 拥有的所有服务 entry,返回被移除的服务名列表。"拥有" 同 hasByContext(含 id 与 id + '/' 前缀子 entry)。用于插件卸载时清理。
服务偏好
当多个插件提供同名服务时,所有者可显式指定偏好的提供者(按 contextId),使其无视 priority 数值始终成为 get() 的胜者。
prefer(name, contextId) / unprefer(name) / getPreferred(name)
容器层的偏好读写。偏好可在目标 entry 注册前提前设置——一旦该 contextId 注册即生效。
公开 API 走
ctx.preferService()/ctx.unpreferService()/ctx.getPreferredService()(额外 emitservice:preference-changed触发whenService重挂);容器层方法仅供 Context 内部转发,插件勿直接调用。所有者也可在 WebUI 的 Services 页面设置偏好。
依赖规范化
function normalizeDependency(dep: string | ServiceDependency): NormalizedDependency将依赖声明统一为 { service }:字符串 'llm' 与对象 { service: 'llm' } 都归一为 { service: 'llm' }。