Forge
csharpdeeb25a2
1using CascadeIDE.Models.AgentChat;
2using CascadeIDE.Models.Intercom;
3
4namespace CascadeIDE.Features.Intercom.Transport;
5
6/// <summary>Правила fan-out на team transport (ADR 0144 §5, фаза 3.1).</summary>
7public static class IntercomTransportPublishRules
8{
9 public static bool ShouldPublish(string eventKind, string payloadJson, bool syncAgentChannelMessages = true)
10 {
11 if (IsNeverSynced(eventKind))
12 return false;
13
14 return eventKind switch
15 {
16 ChatHistoryEventKind.MessageAdded or ChatHistoryEventKind.MessageCompleted
17 or ChatHistoryEventKind.MessageEdited => IsChannelMessagePayload(payloadJson, syncAgentChannelMessages),
18 ChatHistoryEventKind.ThreadForked or ChatHistoryEventKind.MessageRangeRelated => true,
19 _ => false,
20 };
21 }
22
23 public static string ToWireEventKind(string localKind) =>
24 localKind switch
25 {
26 ChatHistoryEventKind.MessageCompleted => "message_completed",
27 ChatHistoryEventKind.MessageEdited => "message_edited",
28 ChatHistoryEventKind.ThreadForked => "thread_forked",
29 ChatHistoryEventKind.MessageRangeRelated => "message_range_related",
30 _ => "message_added",
31 };
32
33 public static string ResolveWireSenderRole(string payloadJson, string localKind)
34 {
35 if (localKind is ChatHistoryEventKind.ThreadForked or ChatHistoryEventKind.MessageRangeRelated)
36 return "human";
37
38 if (!TryDeserializeMessage(payloadJson, out var payload))
39 return "human";
40
41 return payload.Role.ToLowerInvariant() switch
42 {
43 "assistant" => "agent",
44 "system" => "system",
45 _ => "human",
46 };
47 }
48
49 public static string? TryExtractThreadId(string eventKind, string payloadJson)
50 {
51 if (eventKind == ChatHistoryEventKind.ThreadForked)
52 {
53 try
54 {
55 var fork = System.Text.Json.JsonSerializer.Deserialize<ChatHistoryThreadForkedPayload>(
56 payloadJson,
57 IntercomTransportJson.Web);
58 return fork?.NewThreadId;
59 }
60 catch (System.Text.Json.JsonException)
61 {
62 return null;
63 }
64 }
65
66 if (eventKind == ChatHistoryEventKind.MessageRangeRelated)
67 {
68 try
69 {
70 var rel = System.Text.Json.JsonSerializer.Deserialize<ChatHistoryMessageRangeRelatedPayload>(
71 payloadJson,
72 IntercomTransportJson.Web);
73 return rel?.ThreadId;
74 }
75 catch (System.Text.Json.JsonException)
76 {
77 return null;
78 }
79 }
80
81 if (!TryDeserializeMessage(payloadJson, out var msg))
82 return null;
83
84 return msg.ThreadId;
85 }
86
87 public static string TopicTitleForThread(string threadId) =>
88 string.IsNullOrWhiteSpace(threadId) ? "general" : $"thread-{threadId}";
89
90 private static bool IsNeverSynced(string eventKind) =>
91 string.Equals(eventKind, ChatHistoryEventKind.MessageStreamDelta, StringComparison.Ordinal)
92 || string.Equals(eventKind, ChatHistoryEventKind.ClarificationBatchOpened, StringComparison.Ordinal)
93 || string.Equals(eventKind, ChatHistoryEventKind.ClarificationAnswerSubmitted, StringComparison.Ordinal);
94
95 private static bool IsChannelMessagePayload(string payloadJson, bool syncAgentChannelMessages)
96 {
97 if (!TryDeserializeMessage(payloadJson, out var payload))
98 return false;
99
100 if (payload.Audience is IntercomMessageAudience.SelfOnly)
101 return false;
102
103 if (!string.IsNullOrWhiteSpace(payload.SlashCommandPath))
104 return false;
105
106 if (!syncAgentChannelMessages
107 && string.Equals(payload.Role, "assistant", StringComparison.OrdinalIgnoreCase))
108 return false;
109
110 return true;
111 }
112
113 private static bool TryDeserializeMessage(string payloadJson, out ChatHistoryMessagePayload payload)
114 {
115 payload = default!;
116 try
117 {
118 var p = System.Text.Json.JsonSerializer.Deserialize<ChatHistoryMessagePayload>(
119 payloadJson,
120 IntercomTransportJson.Web);
121 if (p is null)
122 return false;
123 payload = p;
124 return true;
125 }
126 catch (System.Text.Json.JsonException)
127 {
128 return false;
129 }
130 }
131}
132
View only · write via MCP/CIDE