-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathOpenAiMeaiStreamAdapter.cs
More file actions
105 lines (97 loc) · 3.71 KB
/
Copy pathOpenAiMeaiStreamAdapter.cs
File metadata and controls
105 lines (97 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
using System.Runtime.CompilerServices;
using Microsoft.Extensions.AI;
using SharpClaw.Code.Infrastructure.Abstractions;
using SharpClaw.Code.Providers.Models;
using SharpClaw.Code.Protocol.Models;
namespace SharpClaw.Code.Providers.Internal;
/// <summary>
/// Converts Microsoft.Extensions.AI streaming chat updates into SharpClaw <see cref="ProviderEvent"/> values.
/// </summary>
internal static class OpenAiMeaiStreamAdapter
{
/// <summary>
/// Adapts an MEAI streaming sequence to provider events (text deltas followed by a single completed event).
/// </summary>
public static async IAsyncEnumerable<ProviderEvent> AdaptAsync(
IAsyncEnumerable<ChatResponseUpdate> updates,
string requestId,
ISystemClock clock,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var completed = false;
IAsyncEnumerator<ChatResponseUpdate>? enumerator = null;
try
{
enumerator = updates.GetAsyncEnumerator(cancellationToken);
while (true)
{
bool moved;
string? streamError = null;
try
{
moved = await enumerator.MoveNextAsync().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
cancellationToken.ThrowIfCancellationRequested();
streamError = ProviderStreamFailureClassifier.Describe(ex);
moved = false;
}
if (streamError is not null)
{
yield return ProviderStreamEventFactory.Failed(requestId, clock, streamError);
yield break;
}
if (!moved)
{
break;
}
var update = enumerator.Current;
var text = update.Text;
if (!string.IsNullOrEmpty(text))
{
yield return ProviderStreamEventFactory.Delta(requestId, clock, text);
}
if (update.Contents is not null)
{
foreach (var content in update.Contents)
{
if (content is FunctionCallContent functionCall)
{
var argsJson = functionCall.Arguments is not null
? System.Text.Json.JsonSerializer.Serialize(functionCall.Arguments)
: "{}";
yield return ProviderStreamEventFactory.ToolUse(
requestId, clock,
functionCall.CallId ?? $"call-{Guid.NewGuid():N}",
functionCall.Name ?? "unknown",
argsJson);
}
}
}
if (update.FinishReason is { } finish && !string.IsNullOrEmpty(finish.Value))
{
var usage = ProviderStreamEventFactory.TryUsageFromUpdate(update);
yield return ProviderStreamEventFactory.Completed(requestId, clock, usage);
completed = true;
yield break;
}
}
}
finally
{
if (enumerator is not null)
{
await enumerator.DisposeAsync().ConfigureAwait(false);
}
}
if (!completed)
{
yield return ProviderStreamEventFactory.Completed(requestId, clock, null);
}
}
}