620 lines
No EOL
21 KiB
C#
620 lines
No EOL
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using Newtonsoft.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
|
|
namespace GumYum.NPC {
|
|
/// <summary>
|
|
/// GumYum Game Client for Unity (API Key Authentication Only)
|
|
///
|
|
/// Streamlined client for game integration using API keys only.
|
|
/// Perfect for embedding in shipped games with just the essential features.
|
|
///
|
|
/// This client does NOT support username/password authentication.
|
|
/// Use API keys obtained from the GumYum dashboard.
|
|
///
|
|
/// Features:
|
|
/// - Character spawning and dialogue
|
|
/// - API key authentication only (no login/register)
|
|
/// - Automatic JWT token management via API key exchange
|
|
/// - Automatic retry and error handling
|
|
/// - Streaming dialogue support
|
|
/// </summary>
|
|
public class Client : IDisposable {
|
|
// Events
|
|
public event EventHandler<NPC> CharacterSpawned;
|
|
public event EventHandler<DialogueReceivedEventArgs> DialogueReceived;
|
|
public event EventHandler<string> DialogueChunkReceived;
|
|
public event EventHandler DialogueStreamStarted;
|
|
public event EventHandler<DialogueStreamEndedEventArgs> DialogueStreamEnded;
|
|
public event EventHandler<RequestCompletedEventArgs> RequestCompleted;
|
|
public event EventHandler<RequestFailedEventArgs> RequestFailed;
|
|
|
|
// Configuration
|
|
public string BaseUrl { get; set; } = "https://npc.gumyum.com";
|
|
public string ApiVersion { get; set; } = "v1";
|
|
public TimeSpan Timeout {
|
|
get; set;
|
|
} = TimeSpan.FromSeconds(75); // Higher than server's 65s timeout
|
|
public int MaxRetries { get; set; } = 3;
|
|
public bool DebugMode { get; set; } = false;
|
|
|
|
// API Credentials
|
|
private string _apiKey = "";
|
|
private string _apiSecret = "";
|
|
private string _jwtToken = "";
|
|
private string _refreshToken = "";
|
|
private DateTime _tokenExpiresAt = DateTime.MinValue;
|
|
|
|
public string ApiKey {
|
|
get => _apiKey;
|
|
set {
|
|
_apiKey = value;
|
|
if (DebugMode)
|
|
Debug.Log($"[GumYum Game] API key set: {value}");
|
|
}
|
|
}
|
|
|
|
public string ApiSecret {
|
|
get => _apiSecret;
|
|
set {
|
|
_apiSecret = value;
|
|
if (DebugMode)
|
|
Debug.Log("[GumYum Game] API secret set: ****");
|
|
}
|
|
}
|
|
|
|
// HTTP Client
|
|
private readonly HttpClient _httpClient;
|
|
private readonly SemaphoreSlim _authSemaphore = new SemaphoreSlim(1, 1);
|
|
private readonly JsonSerializerSettings _jsonSettings;
|
|
|
|
// Managers
|
|
public NPCManager NPCs { get; private set; }
|
|
public UniverseManager Universes { get; private set; }
|
|
public ChatManager Chat { get; private set; }
|
|
|
|
public Client(HttpClient httpClient = null) {
|
|
_httpClient = httpClient ?? new HttpClient();
|
|
_httpClient.Timeout = Timeout;
|
|
|
|
_jsonSettings = new JsonSerializerSettings {
|
|
ContractResolver = new Newtonsoft.Json.Serialization
|
|
.CamelCasePropertyNamesContractResolver(),
|
|
MissingMemberHandling = MissingMemberHandling.Ignore
|
|
};
|
|
|
|
InitializeManagers();
|
|
|
|
if (DebugMode)
|
|
Debug.Log("[GumYum Game] Client initialized");
|
|
}
|
|
|
|
private void InitializeManagers() {
|
|
NPCs = new NPCManager(this);
|
|
Universes = new UniverseManager(this);
|
|
Chat = new ChatManager(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if authenticated
|
|
/// </summary>
|
|
public bool IsReady() =>
|
|
!string.IsNullOrEmpty(_jwtToken) && DateTime.UtcNow < _tokenExpiresAt;
|
|
|
|
/// <summary>
|
|
/// Check if token needs refresh (within 60 seconds of expiry)
|
|
/// </summary>
|
|
private bool NeedsRefresh() =>
|
|
!string.IsNullOrEmpty(_jwtToken) && DateTime.UtcNow
|
|
> _tokenExpiresAt.AddSeconds(-60);
|
|
|
|
/// <summary>
|
|
/// Get full API URL
|
|
/// </summary>
|
|
private string GetApiUrl(string endpoint) =>
|
|
$"{BaseUrl}/{ApiVersion}/{endpoint.TrimStart('/')}";
|
|
|
|
/// <summary>
|
|
/// Get request headers
|
|
/// </summary>
|
|
private void SetRequestHeaders(HttpRequestMessage request) {
|
|
request.Headers.Clear();
|
|
request.Headers.Accept.Add(
|
|
new MediaTypeWithQualityHeaderValue("application/json"));
|
|
request.Headers.UserAgent.ParseAdd("GumYum-Unity-SDK/1.0.0");
|
|
|
|
// Add API key header if we have credentials
|
|
if (!string.IsNullOrEmpty(_apiKey) && !string.IsNullOrEmpty(_apiSecret)) {
|
|
request.Headers.Add("X-API-Key", $"{_apiKey}:{_apiSecret}");
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_jwtToken)) {
|
|
request.Headers.Authorization =
|
|
new AuthenticationHeaderValue("Bearer", _jwtToken);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make API request
|
|
/// </summary>
|
|
public async Task<T>
|
|
RequestAsync<T>(HttpMethod method, string endpoint, object data = null,
|
|
Dictionary<string, string> queryParams = null,
|
|
CancellationToken cancellationToken = default) {
|
|
// Ensure we're authenticated
|
|
if (!string.IsNullOrEmpty(_apiKey) && !string.IsNullOrEmpty(_apiSecret) &&
|
|
!IsReady()) {
|
|
await ExchangeApiKeyForJwtAsync(cancellationToken);
|
|
}
|
|
|
|
// Check if we need to refresh token
|
|
if (NeedsRefresh() && !string.IsNullOrEmpty(_refreshToken)) {
|
|
await RefreshJwtTokenAsync(cancellationToken);
|
|
}
|
|
|
|
return await ExecuteRequestAsync<T>(method, endpoint, data, queryParams, 0,
|
|
cancellationToken);
|
|
}
|
|
|
|
private async Task<T>
|
|
ExecuteRequestAsync<T>(HttpMethod method, string endpoint, object data,
|
|
Dictionary<string, string> queryParams, int retryCount,
|
|
CancellationToken cancellationToken) {
|
|
var url = GetApiUrl(endpoint);
|
|
|
|
// Add query parameters
|
|
if (queryParams?.Count > 0) {
|
|
var queryString = string.Join(
|
|
"&", queryParams.Select(
|
|
kvp => $"{kvp.Key}={Uri.EscapeDataString(kvp.Value)}"));
|
|
url += "?" + queryString;
|
|
}
|
|
|
|
using var request = new HttpRequestMessage(method, url);
|
|
SetRequestHeaders(request);
|
|
|
|
if (data != null &&
|
|
(method == HttpMethod.Post || method == HttpMethod.Put)) {
|
|
var json = JsonConvert.SerializeObject(data, _jsonSettings);
|
|
request.Content =
|
|
new StringContent(json, Encoding.UTF8, "application/json");
|
|
}
|
|
|
|
if (DebugMode) {
|
|
Debug.Log($"[GumYum Game] {method} {endpoint}");
|
|
Debug.Log($"[GumYum Game] Full URL: {url}");
|
|
}
|
|
|
|
try {
|
|
var response = await _httpClient.SendAsync(request, cancellationToken);
|
|
var responseContent = await response.Content.ReadAsStringAsync();
|
|
|
|
if (DebugMode) {
|
|
Debug.Log($"[GumYum Game] Response code: {(int)response.StatusCode}");
|
|
if (!response.IsSuccessStatusCode) {
|
|
Debug.Log(
|
|
$"[GumYum Game] Response body: {responseContent.Substring(0, Math.Min(200, responseContent.Length))}");
|
|
}
|
|
}
|
|
|
|
if (response.IsSuccessStatusCode) {
|
|
var result =
|
|
JsonConvert.DeserializeObject<T>(responseContent, _jsonSettings);
|
|
RequestCompleted?.Invoke(
|
|
this, new RequestCompletedEventArgs { Endpoint = endpoint,
|
|
Data = result });
|
|
return result;
|
|
}
|
|
|
|
// Handle errors
|
|
var errorData = JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
responseContent, _jsonSettings);
|
|
var errorMsg = errorData?.GetValueOrDefault("message")?.ToString() ??
|
|
errorData?.GetValueOrDefault("error")?.ToString() ??
|
|
"Unknown error";
|
|
|
|
// Handle 403 - try to refresh token only for expired tokens
|
|
if (response.StatusCode == System.Net.HttpStatusCode.Forbidden &&
|
|
!string.IsNullOrEmpty(_refreshToken) && retryCount == 0) {
|
|
var errorMessage =
|
|
errorData?.GetValueOrDefault("error")?.ToString() ?? "";
|
|
if (errorMessage.ToLower().Contains("expired") &&
|
|
errorMessage.ToLower().Contains("token")) {
|
|
if (DebugMode)
|
|
Debug.Log("[GumYum Game] Got 403 with expired token, attempting " +
|
|
"token refresh...");
|
|
|
|
await RefreshJwtTokenAsync(cancellationToken);
|
|
return await ExecuteRequestAsync<T>(method, endpoint, data,
|
|
queryParams, retryCount + 1,
|
|
cancellationToken);
|
|
}
|
|
}
|
|
|
|
// For 401 errors (like revoked API keys), don't retry - just fail
|
|
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) {
|
|
if (DebugMode)
|
|
Debug.Log("[GumYum Game] Got 401 Unauthorized - not retrying");
|
|
|
|
throw new HttpRequestException(
|
|
$"Error {(int)response.StatusCode}: {errorMsg}");
|
|
}
|
|
|
|
// Retry logic for temporary failures
|
|
if ((int)response.StatusCode >= 500 && retryCount < MaxRetries) {
|
|
if (DebugMode)
|
|
Debug.Log(
|
|
$"[GumYum Game] Retrying request (attempt {retryCount + 1}/{MaxRetries})");
|
|
|
|
// Exponential backoff
|
|
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount + 1)),
|
|
cancellationToken);
|
|
return await ExecuteRequestAsync<T>(method, endpoint, data, queryParams,
|
|
retryCount + 1, cancellationToken);
|
|
}
|
|
|
|
throw new HttpRequestException(
|
|
$"Error {(int)response.StatusCode}: {errorMsg}");
|
|
} catch (Exception ex) {
|
|
var error = ex.Message;
|
|
if (DebugMode)
|
|
Debug.LogError($"[GumYum Game] Request failed: {endpoint} - {error}");
|
|
|
|
RequestFailed?.Invoke(
|
|
this,
|
|
new RequestFailedEventArgs { Endpoint = endpoint, Error = error });
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async
|
|
Task ExchangeApiKeyForJwtAsync(CancellationToken cancellationToken) {
|
|
if (string.IsNullOrEmpty(_apiKey) || string.IsNullOrEmpty(_apiSecret))
|
|
return;
|
|
|
|
await _authSemaphore.WaitAsync(cancellationToken);
|
|
try {
|
|
// Double-check after acquiring semaphore
|
|
if (IsReady())
|
|
return;
|
|
|
|
var data = new { public_key = _apiKey, secret_key = _apiSecret };
|
|
|
|
var url = GetApiUrl("auth/exchange");
|
|
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
|
SetRequestHeaders(request);
|
|
|
|
var json = JsonConvert.SerializeObject(data, _jsonSettings);
|
|
request.Content =
|
|
new StringContent(json, Encoding.UTF8, "application/json");
|
|
|
|
var response = await _httpClient.SendAsync(request, cancellationToken);
|
|
var responseContent = await response.Content.ReadAsStringAsync();
|
|
|
|
if (response.IsSuccessStatusCode) {
|
|
var responseData =
|
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
responseContent, _jsonSettings);
|
|
_jwtToken =
|
|
responseData?.GetValueOrDefault("access_token")?.ToString() ?? "";
|
|
_refreshToken =
|
|
responseData?.GetValueOrDefault("refresh_token")?.ToString() ?? "";
|
|
var expiresIn = 3600;
|
|
if (responseData?.TryGetValue("expires_in", out var expiresInObj) == true) {
|
|
if (expiresInObj is Newtonsoft.Json.Linq.JValue jValue) {
|
|
expiresIn = jValue.ToObject<int>();
|
|
} else {
|
|
expiresIn = Convert.ToInt32(expiresInObj);
|
|
}
|
|
}
|
|
_tokenExpiresAt = DateTime.UtcNow.AddSeconds(expiresIn);
|
|
|
|
if (DebugMode) {
|
|
Debug.Log("[GumYum Game] API key exchanged for JWT");
|
|
Debug.Log(
|
|
$"[GumYum Game] Got refresh token: {!string.IsNullOrEmpty(_refreshToken)}");
|
|
}
|
|
} else {
|
|
var errorMsg = "Invalid API keys";
|
|
try {
|
|
var errorData =
|
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
responseContent, _jsonSettings);
|
|
errorMsg = errorData?.GetValueOrDefault("error")?.ToString() ??
|
|
errorData?.GetValueOrDefault("message")?.ToString() ??
|
|
errorMsg;
|
|
} catch {
|
|
}
|
|
|
|
throw new HttpRequestException($"Authentication failed: {errorMsg}");
|
|
}
|
|
} finally {
|
|
_authSemaphore.Release();
|
|
}
|
|
}
|
|
|
|
private async Task RefreshJwtTokenAsync(CancellationToken cancellationToken) {
|
|
if (string.IsNullOrEmpty(_refreshToken))
|
|
return;
|
|
|
|
await _authSemaphore.WaitAsync(cancellationToken);
|
|
try {
|
|
// Double-check after acquiring semaphore
|
|
if (!NeedsRefresh())
|
|
return;
|
|
|
|
var data = new { refresh_token = _refreshToken };
|
|
|
|
var url = GetApiUrl("auth/refresh");
|
|
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
|
SetRequestHeaders(request);
|
|
|
|
var json = JsonConvert.SerializeObject(data, _jsonSettings);
|
|
request.Content =
|
|
new StringContent(json, Encoding.UTF8, "application/json");
|
|
|
|
if (DebugMode)
|
|
Debug.Log("[GumYum Game] Refreshing JWT token...");
|
|
|
|
var response = await _httpClient.SendAsync(request, cancellationToken);
|
|
var responseContent = await response.Content.ReadAsStringAsync();
|
|
|
|
if (response.IsSuccessStatusCode) {
|
|
var responseData =
|
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
responseContent, _jsonSettings);
|
|
_jwtToken =
|
|
responseData?.GetValueOrDefault("access_token")?.ToString() ?? "";
|
|
|
|
// Server may rotate refresh token
|
|
if (responseData?.ContainsKey("refresh_token") == true) {
|
|
_refreshToken =
|
|
responseData.GetValueOrDefault("refresh_token")?.ToString() ?? "";
|
|
}
|
|
|
|
var expiresIn = 3600;
|
|
if (responseData?.TryGetValue("expires_in", out var expiresInObj) == true) {
|
|
if (expiresInObj is Newtonsoft.Json.Linq.JValue jValue) {
|
|
expiresIn = jValue.ToObject<int>();
|
|
} else {
|
|
expiresIn = Convert.ToInt32(expiresInObj);
|
|
}
|
|
}
|
|
_tokenExpiresAt = DateTime.UtcNow.AddSeconds(expiresIn);
|
|
|
|
if (DebugMode)
|
|
Debug.Log("[GumYum Game] JWT token refreshed successfully");
|
|
} else if (response.StatusCode == System.Net.HttpStatusCode.Forbidden) {
|
|
// Refresh token expired or invalid
|
|
Debug.LogError(
|
|
$"[GumYum Game] Refresh token expired or invalid (code 403)");
|
|
|
|
// Clear tokens
|
|
_jwtToken = "";
|
|
_refreshToken = "";
|
|
|
|
// If we have API keys, try to re-exchange
|
|
if (!string.IsNullOrEmpty(_apiKey)) {
|
|
if (DebugMode)
|
|
Debug.Log("[GumYum Game] Refresh failed, re-exchanging API key...");
|
|
|
|
await ExchangeApiKeyForJwtAsync(cancellationToken);
|
|
} else {
|
|
throw new HttpRequestException(
|
|
"Authentication expired - please re-authenticate");
|
|
}
|
|
} else {
|
|
throw new HttpRequestException(
|
|
$"Token refresh failed (code {(int)response.StatusCode})");
|
|
}
|
|
} finally {
|
|
_authSemaphore.Release();
|
|
}
|
|
}
|
|
|
|
// Streaming support for chat completions
|
|
internal async Task RequestStreamAsync(HttpMethod method, string endpoint,
|
|
object data,
|
|
Dictionary<string, string> queryParams,
|
|
Action<string> onChunk,
|
|
CancellationToken cancellationToken) {
|
|
if (!IsReady()) {
|
|
throw new InvalidOperationException("Not authenticated");
|
|
}
|
|
|
|
var url = GetApiUrl(endpoint);
|
|
|
|
// Add query parameters
|
|
if (queryParams?.Count > 0) {
|
|
var queryString = string.Join(
|
|
"&", queryParams.Select(
|
|
kvp => $"{kvp.Key}={Uri.EscapeDataString(kvp.Value)}"));
|
|
url += "?" + queryString;
|
|
}
|
|
|
|
using var request = new HttpRequestMessage(method, url);
|
|
SetRequestHeaders(request);
|
|
request.Headers.Accept.Clear();
|
|
request.Headers.Accept.Add(
|
|
new MediaTypeWithQualityHeaderValue("text/event-stream"));
|
|
|
|
if (data != null) {
|
|
var json = JsonConvert.SerializeObject(data, _jsonSettings);
|
|
request.Content =
|
|
new StringContent(json, Encoding.UTF8, "application/json");
|
|
}
|
|
|
|
if (DebugMode)
|
|
Debug.Log($"[GumYum Stream] Connecting to {url}");
|
|
|
|
using var response = await _httpClient.SendAsync(
|
|
request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
DialogueStreamStarted?.Invoke(this, EventArgs.Empty);
|
|
|
|
using var stream = await response.Content.ReadAsStreamAsync();
|
|
using var reader = new System.IO.StreamReader(stream);
|
|
|
|
var accumulatedContent = "";
|
|
var context = new Dictionary<string, object>();
|
|
var moodTransition = new Dictionary<string, object>();
|
|
|
|
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested) {
|
|
var line = await reader.ReadLineAsync();
|
|
if (line == null)
|
|
break;
|
|
|
|
line = line.Trim();
|
|
if (!line.StartsWith("data: "))
|
|
continue;
|
|
|
|
var dataStr = line.Substring(6);
|
|
if (DebugMode)
|
|
Debug.Log(
|
|
$"[GumYum Stream] Received data: {dataStr.Substring(0, Math.Min(100, dataStr.Length))}");
|
|
|
|
if (dataStr == "[DONE]" || dataStr == "done") {
|
|
// Stream finished
|
|
if (DebugMode)
|
|
Debug.Log(
|
|
$"[GumYum Stream] Stream complete, total content: {accumulatedContent.Length} chars");
|
|
|
|
DialogueStreamEnded?.Invoke(this, new DialogueStreamEndedEventArgs {
|
|
FullResponse = accumulatedContent, Context = context,
|
|
MoodTransition = moodTransition
|
|
});
|
|
|
|
onChunk?.Invoke(null); // Signal completion
|
|
return;
|
|
}
|
|
|
|
// Parse JSON data
|
|
try {
|
|
var eventData =
|
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
dataStr, _jsonSettings);
|
|
|
|
// Handle different event types
|
|
if (eventData.ContainsKey("choices")) {
|
|
var choices =
|
|
JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(
|
|
eventData["choices"].ToString(), _jsonSettings);
|
|
|
|
if (choices?.Count > 0) {
|
|
var choice = choices[0];
|
|
if (choice.ContainsKey("delta")) {
|
|
var delta =
|
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
choice["delta"].ToString(), _jsonSettings);
|
|
|
|
if (delta?.ContainsKey("content") == true) {
|
|
var contentChunk = delta["content"].ToString();
|
|
accumulatedContent += contentChunk;
|
|
DialogueChunkReceived?.Invoke(this, contentChunk);
|
|
onChunk?.Invoke(contentChunk);
|
|
}
|
|
} else if (choice.ContainsKey("message")) {
|
|
var message =
|
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
choice["message"].ToString(), _jsonSettings);
|
|
|
|
if (message?.ContainsKey("content") == true) {
|
|
var content = message["content"].ToString();
|
|
accumulatedContent = content;
|
|
DialogueChunkReceived?.Invoke(this, content);
|
|
onChunk?.Invoke(content);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for context and mood transition
|
|
if (eventData.ContainsKey("npc_context")) {
|
|
context = JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
eventData["npc_context"].ToString(), _jsonSettings);
|
|
}
|
|
if (eventData.ContainsKey("mood_transition")) {
|
|
moodTransition =
|
|
JsonConvert.DeserializeObject<Dictionary<string, object>>(
|
|
eventData["mood_transition"].ToString(), _jsonSettings);
|
|
}
|
|
} catch (Exception ex) {
|
|
if (DebugMode)
|
|
Debug.LogError(
|
|
$"[GumYum Stream] Error parsing event data: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Ensure we emit the final response
|
|
DialogueStreamEnded?.Invoke(this, new DialogueStreamEndedEventArgs {
|
|
FullResponse = accumulatedContent, Context = context,
|
|
MoodTransition = moodTransition
|
|
});
|
|
}
|
|
|
|
// Internal methods for Managers to raise events
|
|
internal void RaiseCharacterSpawned(NPC npc) {
|
|
CharacterSpawned?.Invoke(this, npc);
|
|
}
|
|
|
|
internal void RaiseDialogueReceived(DialogueReceivedEventArgs args) {
|
|
DialogueReceived?.Invoke(this, args);
|
|
}
|
|
|
|
internal void RaiseDialogueChunkReceived(string chunk) {
|
|
DialogueChunkReceived?.Invoke(this, chunk);
|
|
}
|
|
|
|
internal void RaiseDialogueStreamStarted() {
|
|
DialogueStreamStarted?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
internal void RaiseDialogueStreamEnded(DialogueStreamEndedEventArgs args) {
|
|
DialogueStreamEnded?.Invoke(this, args);
|
|
}
|
|
|
|
internal void RaiseRequestCompleted(RequestCompletedEventArgs args) {
|
|
RequestCompleted?.Invoke(this, args);
|
|
}
|
|
|
|
internal void RaiseRequestFailed(RequestFailedEventArgs args) {
|
|
RequestFailed?.Invoke(this, args);
|
|
}
|
|
|
|
public void Dispose() {
|
|
_authSemaphore?.Dispose();
|
|
_httpClient?.Dispose();
|
|
}
|
|
}
|
|
|
|
// Event Args Classes
|
|
public class DialogueReceivedEventArgs : EventArgs {
|
|
public string Response { get; set; }
|
|
public Dictionary<string, object> Context { get; set; }
|
|
public Dictionary<string, object> MoodTransition { get; set; }
|
|
}
|
|
|
|
public class DialogueStreamEndedEventArgs : EventArgs {
|
|
public string FullResponse { get; set; }
|
|
public Dictionary<string, object> Context { get; set; }
|
|
public Dictionary<string, object> MoodTransition { get; set; }
|
|
}
|
|
|
|
public class RequestCompletedEventArgs : EventArgs {
|
|
public string Endpoint { get; set; }
|
|
public object Data { get; set; }
|
|
}
|
|
|
|
public class RequestFailedEventArgs : EventArgs {
|
|
public string Endpoint { get; set; }
|
|
public string Error { get; set; }
|
|
}
|
|
} |