using System.Text.Json; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using RevitBridge.Tools; namespace RevitBridge { /// /// Execution context handed to tools. Document and UIApplication are non-null when the /// tool runs on the Revit API thread (RequiresDocument = true, the default). Tools with /// RequiresDocument = false run on the bridge's server task with both set to null and /// must not touch the Revit API. /// internal sealed record ToolContext(Document? Document, UIApplication? UIApplication); /// /// Optional tool return shape: compact text for model context plus the full payload for /// details. Tools may instead return any plain JSON-serializable object, which the bridge /// serializes into both channels (capped in content). /// internal sealed record ToolOutput(object? Payload, string? CompactText = null); /// A single Revit tool exposed through the bridge. internal interface ITool { string Name { get; } string Label { get; } string Description { get; } /// JSON schema (anonymous object) describing the tool arguments. object ParametersSchema { get; } /// True only for tools that mutate the model. Write tools own their transaction. bool Write => false; /// /// False only for tools that never touch the Revit API. They skip the bridge's /// no-document 409 gate and run directly on the server task instead of the Revit /// API thread, so ToolContext.Document/UIApplication are null for them. /// bool RequiresDocument => true; /// Activation tier metadata: "core" (default) or "advanced". string Tier => "core"; /// Optional one-line "Available tools" system prompt entry. string? PromptSnippet => null; /// Optional system prompt guideline bullets. Each bullet must name the tool. IReadOnlyList? PromptGuidelines => null; /// /// Runs on the Revit API thread (or the server task when RequiresDocument is false). /// Returns a ToolOutput or any plain JSON-serializable payload. /// object? Execute(JsonElement args, ToolContext context); } /// Name -> tool map plus the metadata projection served by GET /tools. internal sealed class ToolRegistry { private readonly SortedDictionary _tools = new(StringComparer.Ordinal); public static ToolRegistry CreateDefault() { var registry = new ToolRegistry(); registry.Add(new SearchApiDocs()); registry.Add(new ExecuteCsharp()); registry.Add(new GetModelOverview()); registry.Add(new GetElements()); registry.Add(new GetElementTypes()); registry.Add(new GetElementDetails()); registry.Add(new ManageSelection()); registry.Add(new OpenView()); registry.Add(new SetParameters()); registry.Add(new CaptureView()); registry.Add(new ExportDocuments()); registry.Add(new GetModelHealth()); return registry; } public void Add(ITool tool) { if (_tools.ContainsKey(tool.Name)) throw new InvalidOperationException($"Duplicate tool name: {tool.Name}"); _tools[tool.Name] = tool; } public ITool? Get(string name) => _tools.TryGetValue(name, out var tool) ? tool : null; public IReadOnlyList DescribeAll() => _tools.Values.Select(Describe).ToArray(); public object Describe(ITool tool) => new { name = tool.Name, label = tool.Label, description = tool.Description, category = tool.Write ? "write" : "read", tier = tool.Tier, parameters = tool.ParametersSchema, executionMode = "sequential", write = tool.Write, requiresDocument = tool.RequiresDocument, promptSnippet = tool.PromptSnippet, promptGuidelines = tool.PromptGuidelines, }; } }