Add SetVolumeTool (validates 0..100, delegates to Pi via IDeviceChannel)
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using backend.Tools;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace backend.tests;
|
||||||
|
|
||||||
|
public class SetVolumeToolTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Forwards_call_to_pi_and_returns_its_result()
|
||||||
|
{
|
||||||
|
var fake = new RecordingChannel(
|
||||||
|
JsonDocument.Parse("""{"ok":true}""").RootElement);
|
||||||
|
var tool = new SetVolumeTool();
|
||||||
|
var ctx = new DeviceContext(Guid.NewGuid(), Guid.NewGuid(), fake);
|
||||||
|
|
||||||
|
var args = JsonDocument.Parse("""{"level":42}""").RootElement;
|
||||||
|
var res = await tool.ExecuteAsync(new ToolInvocation("c1", args), ctx, default);
|
||||||
|
|
||||||
|
fake.LastName.Should().Be("set_volume");
|
||||||
|
fake.LastArgs.GetProperty("level").GetInt32().Should().Be(42);
|
||||||
|
res.Output.GetProperty("ok").GetBoolean().Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Rejects_out_of_range_level_without_calling_pi()
|
||||||
|
{
|
||||||
|
var fake = new RecordingChannel(JsonDocument.Parse("""{"ok":true}""").RootElement);
|
||||||
|
var tool = new SetVolumeTool();
|
||||||
|
var ctx = new DeviceContext(Guid.NewGuid(), Guid.NewGuid(), fake);
|
||||||
|
|
||||||
|
var args = JsonDocument.Parse("""{"level":150}""").RootElement;
|
||||||
|
var res = await tool.ExecuteAsync(new ToolInvocation("c1", args), ctx, default);
|
||||||
|
|
||||||
|
fake.LastName.Should().BeNull();
|
||||||
|
res.Output.GetProperty("ok").GetBoolean().Should().BeFalse();
|
||||||
|
res.Output.GetProperty("error").GetString().Should().Contain("0..100");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecordingChannel(JsonElement reply) : IDeviceChannel
|
||||||
|
{
|
||||||
|
public string? LastName { get; private set; }
|
||||||
|
public JsonElement LastArgs { get; private set; }
|
||||||
|
public Task<JsonElement> CallPiToolAsync(string name, JsonElement args, CancellationToken ct)
|
||||||
|
{
|
||||||
|
LastName = name;
|
||||||
|
LastArgs = args;
|
||||||
|
return Task.FromResult(reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace backend.Tools;
|
||||||
|
|
||||||
|
public class SetVolumeTool : ITool
|
||||||
|
{
|
||||||
|
public string Name => "set_volume";
|
||||||
|
public string Description =>
|
||||||
|
"Sets the assistant's audio output volume on the device. Level is 0..100.";
|
||||||
|
public bool RunsDuringResponse => true;
|
||||||
|
|
||||||
|
public JsonElement ParameterSchema { get; } = JsonDocument.Parse(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"type":"object",
|
||||||
|
"properties":{"level":{"type":"integer","minimum":0,"maximum":100}},
|
||||||
|
"required":["level"]
|
||||||
|
}
|
||||||
|
""").RootElement;
|
||||||
|
|
||||||
|
public async Task<ToolResult> ExecuteAsync(ToolInvocation invocation, DeviceContext device, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (!invocation.Arguments.TryGetProperty("level", out var lvlEl)
|
||||||
|
|| !lvlEl.TryGetInt32(out var level)
|
||||||
|
|| level < 0 || level > 100)
|
||||||
|
{
|
||||||
|
var err = JsonDocument.Parse("""{"ok":false,"error":"level must be an integer in 0..100"}""").RootElement;
|
||||||
|
return new ToolResult(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var piReply = await device.Channel.CallPiToolAsync(Name, invocation.Arguments, ct);
|
||||||
|
return new ToolResult(piReply);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user