当前位置: 首页 > news >正文

如何将 Reasonix CLI 集成到 HagiCode 系统中

Reasonix CLI,说起来也是个挺有意思的东西。它是一个基于 ACP(Agent Communication Protocol)的 AI 代码助手工具,提供了强大的流式传输和会话管理能力。其实在 HagiCode.Libs 层,我们已经把它的底层实现都搞定了,只是这些组件还处在孤立的状态,就像一个个漂亮的珍珠,还没串成项链。用户无法通过 Hero 职业选择、会话执行链路或监控面板来使用它,这多少有点可惜。

我们面临的问题是:如何将 Reasonix 提升为与 Codex、Hermes 等同等级的一等 Agent Provider,实现完整的后端路由和前端展示?这可不是简单地注册一个枚举值就能了事的,而是需要构建从底层抽象到用户界面的完整链路。就像盖房子,不能只打个地基就完事了,总得把墙砌起来,屋顶盖上去。

这个集成的挑战在于,Reasonix 作为一个本地 CLI 工具,有自己的性格和脾气。比如它不需要连接字符串,所有参数都是用户运行时配置;它可能根本没有安装,需要优雅降级处理;它兼容 anthropic 系列模型,但又有自己的 effort、budget 等 ACP 特有参数。这就像一个人,有自己独特的处事方式,不能硬来。

经过仔细的架构设计和多轮讨论,我们最终采用了一套清晰的三层架构方案,将 Reasonix 成功集成到系统中。这套方案不仅解决了眼前的问题,也为后续类似 CLI Provider 的集成提供了可复用的模式。其实很多事情就是这样,一旦找到了正确的方法,后面的路就好走多了。

关于 HagiCode

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个开源的 AI 代码助手项目,致力于为开发者提供强大的代码生成、重构和优化能力。在开发过程中,我们遇到了各种各样的技术挑战,将 Reasonix 集成为一等 Agent Provider 就是其中之一。如果你觉得本文分享的方案有价值,说明我们的工程实践还不错,那么 HagiCode 本身也值得关注一下。

核心内容

技术架构设计

系统采用了清晰的三层架构来分离关注点,每层都有明确的职责边界:

HagiCode.Libs 层:这一层已经完成,提供了 CLI provider 的抽象和具体实现。它定义了ICliProvider<ReasonixOptions>接口,实现了ReasonixProvider来处理 ACP 流式传输和会话管理,同时支持 effort、budget、yolo、transcript 等参数。这一层的职责是提供稳定、可复用的底层能力,不涉及任何业务逻辑。就像房子的地基,虽然看不见,但很重要。

hagicode-core 层:这是我们本次集成的重点。它负责将底层抽象桥接到系统的统一接口上。具体工作包括注册AIProviderType.ReasonixCli = 12枚举值,创建ReasonixCliProvider作为 thin adapter 桥接 Libs 层,实现ReasonixGrain处理会话状态和执行流,以及集成 Hero 系统进行参数映射和配置管理。这一层的核心是协调各组件,构建完整的业务链路。就像房子的承重墙,把各个部分连接起来。

web 层:负责向用户展示和收集配置。我们需要重新生成 OpenAPI 类型以支持新枚举值,实现视觉类型映射让 Reasonix 有自己的图标和显示名称,创建 CLI 参数配置表单让用户可以配置各个参数,以及添加多语言支持。这一层的重点是用户体验和交互设计。就像房子的装修,做得好不好直接影响住得舒不舒服。

这样的分层设计让每一层都专注于自己的职责,降低了系统的复杂度,也便于后续维护和扩展。其实很多时候,把事情分清楚,反而会更简单。

关键技术决策

在实现过程中,我们做了几个关键的技术决策,这些决策对最终的架构和用户体验都有重要影响。

决策一:使用专用 Grain

我们创建了独立的ReasonixGrain : IReasonixGrain, IExecutorStreamGrain,而不是尝试复用某个共享的 Grain。这个决策遵循了系统现有的 11 个 provider 的既定模式。虽然看起来可能会有些代码重复,但专用 Grain 让我们可以针对 Reasonix 的特性做精细化的控制,比如它特有的会话绑定机制和 ACP 消息映射。我们还定义了一个空的响应 DTOReasonixResponse作为类型区分符,虽然它不包含实际数据,但在类型系统中起到了重要的作用。就像每个人都有自己的房间,哪怕空着,也是自己的空间。

决策二:不创建专用 Settings 类

与某些需要连接字符串的 Provider 不同,Reasonix 的所有配置都是用户运行时设置的,不需要启动时验证。因此我们没有创建专用的 Settings 类,而是将所有配置存储在AIProviderOptions.Providers[ReasonixCli].Settings字典中。这种模式与 Qoder、Kiro、Kimi 等其他本地 CLI Provider 保持一致,简化了代码结构,也避免了不必要的抽象层。支持的设置键包括:effortbudgetUsdtranscriptPathenableYoloargumentsstartupTimeoutMsreasoning。有时候简单点,反而更好。

决策三:Provider 策略健康监控

Reasonix 是用户本地安装的 CLI,可能根本没有安装,或者不在系统 PATH 中。这种情况下,我们不应该直接报错,而是应该优雅地降级处理。我们使用了Provider策略,通过CommandUtil.TryResolveExecutablePath来检查 CLI 是否可用。如果检查失败,UI 会显示为"不可用",但不会影响系统的其他部分。这种设计让系统更加健壮,也给用户清晰的反馈。毕竟谁也不会希望因为一个小问题,整个系统都挂了。

决策四:经济系统分类

在 HagiCode 系统中,不同的 Provider 有不同的经济系统分类。我们决定让 Reasonix 默认使用'claude'经济系统分类,因为 Reasonix 本身兼容 anthropic 系列模型。目前只有 Codex 和 Copilot 有专用的经济系统分类,其他 Provider 都是复用现有的分类。这样既保持了系统的简洁性,又能正确处理计费和成本统计。复用也是个智慧,不需要什么都从头来。

决策五:模型兼容性

Reasonix 通过--model标志支持多种模型,特别是 anthropic 系列。我们在secondary-professions.index.json中添加了兼容性映射,让用户可以在 Reasonix 中选择这些模型。这种设计既尊重了 Reasonix 的能力,又保持了系统的一致性,用户无需理解底层的区别,就能顺畅地使用各种模型。用户也不容易,还是让他们简单点好。

后端实现细节

后端实现分为几个关键部分,每部分都有其独特的技术要点。

枚举和类型注册

首先,我们需要在系统中注册新的 Provider 类型:

// AIProviderType.cs
public enum AIProviderType
{
// ... 其他 provider
ReasonixCli = 12,
}
// AIProviderTypeExtensions.cs
private static readonly Dictionary<string, AIProviderType> _typeMap = new()
{
// ... 其他映射
["Reasonix"] = AIProviderType.ReasonixCli,
["reasonix"] = AIProviderType.ReasonixCli,
["reasonix-cli"] = AIProviderType.ReasonixCli,
["ReasonixCli"] = AIProviderType.ReasonixCli,
};

这个枚举值需要与其他并发变更协调,避免冲突。我们选择了 12 这个值,因为它是下一个可用的编号。就像排队一样,总得有个先后顺序。

Thin Adapter 实现

ReasonixCliProvider是连接 Libs 层和系统统一接口的关键组件:

public sealed class ReasonixCliProvider : IAIProvider, IVersionedAIProvider, IAsyncDisposable
{
private static readonly IReadOnlyList<string> SupportedSettingKeys =
[
"effort",
"budgetUsd",
"transcriptPath",
"enableYolo",
"arguments",
"startupTimeoutMs",
"reasoning"
];
private readonly ICliProvider<ReasonixOptions> _provider;
private readonly ConcurrentDictionary<string, string> _sessionBindings = new(StringComparer.Ordinal);
public async IAsyncEnumerable<AIStreamingChunk> StreamCoreAsync(
AIRequest request,
string? sessionId = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var options = BuildOptions(request, sessionId);
await foreach (var message in _provider.StreamAsync(options, cancellationToken))
{
yield return MapToStreamingChunk(message);
}
}
private ReasonixOptions BuildOptions(AIRequest request, string? sessionId)
{
return new ReasonixOptions
{
ExecutablePath = GetExecutablePath(),
WorkingDirectory = GetWorkingDirectory(),
Model = _config.Model,
Effort = _config.Settings.GetValueOrDefault("effort", "medium"),
Budget = _config.Settings.GetValueOrDefault("budgetUsd", 10.0),
Yolo = _config.Settings.GetValueOrDefault("enableYolo", false),
TranscriptPath = _config.Settings.GetValueOrDefault("transcriptPath"),
Arguments = _config.Settings.GetValueOrDefault("arguments", ""),
StartupTimeout = GetStartupTimeout(),
EnvironmentVariables = _environmentVariables,
CessionId = sessionId ?? GetCessionId()
};
}
}

这个 adapter 的关键职责是:

  1. 验证配置参数,拒绝不支持的设置键
  2. 维护会话绑定关系,支持会话恢复
  3. 将 ACP 消息映射到系统的统一格式
  4. 使用ProviderErrorAutoRetryCoordinator实现自动重试

就像一个翻译官,把一种语言翻译成另一种语言,还得保证意思准确无误。

Orleans Grain 实现

ReasonixGrain负责处理会话状态和执行流:

public class ReasonixGrain : Grain, IReasonixGrain, IExecutorStreamGrain
{
private readonly Dictionary<string, ExecutorToolLifecycleStatus> _toolLifecycleState =
new(StringComparer.Ordinal);
public IAsyncEnumerable<ReasonixResponse> ExecuteCommandStreamAsync(
string command,
string? heroId = null,
CancellationToken token = default,
string? executionMessageId = null,
string? systemMessage = null,
Dictionary<string, string>? requestSettings = null)
{
var request = BuildRequest(command, isEdit: false, heroId, executionMessageId, systemMessage, requestSettings);
return SendAsync(request, heroId, token);
}
private async IAsyncEnumerable<ReasonixResponse> SendAsync(
AIRequest request,
string? heroId,
[EnumeratorCancellation] CancellationToken token)
{
_cancellationTokenSource = new CancellationTokenSource();
var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellationTokenSource.Token);
var provider = await ResolveReasonixProviderAsync(heroId);
await foreach (var chunk in provider.StreamAsync(request, linkedToken.Token))
{
var response = BuildChunkResponse(chunk);
yield return response;
}
}
private async Task<IAIProvider> ResolveReasonixProviderAsync(string? heroId)
{
// Hero 感知的配置回退逻辑
var config = await HeroProviderResolver.ResolveAsync(AIProviderType.ReasonixCli, heroId);
return _aiProviderFactory.CreateProvider(AIProviderType.ReasonixCli, config);
}
}

Grain 的核心功能包括:

  1. 使用[PersistentState("reasonix-interop")]维护会话状态
  2. 实现 Hero 感知的配置回退逻辑
  3. 追踪工具生命周期状态
  4. 支持取消令牌链,确保执行可以及时中断

这就像一个管家,把事情安排得井井有条。

Hero 系统集成

Hero 系统是 HagiCode 的职业配置系统,我们需要将 Reasonix 集成到这个体系中:

// HeroAppService.cs
// Family 推断
AIProviderType.ReasonixCli => "reasonix"
// 托管的 CLI 参数
ManagedCliParameterKeysByProvider[AIProviderType.ReasonixCli] =
["binary", "effort", "budgetUsd", "transcriptPath", "enableYolo", "arguments", "startupTimeoutMs"];
// 托管的模型参数
ManagedModelParameterKeysByProvider[AIProviderType.ReasonixCli] =
["model", "reasoning"];

在职业目录配置文件main-professions.yaml中:

- Id: "profession-reasonix"
Name: "Reasonix"
Family: "reasonix"
Summary: "hero.professionCopy.primary.reasonix.summary"
Icon: "executor-avatar:Reasonix"
SourceLabel: "hero.professionCopy.sources.aiProvidersReasonixCli"
ProviderType: "ReasonixCli"
SortOrder: 130
DefaultEnabled: true
DefaultParameters:
binary: "reasonix"
effort: "medium"
enableYolo: "false"
startupTimeoutMs: "15000"
http://www.jsqmd.com/news/1089017/

相关文章:

  • DLSS Swapper终极指南:一键升级游戏画质与性能的免费工具
  • WechatDecrypt:3步解锁你的微信聊天记录,重获数据自主权
  • 软考成绩明天下午公布,下半年备考计划
  • 终极Jable视频下载解决方案:开源工具实现一键离线保存
  • 任意文件上传漏洞实战:从原理到利用与防御
  • 从零到一:在Ubuntu上搭建Petalinux开发环境全攻略
  • 终极qmcdump指南:彻底解锁QQ音乐加密音频的完整解决方案
  • 微博图片批量下载终极指南:高效获取高清原图的完整方案
  • 微信小程序渗透测试实战:从信息收集到漏洞挖掘的完整指南
  • openEuler libummu在异构计算中的应用:GPU与AI加速器内存共享终极指南
  • HC32F460+RT-Thread U盘在线升级实战指南
  • 为什么你的 C++ 代码总比别人慢?这招链接时优化能让性能翻倍
  • 统信UOS系统下Nvidia显卡驱动从入门到精通:手动安装与疑难排解
  • NS-USBLoader:一站式解决Switch游戏传输、系统破解与文件管理的全能工具
  • 智慧树刷课插件:3分钟实现学习自动化,效率提升300%的终极指南
  • Claude 4.8 输出不稳定、格式跑偏与幻觉问题排查及解决方案
  • GLPI未授权SQL注入漏洞CVE-2025-24799深度剖析与复现
  • 从零到一:基于STM32与DDS技术的可编程信号发生器实战(附完整工程文件)
  • 2025 Linux内核年度复盘:从6.12到6.18,实时、Rust、eBPF三大革命落地
  • 魔兽争霸III终极兼容优化指南:三步解决宽屏适配、地图加载与性能问题
  • Neo4j 之水浒传梁山好汉图谱构建与关系推演
  • 【课程设计/毕业设计】面向校园 / 城市的便民租房管理系统的设计与实现 基于 Web 技术的同城房源匹配租房系统的设计与实现【附源码、数据库、万字文档】
  • QMCDecode终极指南:如何轻松解密QQ音乐加密文件实现跨平台播放
  • FPGA驱动OV5640:从SCCB时序到图像采集的实战解析
  • 从crAPI靶场实战看API安全:逆向工程与逻辑漏洞深度剖析
  • Verilog 高级调试与验证实战笔记——系统任务深度解析
  • SPSS假设检验实战指南:从参数、非参数到方差分析的应用抉择
  • 终极OneNote插件OneMore:160+功能全面解锁你的笔记效率
  • 从零到一:基于XCAT构建企业级计算集群实战
  • 决策树原理与工程落地:从可解释性到业务规则对齐