Forge
csharpdeeb25a2
1#nullable enable
2using CascadeIDE.Features.Agent.Harness;
3using CascadeIDE.Models;
4using CascadeIDE.Services;
5using CascadeIDE.Services.Fm;
6using CommunityToolkit.Mvvm.ComponentModel;
7
8namespace CascadeIDE.Features.Chat;
9
10/// <summary>FM token usage: catalog, capture, Intercom chrome subtitle (ADR 0166 P1 P0).</summary>
11public partial class ChatPanelViewModel
12{
13 private readonly FmModelCatalog _fmModelCatalog = new();
14 private FmTurnUsage? _fmSessionUsage;
15 private FmTurnUsage? _fmLastTurnUsage;
16 private int? _fmMaxModelLen;
17 private Func<FmOpenAiCompatibleCredentials?>? _getFmOpenAiCredentials;
18
19 [ObservableProperty]
20 private string _fmUsageSubtitle = "";
21
22 public void SetFmOpenAiCredentialsAccessor(Func<FmOpenAiCompatibleCredentials?> getCredentials) =>
23 _getFmOpenAiCredentials = getCredentials;
24
25 private async Task RecordFmTurnUsageAsync(FmTurnUsage? usage, CancellationToken cancellationToken = default)
26 {
27 if (usage is null)
28 return;
29
30 _fmLastTurnUsage = usage;
31 _fmSessionUsage = _fmSessionUsage is null ? usage : _fmSessionUsage.Add(usage);
32
33 await EnsureFmModelContextAsync(cancellationToken).ConfigureAwait(false);
34 RefreshFmUsageSubtitle();
35
36 if (_fmMaxModelLen is > 0 && usage.PromptTokens > 0)
37 {
38 HarnessContextPressureResult usagePressure = HarnessContextPressureResult.None;
39 await UiScheduler.Default.InvokeAsync(() =>
40 {
41 usagePressure = Harness.OnContextUsagePct(usage.PromptTokens, _fmMaxModelLen.Value);
42 }).ConfigureAwait(false);
43
44 if (usagePressure.InjectPreCompact && !string.IsNullOrWhiteSpace(usagePressure.PreCompactUserMessage))
45 await InjectHarnessUserMessageAsync(usagePressure.PreCompactUserMessage!).ConfigureAwait(false);
46 }
47 }
48
49 private async Task EnsureFmModelContextAsync(CancellationToken cancellationToken)
50 {
51 if (_fmMaxModelLen is > 0)
52 return;
53
54 var creds = _getFmOpenAiCredentials?.Invoke();
55 if (creds is null)
56 return;
57
58 var info = await _fmModelCatalog.TryResolveModelAsync(
59 creds.BaseUrl,
60 creds.ApiKey,
61 creds.ModelId,
62 cancellationToken).ConfigureAwait(false);
63 if (info?.MaxModelLen is > 0)
64 _fmMaxModelLen = info.MaxModelLen;
65 }
66
67 private void RefreshFmUsageSubtitle()
68 {
69 var harness = _getCascadeSettings?.Invoke()?.Agent.Harness;
70 var warnPct = harness?.ContextWarnPct ?? 75;
71 var text = FmUsagePresentation.FormatSubtitle(
72 _fmLastTurnUsage,
73 _fmSessionUsage,
74 _fmMaxModelLen,
75 warnPct);
76
77 UiScheduler.Default.Post(() => FmUsageSubtitle = text);
78 }
79}
80
81/// <summary>OpenAI-compatible FM endpoint + key для catalog/usage.</summary>
82public sealed record FmOpenAiCompatibleCredentials(string BaseUrl, string ApiKey, string ModelId);
83
View only · write via MCP/CIDE