mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-15 14:08:08 +01:00
tcloud compatibility, dotnet bug fixes
This commit is contained in:
@@ -1,20 +1,28 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Temporalio.Activities;
|
||||
using TrainSearchWorker.Models;
|
||||
using TrainSearchWorker.Converters;
|
||||
|
||||
namespace TrainSearchWorker.Activities;
|
||||
|
||||
public class TrainActivities
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public TrainActivities(IHttpClientFactory clientFactory)
|
||||
{
|
||||
_client = clientFactory.CreateClient("TrainApi");
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
[Activity]
|
||||
public async Task<List<Journey>> SearchTrains(SearchTrainsRequest request)
|
||||
public async Task<JourneyResponse> SearchTrains(SearchTrainsRequest request)
|
||||
{
|
||||
var response = await _client.GetAsync(
|
||||
$"api/search?from={Uri.EscapeDataString(request.From)}" +
|
||||
@@ -24,17 +32,28 @@ public class TrainActivities
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<List<Journey>>()
|
||||
?? throw new InvalidOperationException("Received null response from API");
|
||||
// Deserialize into JourneyResponse rather than List<Journey>
|
||||
var journeyResponse = await response.Content.ReadFromJsonAsync<JourneyResponse>(_jsonOptions)
|
||||
?? throw new InvalidOperationException("Received null response from API");
|
||||
|
||||
return journeyResponse;
|
||||
}
|
||||
|
||||
[Activity]
|
||||
public async Task<List<Journey>> BookTrains(BookTrainsRequest request)
|
||||
public async Task<BookTrainsResponse> BookTrains(BookTrainsRequest request)
|
||||
{
|
||||
var response = await _client.PostAsJsonAsync("api/book", request);
|
||||
// Build the URL using the train IDs from the request
|
||||
var url = $"api/book/{Uri.EscapeDataString(request.TrainIds)}";
|
||||
|
||||
// POST with no JSON body, matching the Python version
|
||||
var response = await _client.PostAsync(url, null);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<List<Journey>>()
|
||||
?? throw new InvalidOperationException("Received null response from API");
|
||||
// Deserialize into a BookTrainsResponse (a single object)
|
||||
var bookingResponse = await response.Content.ReadFromJsonAsync<BookTrainsResponse>(_jsonOptions)
|
||||
?? throw new InvalidOperationException("Received null response from API");
|
||||
|
||||
return bookingResponse;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TrainSearchWorker.Models;
|
||||
|
||||
public record BookTrainsRequest
|
||||
{
|
||||
[JsonPropertyName("train_ids")]
|
||||
public required string TrainIds { get; init; }
|
||||
}
|
||||
|
||||
17
enterprise/Models/BookTrainsResponse.cs
Normal file
17
enterprise/Models/BookTrainsResponse.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TrainSearchWorker.Models;
|
||||
|
||||
public record BookTrainsResponse
|
||||
{
|
||||
[JsonPropertyName("booking_reference")]
|
||||
public required string BookingReference { get; init; }
|
||||
|
||||
// If the API now returns train_ids as an array, use List<string>
|
||||
[JsonPropertyName("train_ids")]
|
||||
public required List<string> TrainIds { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public required string Status { get; init; }
|
||||
}
|
||||
@@ -1,12 +1,27 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TrainSearchWorker.Models;
|
||||
|
||||
public record Journey
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public required string Id { get; init; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public required string Type { get; init; }
|
||||
|
||||
[JsonPropertyName("departure")]
|
||||
public required string Departure { get; init; }
|
||||
|
||||
[JsonPropertyName("arrival")]
|
||||
public required string Arrival { get; init; }
|
||||
|
||||
[JsonPropertyName("departure_time")]
|
||||
public required string DepartureTime { get; init; }
|
||||
|
||||
[JsonPropertyName("arrival_time")]
|
||||
public required string ArrivalTime { get; init; }
|
||||
|
||||
[JsonPropertyName("price")]
|
||||
public required decimal Price { get; init; }
|
||||
}
|
||||
}
|
||||
10
enterprise/Models/JourneyResponse.cs
Normal file
10
enterprise/Models/JourneyResponse.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TrainSearchWorker.Models;
|
||||
|
||||
public record JourneyResponse
|
||||
{
|
||||
[JsonPropertyName("journeys")]
|
||||
public List<Journey>? Journeys { get; init; }
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TrainSearchWorker.Models;
|
||||
|
||||
public record SearchTrainsRequest
|
||||
{
|
||||
[JsonPropertyName("origin")]
|
||||
public required string From { get; init; }
|
||||
|
||||
[JsonPropertyName("destination")]
|
||||
public required string To { get; init; }
|
||||
|
||||
[JsonPropertyName("outbound_time")]
|
||||
public required string OutboundTime { get; init; }
|
||||
|
||||
[JsonPropertyName("return_time")]
|
||||
public required string ReturnTime { get; init; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Temporalio.Client;
|
||||
using Temporalio.Worker;
|
||||
using TrainSearchWorker.Activities;
|
||||
|
||||
// Set up dependency injection
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Add HTTP client
|
||||
@@ -17,11 +18,17 @@ services.AddScoped<TrainActivities>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Create client
|
||||
var client = await TemporalClient.ConnectAsync(new()
|
||||
{
|
||||
TargetHost = "localhost:7233",
|
||||
});
|
||||
// Create client using the helper, which supports Temporal Cloud if environment variables are set
|
||||
var client = await TemporalClientHelper.CreateClientAsync();
|
||||
|
||||
// Read connection details from environment or use defaults
|
||||
var address = Environment.GetEnvironmentVariable("TEMPORAL_ADDRESS") ?? "localhost:7233";
|
||||
var ns = Environment.GetEnvironmentVariable("TEMPORAL_NAMESPACE") ?? "default";
|
||||
|
||||
// Log connection details
|
||||
Console.WriteLine("Starting worker...");
|
||||
Console.WriteLine($"Connecting to Temporal at address: {address}");
|
||||
Console.WriteLine($"Using namespace: {ns}");
|
||||
|
||||
// Create worker options
|
||||
var options = new TemporalWorkerOptions("agent-task-queue-legacy");
|
||||
@@ -34,7 +41,6 @@ options.AddActivity(activities.BookTrains);
|
||||
// Create and run worker
|
||||
var worker = new TemporalWorker(client, options);
|
||||
|
||||
Console.WriteLine("Starting worker...");
|
||||
using var tokenSource = new CancellationTokenSource();
|
||||
Console.CancelKeyPress += (_, eventArgs) =>
|
||||
{
|
||||
@@ -49,4 +55,4 @@ try
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Worker shutting down...");
|
||||
}
|
||||
}
|
||||
|
||||
29
enterprise/SingleOrArrayConverter.cs
Normal file
29
enterprise/SingleOrArrayConverter.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TrainSearchWorker.Converters
|
||||
{
|
||||
public class SingleOrArrayConverter<T> : JsonConverter<List<T>>
|
||||
{
|
||||
public override List<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.StartArray)
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<T>>(ref reader, options) ?? new List<T>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single element – wrap it in a list.
|
||||
T element = JsonSerializer.Deserialize<T>(ref reader, options);
|
||||
return new List<T> { element };
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, List<T> value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
enterprise/TemporalClientHelper.cs
Normal file
48
enterprise/TemporalClientHelper.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Temporalio.Client;
|
||||
|
||||
public static class TemporalClientHelper
|
||||
{
|
||||
public static async Task<ITemporalClient> CreateClientAsync()
|
||||
{
|
||||
var address = Environment.GetEnvironmentVariable("TEMPORAL_ADDRESS") ?? "localhost:7233";
|
||||
var ns = Environment.GetEnvironmentVariable("TEMPORAL_NAMESPACE") ?? "default";
|
||||
var clientCertPath = Environment.GetEnvironmentVariable("TEMPORAL_TLS_CERT");
|
||||
var clientKeyPath = Environment.GetEnvironmentVariable("TEMPORAL_TLS_KEY");
|
||||
var apiKey = Environment.GetEnvironmentVariable("TEMPORAL_API_KEY");
|
||||
|
||||
var options = new TemporalClientConnectOptions(address)
|
||||
{
|
||||
Namespace = ns
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(clientCertPath) && !string.IsNullOrEmpty(clientKeyPath))
|
||||
{
|
||||
// mTLS authentication
|
||||
options.Tls = new()
|
||||
{
|
||||
ClientCert = await File.ReadAllBytesAsync(clientCertPath),
|
||||
ClientPrivateKey = await File.ReadAllBytesAsync(clientKeyPath),
|
||||
};
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
// API Key authentication
|
||||
// TODO test
|
||||
options.RpcMetadata = new Dictionary<string, string>()
|
||||
{
|
||||
["authorization"] = $"Bearer {apiKey}",
|
||||
["temporal-namespace"] = ns
|
||||
};
|
||||
options.RpcMetadata = new Dictionary<string, string>()
|
||||
{
|
||||
["temporal-namespace"] = ns
|
||||
};
|
||||
options.Tls = new();
|
||||
}
|
||||
|
||||
return await TemporalClient.ConnectAsync(options);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user