claude-code 기준 provider 호환성과 compact 후속 흐름을 보강한다

- OpenAI 호환 tool_choice 400 오류에 대한 일반 fallback을 추가하고 Qwen·LLaMA·DeepSeek 계열 vLLM의 도구 호출 프로파일을 더 보수적으로 조정

- compact 이후 branch context와 최근 tool state를 query view에 재주입하고 UI 표현 수준에 맞춰 compact 카드/컨텍스트 사용 팝업/최종 보고 밀도를 세분화

- README와 DEVELOPMENT 문서 이력을 2026-04-12 23:45 KST 기준으로 갱신

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 경고 0 / 오류 0
This commit is contained in:
2026-04-12 22:32:40 +09:00
parent 58b798d3e4
commit da11029284
7 changed files with 215 additions and 27 deletions

View File

@@ -6,7 +6,7 @@ namespace AxCopilot.Views;
public partial class ChatWindow
{
// 토큰 추정 캐시: 메시지 수나 대화 ID가 달라질 때만 재계산
// 메시지 개수와 대화 ID가 바뀔 때만 재계산한다.
private int _cachedMessageTokens;
private int _cachedMessageCountForTokens = -1;
private string? _cachedConvIdForTokens;
@@ -28,6 +28,7 @@ public partial class ChatWindow
}
var llm = _settings.Settings.Llm;
var expressionLevel = GetAgentUiExpressionLevel();
var maxContextTokens = Math.Clamp(llm.MaxContextTokens, 1024, 1_000_000);
var triggerPercent = Math.Clamp(llm.ContextCompactTriggerPercent, 10, 95);
var triggerRatio = triggerPercent / 100.0;
@@ -45,6 +46,7 @@ public partial class ChatWindow
_cachedConvIdForTokens = convId;
_cachedMessageCountForTokens = msgCount;
}
messageTokens = _cachedMessageTokens;
}
@@ -52,7 +54,7 @@ public partial class ChatWindow
var draftTokens = string.IsNullOrWhiteSpace(draftText) ? 0 : Services.TokenEstimator.Estimate(draftText) + 4;
var hasAnyMessages = messageTokens > 0 || _isStreaming;
int baseOverhead = 0;
var baseOverhead = 0;
if (hasAnyMessages)
{
var sysPromptLen = _llm?.SystemPrompt?.Length ?? 0;
@@ -118,11 +120,21 @@ public partial class ChatWindow
if (TokenUsagePopupUsage != null)
TokenUsagePopupUsage.Text = $"{Services.TokenEstimator.Format(currentTokens)}/{Services.TokenEstimator.Format(maxContextTokens)}";
if (TokenUsagePopupDetail != null)
TokenUsagePopupDetail.Text = detailText;
TokenUsagePopupDetail.Text = expressionLevel == "simple"
? detailText
: $"{detailText} · 임계 {triggerPercent}%";
if (TokenUsagePopupCompact != null)
{
TokenUsagePopupCompact.Text = _sessionCompactionCount > 0
? $"누적 압축 {_sessionCompactionCount}회 · 절감 {FormatTokenCount(_sessionCompactionSavedTokens)}"
: "AX Agent가 컨텍스트를 자동 관리합니다";
? expressionLevel switch
{
"simple" => $"압축 {_sessionCompactionCount}회 · {FormatTokenCount(_sessionCompactionSavedTokens)} 절감",
_ => $"누적 압축 {_sessionCompactionCount}회 · 절감 {FormatTokenCount(_sessionCompactionSavedTokens)}"
}
: expressionLevel == "rich"
? "AX Agent가 컨텍스트를 자동 관리합니다"
: "컨텍스트 자동 관리";
}
TokenUsageCard.ToolTip = null;

View File

@@ -350,6 +350,7 @@ public partial class ChatWindow
private Border CreateCompactionMetaCard(ChatMessage message, Brush primaryText, Brush secondaryText, Brush hintBg, Brush borderBrush, Brush accentBrush)
{
var expressionLevel = GetAgentUiExpressionLevel();
var icon = "\uE9CE";
var title = message.MetaKind switch
{
@@ -399,20 +400,40 @@ public partial class ChatWindow
};
var detailBits = new List<string>();
if (message.AttachedFiles?.Count > 0)
if (message.AttachedFiles?.Count > 0 && expressionLevel != "simple")
detailBits.Add($"파일 {message.AttachedFiles.Count}개");
var compactDetail = detailBits.Count > 0
? $"{summary} · {string.Join(", ", detailBits)}"
: summary;
var compactDetail = expressionLevel switch
{
"simple" => summary,
_ when detailBits.Count > 0 => $"{summary} · {string.Join(", ", detailBits)}",
_ => summary
};
stack.Children.Add(new TextBlock
{
Text = compactDetail,
FontSize = 10.5,
FontSize = expressionLevel == "simple" ? 10 : 10.5,
Foreground = secondaryText,
TextWrapping = TextWrapping.Wrap,
});
if (expressionLevel == "rich" && !string.IsNullOrWhiteSpace(message.Content))
{
var preview = message.Content.Trim();
if (preview.Length > 120)
preview = preview[..120] + "...";
stack.Children.Add(new TextBlock
{
Text = preview,
FontSize = 10,
Foreground = secondaryText,
Opacity = 0.88,
Margin = new Thickness(0, 6, 0, 0),
TextWrapping = TextWrapping.Wrap,
});
}
wrapper.Child = stack;
return wrapper;
}