127 {
128 var request = new ChatRequest(
129 Model: "gpt-oss",
130 Messages: new[]
131 {
132 new ChatMessage("system", systemPrompt),
133 new ChatMessage("user", userPrompt)
134 });
135
136 var json = JsonSerializer.Serialize(request, _jsonOptions);
137 using var content = new StringContent(json, Encoding.UTF8, "application/json");
138
139 using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30));
140 using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
141 cancellationToken, timeoutCts.Token);
142
143 HttpResponseMessage httpResponse;
144 try
145 {
146 httpResponse = await _httpClient.PostAsync(
147 "/v1/chat/completions",
148 content,
149 linkedCts.Token)
150 .ConfigureAwait(false);
151 }
152 catch (Exception e) when (!cancellationToken.IsCancellationRequested)
153 {
154 throw new Exception();
155 }
156
157 if (!httpResponse.IsSuccessStatusCode)
158 {
159 var rawError = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
160 throw new HttpRequestException(
161 $"Server returned {(int)httpResponse.StatusCode} – {httpResponse.ReasonPhrase}. Body: {rawError}");
162 }
163
164 var chatResponse = await httpResponse.Content
165 .ReadFromJsonAsync<ChatResponse>(_jsonOptions, linkedCts.Token)
166 .ConfigureAwait(false);
167
168 var firstChoice = chatResponse?.Choices?.Count > 0 ? chatResponse.Choices[0] : null;
169 if (firstChoice?.Message?.Content == null)
170 throw new InvalidOperationException("The response does not contain a usable message.");
171
172 return firstChoice.Message.Content;
173 }