forked from admin/VolcanicAgenticCore
feat(core): session SetTimeZone and the date and zone in the system prompt
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Volcanic.AgenticCore.Abstractions.Extensions.Events;
|
||||
using Volcanic.AgenticCore.Abstractions.Json;
|
||||
using Volcanic.AgenticCore.Abstractions.Messages;
|
||||
@@ -28,7 +29,7 @@ public class SystemPromptBuilderTests
|
||||
new DelegateTool<object>("alpha", "d", (_, _, _) => Task.FromResult(ToolResult.Text("")), promptSnippet: "alpha: does a", promptGuidelines: ["Use alpha wisely"]),
|
||||
new DelegateTool<object>("silent", "d", (_, _, _) => Task.FromResult(ToolResult.Text(""))),
|
||||
};
|
||||
var builder = new SystemPromptBuilder(sub, () => tools, () => new DateTimeOffset(2026, 9, 25, 0, 0, 0, TimeSpan.Zero));
|
||||
var builder = new SystemPromptBuilder(sub, () => tools, new FakeTimeProvider(new DateTimeOffset(2026, 9, 25, 0, 0, 0, TimeSpan.Zero)), () => TimeZoneInfo.Utc);
|
||||
var options = builder.DefaultOptions("BASE");
|
||||
options.Sections["extra"] = "more";
|
||||
var prompt = builder.Render(options);
|
||||
@@ -41,7 +42,7 @@ public class SystemPromptBuilderTests
|
||||
var ctxStart = prompt.IndexOf("<project_context>", StringComparison.Ordinal);
|
||||
Assert.True(prompt.IndexOf("Sub rules", StringComparison.Ordinal) < prompt.IndexOf("Root rules", StringComparison.Ordinal));
|
||||
Assert.True(ctxStart > 0);
|
||||
Assert.Contains("Current date: 2026-09-25", prompt);
|
||||
Assert.Contains("Current date: 2026-09-25 (Friday)\nTime zone: ", prompt);
|
||||
Assert.Contains($"Working directory: {sub}", prompt);
|
||||
|
||||
options.SelectedTools.Clear();
|
||||
@@ -66,7 +67,7 @@ public class SystemPromptBuilderTests
|
||||
File.WriteAllText(Path.Combine(dir, "memory.md"), "- User prefers tabs");
|
||||
try
|
||||
{
|
||||
var builder = new SystemPromptBuilder(dir, () => [], () => DateTimeOffset.UnixEpoch);
|
||||
var builder = new SystemPromptBuilder(dir, () => [], new FakeTimeProvider(DateTimeOffset.UnixEpoch), () => TimeZoneInfo.Utc);
|
||||
var prompt = builder.Render(builder.DefaultOptions("BASE"));
|
||||
|
||||
Assert.Contains("Agent rules", prompt);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Volcanic.AgenticCore.Abstractions.Extensions;
|
||||
using Volcanic.AgenticCore.Abstractions.Tools;
|
||||
using Volcanic.AgenticCore.Features.Agent;
|
||||
using Volcanic.AgenticCore.Features.Extensions;
|
||||
using Volcanic.AgenticCore.Hosting;
|
||||
using Volcanic.AgenticCore.Testing;
|
||||
@@ -76,4 +78,72 @@ public class SessionTimeZoneTests
|
||||
Assert.Equal("Europe/Berlin", TimeZoneIds.ToIana(Berlin));
|
||||
Assert.Equal("Europe/Berlin", TimeZoneIds.ToIana(TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time")));
|
||||
}
|
||||
|
||||
private static readonly TimeZoneInfo NewYork = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
|
||||
private static readonly DateTimeOffset LateSaturdayUtc = new(2026, 9, 26, 23, 30, 0, TimeSpan.Zero);
|
||||
|
||||
[Theory]
|
||||
[InlineData("Asia/Tokyo", "Current date: 2026-09-27 (Sunday)", "Time zone: Asia/Tokyo (UTC+09:00)")]
|
||||
[InlineData("Europe/Berlin", "Current date: 2026-09-27 (Sunday)", "Time zone: Europe/Berlin (UTC+02:00)")]
|
||||
[InlineData("America/New_York", "Current date: 2026-09-26 (Saturday)", "Time zone: America/New_York (UTC-04:00)")]
|
||||
public async Task Prompt_shows_the_date_and_zone_in_the_session_zone(string zoneId, string dateLine, string zoneLine)
|
||||
{
|
||||
await using var host = new TestHost(new FakeTimeProvider(LateSaturdayUtc));
|
||||
await using var session = await host.CreateSessionAsync(b => b.TimeZone = TimeZoneInfo.FindSystemTimeZoneById(zoneId));
|
||||
|
||||
var prompt = session.SystemPrompt;
|
||||
|
||||
Assert.Contains($"{dateLine}\n{zoneLine}\nWorking directory: ", prompt);
|
||||
Assert.DoesNotMatch(@"\d{2}:\d{2}\n", prompt.Split("<environment>")[1]); // no time of day in the cached prefix
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetTimeZone_during_a_run_changes_the_next_runs_prompt_not_the_current_one()
|
||||
{
|
||||
AgentSession? session = null;
|
||||
var switchZone = new DelegateTool<ProbeArgs>("switch_zone", "s", (_, _, _) =>
|
||||
{
|
||||
session!.SetTimeZone(Tokyo);
|
||||
return Task.FromResult(ToolResult.Text("switched"));
|
||||
});
|
||||
await using var host = new TestHost(new FakeTimeProvider(LateSaturdayUtc), null,
|
||||
new LambdaExtension("tools", api => { api.RegisterTool(switchZone); return ValueTask.CompletedTask; }));
|
||||
host.Faux.Enqueue(FauxTurn.Tool("switch_zone", new { value = "x" }), FauxTurn.Say("done"), FauxTurn.Say("second"));
|
||||
session = await host.CreateSessionAsync(b => b.TimeZone = Berlin);
|
||||
|
||||
await session.PromptAsync("first");
|
||||
Assert.Equal("Asia/Tokyo", TimeZoneIds.ToIana(session.TimeZone)); // the property changes at once
|
||||
await session.PromptAsync("again");
|
||||
|
||||
Assert.Equal(3, host.Faux.Requests.Count);
|
||||
Assert.Contains("Time zone: Europe/Berlin", host.Faux.Requests[0].SystemPrompt);
|
||||
Assert.Contains("Time zone: Europe/Berlin", host.Faux.Requests[1].SystemPrompt); // same run, same prompt
|
||||
Assert.Contains("Time zone: Asia/Tokyo (UTC+09:00)", host.Faux.Requests[2].SystemPrompt);
|
||||
await session.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetTimeZone_changes_the_TZ_id_for_processes()
|
||||
{
|
||||
await using var host = new TestHost();
|
||||
await using var session = await host.CreateSessionAsync();
|
||||
session.SetTimeZone(Tokyo);
|
||||
Assert.Equal("Asia/Tokyo", ((IExtensionHost)session).TimeZoneId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Without_options_the_prompt_uses_the_local_zone()
|
||||
{
|
||||
await using var host = new TestHost();
|
||||
await using var session = await host.CreateSessionAsync();
|
||||
Assert.Contains($"Time zone: {TimeZoneIds.Name(TimeZoneInfo.Local)} (", session.SystemPrompt);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(9, 0, "UTC+09:00")]
|
||||
[InlineData(-4, 0, "UTC-04:00")]
|
||||
[InlineData(5, 30, "UTC+05:30")]
|
||||
[InlineData(0, 0, "UTC+00:00")]
|
||||
public void FormatOffset_formats_sign_hours_and_minutes(int hours, int minutes, string expected) =>
|
||||
Assert.Equal(expected, TimeZoneIds.FormatOffset(new TimeSpan(hours, hours < 0 ? -minutes : minutes, 0)));
|
||||
}
|
||||
|
||||
@@ -19,13 +19,21 @@ public sealed class TestHost : IAsyncDisposable
|
||||
public string Cwd { get; }
|
||||
public List<AgentSessionEvent> Events { get; } = [];
|
||||
|
||||
public TestHost(Action<AgenticCoreOptions>? configure = null, params IExtension[] extensions) : this(useFileStore: false, configure, extensions) { }
|
||||
public TestHost(Action<AgenticCoreOptions>? configure = null, params IExtension[] extensions) : this(useFileStore: false, time: null, configure, extensions) { }
|
||||
|
||||
public TestHost(bool useFileStore, Action<AgenticCoreOptions>? configure = null, params IExtension[] extensions)
|
||||
: this(useFileStore, time: null, configure, extensions) { }
|
||||
|
||||
/// <summary>A host whose sessions read <paramref name="time"/> (registered as the DI <see cref="TimeProvider"/>).</summary>
|
||||
public TestHost(TimeProvider time, Action<AgenticCoreOptions>? configure = null, params IExtension[] extensions)
|
||||
: this(useFileStore: false, time, configure, extensions) { }
|
||||
|
||||
private TestHost(bool useFileStore, TimeProvider? time, Action<AgenticCoreOptions>? configure, IExtension[] extensions)
|
||||
{
|
||||
Cwd = Path.Combine(Path.GetTempPath(), "volcanic-host", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(Cwd);
|
||||
var services = new ServiceCollection();
|
||||
if (time is not null) services.AddSingleton(time);
|
||||
services.AddAgenticCore(o =>
|
||||
{
|
||||
o.AgentDir = Path.Combine(Cwd, ".agent");
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../src/Volcanic.AgenticCore/Volcanic.AgenticCore.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user