C#后端集成CodeBuddy CLI的完整方案
本文將詳細介紹如何在 C# 后端項目中集成 CodeBuddy CLI,實現(xiàn) AI 編程助手能力的完整方案。
背景
在現(xiàn)代 AI 代碼助手開發(fā)中,單一 AI Provider 往往無法滿足復雜多變的開發(fā)場景。這就像,人生路遠,總不能只認一個方向吧?HagiCode 作為一款多功能 AI 編程助手,需要支持多種 AI Provider 以提供更好的用戶體驗。畢竟,用戶的選擇權還是要給夠的。在 2026 年初,項目面臨一個關鍵決策:如何在 C# 后端中恢復 CodeBuddy 的 ACP(Agent Communication Protocol)集成能力。
此前項目中曾實現(xiàn)過 CodeBuddy 對接,但相關代碼在一次重構中被移除了。其實也沒什么好抱怨的,代碼迭代嘛,總有東西要被遺忘。本次技術方案的目標是完整恢復這一能力,并優(yōu)化架構使其更加健壯和可維護。
如果你也在考慮為自己的項目接入多種 AI 編程助手,下面的方案或許能給你一些啟發(fā)——這可是我們踩了無數(shù)坑之后總結出來的經(jīng)驗。或許能讓你少走點彎路,也算是我做過的一點好事吧。
關于 HagiCode
本文分享的方案來自我們在 HagiCode 項目中的實踐經(jīng)驗。HagiCode 是一個開源的 AI 代碼助手項目,支持多種 AI Provider 和跨平臺運行。為了滿足不同用戶的偏好,我們需要能夠靈活切換各種 AI 編程助手,這就有了本文要介紹的 CodeBuddy 集成方案。
HagiCode 采用模塊化設計,AI Provider 作為可插拔的組件,這種架構讓我們可以輕松添加新的 AI 支持,而不影響現(xiàn)有功能。這也罷了,設計這種東西,當初做得好,后面省心不少。如果你對我們的技術架構感興趣,可以在 GitHub 上查看完整源碼。
架構設計
分層架構概覽
C# 與 CodeBuddy 的對接采用清晰的分層架構,這種設計讓代碼職責分明,后期維護起來也更加方便:
┌─────────────────────────────────────────────┐ │ Provider 契約層 │ │ AIProviderType 枚舉 + 擴展方法 │ ├─────────────────────────────────────────────┤ │ Provider 工廠層 │ │ AIProviderFactory 依賴注入工廠 │ ├─────────────────────────────────────────────┤ │ Provider 實現(xiàn)層 │ │ CodebuddyCliProvider 具體實現(xiàn) │ ├─────────────────────────────────────────────┤ │ ACP 基礎設施層 │ │ ACPSessionManager / StdioAcpTransport │ │ AcpRpcClient / AcpAgentClient │ └─────────────────────────────────────────────┘
這種分層的好處是什么呢?簡單說就是各層之間互不打擾。假設以后要換一種通信方式(比如從 stdio 改成 WebSocket),你只需要改最下面那一層,上面的業(yè)務代碼完全不用動。畢竟,誰也不想牽一發(fā)而動全身,改個通信方式還要改半天業(yè)務代碼,那也太慘了。
核心組件解析
Provider 契約層 是整個架構的基石。我們定義了 AIProviderType 枚舉,其中 CodebuddyCli = 3 作為枚舉值,通過擴展方法實現(xiàn)字符串與枚舉的雙向映射。這樣配置文件中的字符串可以很方便地轉成枚舉,調(diào)試時枚舉也能轉成字符串輸出。這也罷了,其實就是個映射關系,但做好了就是省心。
Provider 工廠層 負責根據(jù)配置創(chuàng)建對應的 Provider 實例。這里使用了 .NET 的依賴注入機制,配合 ActivatorUtilities.CreateInstance 實現(xiàn)動態(tài)創(chuàng)建。工廠模式的好處在于,新增一個 Provider 時只需要添加創(chuàng)建邏輯,不用修改已有的代碼。這和寫文章差不多,想加個新章節(jié),就加個新章節(jié),不用把前面的都重寫一遍。
Provider 實現(xiàn)層 是真正干活的地方。CodebuddyCliProvider 實現(xiàn)了 IAIProvider 接口,提供 ExecuteAsync(非流式)和 StreamAsync(流式)兩種調(diào)用方式。
ACP 基礎設施層 則是通信的底層支撐。這一層處理所有的協(xié)議細節(jié),包括進程管理、消息序列化、響應解析等。就像房子的地基,上面蓋得再漂亮,底下的東西得穩(wěn)才行。
通信機制
Stdio 傳輸模式
CodeBuddy 使用 Stdio(標準輸入輸出) 方式與外部進程通信。啟動命令很簡單:
codebuddy --acp
然后通過標準輸入輸出進行 JSON-RPC 消息交換。這種方式的優(yōu)勢在于:
- 啟動迅速:本地進程通信沒有網(wǎng)絡延遲
- 配置簡單:只需要指定可執(zhí)行文件路徑
- 環(huán)境隔離:每個會話獨立進程,互不影響
通信過程中支持環(huán)境變量注入,常用的包括:
CODEBUDDY_API_KEY:API 密鑰認證CODEBUDDY_INTERNET_ENVIRONMENT:網(wǎng)絡環(huán)境配置
這就像,人與人之間的溝通,找個方便的方式,才能說得上話。
消息協(xié)議
ACP 基于 JSON-RPC 2.0 協(xié)議,消息格式大概是醬紫的:
// 請求消息
{
"jsonrpc": "2.0",
"id": 1,
"method": "agent/prompt",
"params": {
"prompt": "幫我寫一個排序算法",
"sessionId": "session-123"
}
}
// 響應消息
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": "這里是 AI 的回復..."
}
}實際實現(xiàn)中,我們把這些協(xié)議細節(jié)都封裝好了,上層業(yè)務代碼只需要關注 prompt 和 response 就行。這也罷了,封裝得好,后面的人用起來就舒服點。
核心實現(xiàn)
1. Provider 契約恢復
首先在枚舉文件中恢復 CodeBuddy 類型:
// PCode.Models/AIProviderType.cs
public enum AIProviderType
{
ClaudeCodeCli = 0,
CodexCli = 1,
GitHubCopilot = 2,
CodebuddyCli = 3, // 恢復這個枚舉值
OpenCodeCli = 4,
IFlowCli = 5,
}然后在擴展方法中添加字符串映射,這樣配置文件就可以用字符串指定 Provider:
// AIProviderTypeExtensions.cs
private static readonly Dictionary<string, AIProviderType> _typeMap = new(
StringComparer.OrdinalIgnoreCase)
{
["CodebuddyCli"] = AIProviderType.CodebuddyCli,
["Codebuddy"] = AIProviderType.CodebuddyCli,
["codebuddy"] = AIProviderType.CodebuddyCli,
// ... 其他 provider 的映射
};2. Provider 工廠集成
在工廠類中添加 CodeBuddy 的創(chuàng)建分支:
// AIProviderFactory.cs
private IAIProvider? CreateProvider(AIProviderType providerType, ProviderConfiguration config)
{
return providerType switch
{
AIProviderType.CodebuddyCli =>
ActivatorUtilities.CreateInstance<CodebuddyCliProvider>(
_serviceProvider,
Options.Create(config)),
// ... 其他 provider
_ => throw new NotSupportedException($"Provider {providerType} not supported")
};
}這里用了依賴注入的 ActivatorUtilities,它會自動處理構造函數(shù)的參數(shù)注入,非常方便。這也罷了,.NET 的東西,用對了就是省心。
3. 完整的 Provider 實現(xiàn)
下面是 CodebuddyCliProvider 的核心實現(xiàn),包含了流式和非流式兩種調(diào)用方式:
public class CodebuddyCliProvider : IAIProvider
{
private readonly ILogger<CodebuddyCliProvider> _logger;
private readonly IACPSessionManager _sessionManager;
private readonly ProviderConfiguration _config;
public string Name => "CodebuddyCli";
public bool SupportsStreaming => true;
public ProviderCapabilities Capabilities { get; }
public CodebuddyCliProvider(
ILogger<CodebuddyCliProvider> logger,
IACPSessionManager sessionManager,
IOptions<ProviderConfiguration> config)
{
_logger = logger;
_sessionManager = sessionManager;
_config = config.Value;
// 定義當前 Provider 的能力
Capabilities = new ProviderCapabilities
{
SupportsStreaming = true,
SupportsTools = true,
SupportsSystemMessages = true,
SupportsArtifacts = false,
MaxTokens = 8192
};
}
// 非流式調(diào)用:等所有結果一起返回
public async Task<AIResponse> ExecuteAsync(
AIRequest request,
CancellationToken cancellationToken = default)
{
// 為請求創(chuàng)建獨立會話
var session = await _sessionManager.CreateSessionAsync(
"CodebuddyCli",
request.WorkingDirectory,
cancellationToken,
request.SessionId);
try
{
var fullPrompt = BuildPrompt(request);
await session.SendPromptAsync(fullPrompt, cancellationToken);
var responseBuilder = new StringBuilder();
var toolCalls = new List<AIToolCall>();
// 收集所有響應塊
await foreach (var chunk in StreamFromSession(session, cancellationToken))
{
if (!string.IsNullOrEmpty(chunk.Content))
{
responseBuilder.Append(chunk.Content);
}
// 處理工具調(diào)用...
}
return new AIResponse
{
Content = AIResultContentSanitizer.SanitizeResultContent(
responseBuilder.ToString()),
ToolCalls = toolCalls,
Provider = Name,
Model = string.Empty
};
}
finally
{
// 釋放會話資源
await session.DisposeAsync();
}
}
// 流式調(diào)用:實時返回響應塊
public async IAsyncEnumerable<AIStreamingChunk> StreamAsync(
AIRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var session = await _sessionManager.CreateSessionAsync(
"CodebuddyCli",
request.WorkingDirectory,
cancellationToken);
try
{
var fullPrompt = BuildPrompt(request);
await session.SendPromptAsync(fullPrompt, cancellationToken);
await foreach (var chunk in StreamFromSession(session, cancellationToken))
{
yield return chunk;
}
}
finally
{
await session.DisposeAsync();
}
}
private async IAsyncEnumerable<AIStreamingChunk> StreamFromSession(
IACPSession session,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
// 遍歷會話中的所有更新
await foreach (var notification in session.ReceiveUpdatesAsync(cancellationToken))
{
switch (notification.Update)
{
case AgentMessageChunkSessionUpdate agentMessage:
// 處理文本內(nèi)容塊
if (agentMessage.Content is AcpImp.TextContentBlock textContent)
{
yield return new AIStreamingChunk
{
Content = textContent.Text,
Type = StreamingChunkType.ContentDelta,
IsComplete = false
};
}
break;
case ToolCallSessionUpdate toolCall:
// 處理工具調(diào)用
yield return new AIStreamingChunk
{
Content = string.Empty,
Type = StreamingChunkType.ToolCallDelta,
ToolCallDelta = new AIToolCallDelta
{
Id = toolCall.ToolCallId,
Name = toolCall.Kind.ToString(),
Arguments = toolCall.RawInput?.ToString()
}
};
break;
case AcpImp.PromptCompletedSessionUpdate:
// 響應完成
yield break;
}
}
}
// 構建完整的提示詞
private string BuildPrompt(AIRequest request, string? embeddedCommandPrompt = null)
{
var sb = new StringBuilder();
// 嵌入命令提示詞(如果有)
if (!string.IsNullOrEmpty(embeddedCommandPrompt))
{
sb.AppendLine(embeddedCommandPrompt);
sb.AppendLine();
}
// 系統(tǒng)消息
if (!string.IsNullOrEmpty(request.SystemMessage))
{
sb.AppendLine(request.SystemMessage);
sb.AppendLine();
}
// 用戶 prompt
sb.Append(request.Prompt);
return sb.ToString();
}
}
這段代碼有幾個關鍵點:
- 會話管理:每個請求創(chuàng)建獨立會話,請求完成后釋放資源。這是坑踩出來的經(jīng)驗——如果會話復用做得不好,很容易出現(xiàn)狀態(tài)污染的問題。畢竟,用過就得收拾干凈,不然下次用的人就麻煩了。
- 流式處理:
IAsyncEnumerable讓響應可以邊生成邊返回,不用等全部內(nèi)容生成完。這對于長文本場景特別重要,用戶體驗會好很多。就像,等結果的人也不想一直干等著不是。 - 工具調(diào)用:CodeBuddy 支持工具調(diào)用(Function Calling),通過
ToolCallSessionUpdate處理。這個能力對于復雜的代碼編輯任務很關鍵。 - 內(nèi)容過濾:使用
AIResultContentSanitizer過濾 Think 塊內(nèi)容,保持輸出干凈。
4. 依賴注入配置
在模塊注冊中添加相關服務:
// PCodeClaudeHelperModule.cs
public void ConfigureModule(IServiceCollection context)
{
// 注冊 Provider
context.Services.AddTransient<CodebuddyCliProvider>();
// 注冊 ACP 基礎設施
context.Services.AddSingleton<IACPSessionManager, ACPSessionManager>();
context.Services.AddSingleton<IAcpPlatformConfigurationResolver, AcpPlatformConfigurationResolver>();
context.Services.AddSingleton<IAIRequestToAcpMapper, AIRequestToAcpMapper>();
context.Services.AddSingleton<IAcpToAIResponseMapper, AcpToAIResponseMapper>();
}
配置示例
配置文件
在 appsettings.json 中添加 CodeBuddy 相關配置:
AI:
# 默認使用的 Provider
DefaultProvider: "CodebuddyCli"
# Provider 配置
Providers:
CodebuddyCli:
Type: "CodebuddyCli"
WorkingDirectory: "C:/projects/my-app"
ExecutablePath: "C:/tools/codebuddy.cmd"
# 平臺相關配置
PlatformConfigurations:
CodebuddyCli:
ExecutablePath: "C:/tools/codebuddy.cmd"
Arguments: "--acp"
StartupTimeoutMs: 5000
EnvironmentVariables:
CODEBUDDY_API_KEY: "${CODEBUDDY_API_KEY}"
CODEBUDDY_INTERNET_ENVIRONMENT: "production"配置模型
對應的配置模型定義:
public class CodebuddyPlatformConfiguration : IAcpPlatformConfiguration
{
public string ProviderName => "CodebuddyCli";
public AcpTransportType TransportType => AcpTransportType.Stdio;
public string ExecutablePath { get; set; } = "codebuddy";
public string Arguments { get; set; } = "--acp";
public int StartupTimeoutMs { get; set; } = 5000;
public Dictionary<string, string?>? EnvironmentVariables { get; set; }
}實踐經(jīng)驗總結
踩坑記錄
我們在實現(xiàn)過程中遇到了幾個典型的坑,分享出來讓大家少走彎路。畢竟,別人的坑,自己能避開就是好事:
- 會話泄漏問題:一開始沒有正確釋放會話,導致進程資源耗盡。解決方法是使用
try-finally確保每次請求都會釋放資源。這也罷了,用過的東西得放回去,不然后面的人用什么。 - 環(huán)境變量傳遞:Windows 和 Linux 的環(huán)境變量語法不同,后來統(tǒng)一使用
Dictionary<string, string?>來處理??缙脚_這種事,一開始就統(tǒng)一規(guī)范,后面就省心。 - 超時配置:CLI 啟動需要時間,設置了 5 秒的啟動超時,避免快速請求失敗。凡事都得有個度,太急了反而辦不成事。
- 編碼問題:Windows 上默認編碼可能導致中文亂碼,在啟動進程時顯式指定 UTF-8 編碼。中文顯示不出來,那多難受。
性能優(yōu)化
- 會話池:對于頻繁的短請求,可以考慮實現(xiàn)會話池來復用進程
- 連接緩存:工廠類已經(jīng)支持 Provider 實例緩存
- 異步優(yōu)先:全程使用異步編程,避免阻塞線程
性能這種事,能優(yōu)化就優(yōu)化,畢竟用戶等的越久,體驗就越差。
總結
本文詳細介紹了 C# 后端集成 CodeBuddy CLI 的完整方案,涵蓋了從架構設計到具體實現(xiàn)的全過程。通過分層架構設計,我們將協(xié)議細節(jié)與業(yè)務邏輯分離,使得代碼更加清晰和可維護。
核心要點回顧:
- 采用 Provider 契約層、工廠層、實現(xiàn)層、基礎設施層的分層架構
- 使用 JSON-RPC over Stdio 方式進行進程間通信
- 通過依賴注入實現(xiàn)靈活的配置和擴展
- 提供流式和非流式兩種調(diào)用方式
這套方案不僅適用于 CodeBuddy,添加新的 AI Provider 也遵循同樣的模式。如果你也在做類似的多 AI Provider 集成,希望這篇文章能給你一些參考。其實,寫文章和寫代碼一樣,分享出來,能幫到別人就算沒白寫。
以上就是C#后端集成CodeBuddy CLI的完整方案的詳細內(nèi)容,更多關于C#集成CodeBuddy CLI的資料請關注腳本之家其它相關文章!
相關文章
C#微信公眾號開發(fā)之使用MessageHandler簡化消息處理流程
這篇文章介紹了C#微信公眾號開發(fā)之使用MessageHandler簡化消息處理流程,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
C#基于DBContext(EF)實現(xiàn)通用增刪改查的REST方法實例
這篇文章主要介紹了C#基于DBContext(EF)實現(xiàn)通用增刪改查的REST方法實例,是C#程序設計中非常實用的技巧,需要的朋友可以參考下2014-10-10
C#簡單實現(xiàn)表達式目錄樹(Expression)
表達式目錄樹以數(shù)據(jù)形式表示語言級別代碼。數(shù)據(jù)存儲在樹形結構中。表達式目錄樹中的每個節(jié)點都表示一個表達式。這篇文章給大家介紹C#簡單實現(xiàn)表達式目錄樹(Expression),需要的朋友參考下吧2017-11-11
DevExpress實現(xiàn)GridView當無數(shù)據(jù)行時提示消息
這篇文章主要介紹了DevExpress實現(xiàn)GridView當無數(shù)據(jù)行時提示消息,需要的朋友可以參考下2014-08-08

