Files
Shutong Wu 36cbe62fe1 refactor(asset-gen): dedupe generate_* tool shell (CodeRabbit review)
status / cancel / list_providers were near-identical across GenerateAudio,
GenerateImage and GenerateModel (differing only by a kind label + poll
interval), and NormalizeOutputFolder was a verbatim triple copy.

- New AssetGenToolHelpers.{Status,Cancel,ListProviders} — the three tools now
  delegate, passing their kind label / poll interval.
- NormalizeOutputFolder moved to AssetGenPaths (it already lived on
  TryGetAssetsFolder).
- Behaviour preserved (image `remove_background` arm kept; provider-list
  filtering unchanged). Net -120 lines of duplication.

Verified: EditMode 1167 tests / 0 failures + in-editor smoke (audio/image/model
list_providers, status/cancel error paths, image remove_background all intact).

Claude-Session: https://claude.ai/code/session_015KYy51gwBuhDuLZXXoqc98
2026-07-13 10:26:31 -07:00

88 lines
4.1 KiB
C#

using System;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Security;
using MCPForUnity.Editor.Services.AssetGen;
using MCPForUnity.Editor.Services.AssetGen.Providers;
using Newtonsoft.Json.Linq;
namespace MCPForUnity.Editor.Tools.AssetGen
{
/// <summary>
/// Audio generation (SFX / music) via fal.ai. Triggered here (never from the GUI); the C# side
/// reads the fal key from the secure store and runs the job. Returns a job_id immediately; the
/// client polls the `status` action. When `model` is omitted it falls back to the model selected
/// in the Asset Generation tab, then the catalog default. Status / cancel / list_providers are
/// shared across the generate_* tools via <see cref="AssetGenToolHelpers"/>.
/// </summary>
[McpForUnityTool("generate_audio", AutoRegister = false, Group = "asset_gen", RequiresPolling = true, PollAction = "status", MaxPollSeconds = 600)]
public static class GenerateAudio
{
public static object HandleCommand(JObject @params)
{
if (@params == null) return new ErrorResponse("Parameters cannot be null.");
var p = new ToolParams(@params);
string action = (p.Get("action") ?? string.Empty).ToLowerInvariant();
try
{
switch (action)
{
case "generate": return Generate(p);
case "status": return AssetGenToolHelpers.Status(p, "Audio", 3.0);
case "cancel": return AssetGenToolHelpers.Cancel(p);
case "list_providers": return AssetGenToolHelpers.ListProviders("audio");
case "": return new ErrorResponse("'action' parameter is required.");
default:
return new ErrorResponse($"Unknown action: '{action}'. Supported: generate, status, cancel, list_providers.");
}
}
catch (NotSupportedException nse)
{
return new ErrorResponse(nse.Message);
}
catch (Exception e)
{
return new ErrorResponse(SecretRedactor.Scrub(e.Message));
}
}
private static object Generate(ToolParams p)
{
string provider = (p.Get("provider", "fal") ?? "fal").ToLowerInvariant();
AssetGenProviders.Audio(provider); // throws NotSupportedException for unknown providers
if (!SecureKeyStore.Current.Has(provider))
return new ErrorResponse(AssetGenProviders.MissingKeyMessage(provider));
string prompt = p.Get("prompt");
if (string.IsNullOrWhiteSpace(prompt))
return new ErrorResponse("'prompt' is required for audio generation.");
// Empty -> GUI-selected model -> catalog default. A null model reaches the adapter's own
// default; a resolved id is passed through verbatim (the catalog default equals the
// adapter constant, so an omitted model is a no-op either way).
string model = AssetGenModelCatalog.ResolveModel("audio", provider, p.Get("model"));
var req = new AudioGenRequest
{
Provider = provider,
Model = model,
Prompt = prompt,
Duration = p.GetFloat("duration", 0f) ?? 0f,
Name = p.Get("name"),
OutputFolder = p.Get("outputFolder"),
};
if (!AssetGenPaths.NormalizeOutputFolder(req.OutputFolder, out req.OutputFolder, out string outputErr))
return new ErrorResponse(outputErr);
AssetGenJob job = AssetGenJobManager.StartAudioGeneration(req);
if (job.State == AssetGenJobState.Failed)
return new ErrorResponse(job.Error ?? "Failed to start generation.");
return new PendingResponse(
$"Audio generation started with '{provider}'. Poll the status action with this job_id.",
pollIntervalSeconds: 3.0,
data: new { job_id = job.JobId, provider, status = "pending" });
}
}
}