Skip to content

feat(aitools): component-name aliases for AI-emitted GhJSON#474

Closed
devin-ai-integration[bot] wants to merge 3 commits into
feature/2.0.0-text2jsonfrom
devin/1778713014-tier2-aliases
Closed

feat(aitools): component-name aliases for AI-emitted GhJSON#474
devin-ai-integration[bot] wants to merge 3 commits into
feature/2.0.0-text2jsonfrom
devin/1778713014-tier2-aliases

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Pull Request Title Format Guidelines (Click to expand)

feat(aitools): component-name aliases for AI-emitted GhJSON follows <type>(<scope>): <description>.

Description

This is the SmartHopper-side half of "Tier 2" from the OSS review and import plan (SmartHopper_OSS_Review_and_Import_Plan.md): a small loose-name resolver that maps common informal component names emitted by LLMs (e.g. csharp, slider, python, py, pt, ln, crv, xy, rect) to canonical Grasshopper names (C# Script, Number Slider, Python 3 Script, Point, Line, Curve, XY Plane, Rectangle).

Motivation

GhJSON.Grasshopper.Deserialization.ComponentInstantiator resolves names with StringComparison.OrdinalIgnoreCase against the proxy registry. LLMs frequently emit informal aliases that do not match any registered proxy, which currently forces a retry round-trip with self-healing prompts. A tiny dictionary substitution in the SmartHopper orchestration layer turns those into first-shot successes without weakening GhJSON's strict schema validation.

GhJSON-first design

Per the GhJSON-first principle, all schema/serialization/placement work stays in architects-toolkit/ghjson-dotnet:

  • Put semantics, GUID/name resolution against the component server, validation, migration → all stay in ghjson-dotnet.
  • gh_put already calls GhJson.IsValid, GhJson.FromJson, and GhJson.Fix(document). No duplication needed in SmartHopper.

What this PR adds is purely an orchestration-layer pre-pass: a Dictionary<string, string> of informal → canonical names, plus a Normalize(GhJsonDocument) that walks document.Components and rewrites Name in place. Pure string substitution; no Rhino or Grasshopper APIs touched.

Changes

  • New file src/SmartHopper.Core.Grasshopper/Utils/Canvas/ComponentNameAliases.cs
    • IReadOnlyDictionary<string, string> Aliases (case-insensitive, ~30 entries).
    • string Resolve(string name) — alias lookup with trim/whitespace handling.
    • int Normalize(GhJsonDocument? document) — in-place mutation, returns substitution count.
  • AITools/gh_put.cs — calls ComponentNameAliases.Normalize(document) after GhJson.FromJson and before GhJson.Fix, so both validation and GhJsonGrasshopper.Put see canonical names.
  • AITools/_gh_generate.cs — applies ComponentNameAliases.Resolve(name) when constructing each GhJsonComponent, so the GhJSON returned to the LLM already contains canonical names ready for the follow-up gh_put.
  • SmartHopper.Core.Grasshopper.Tests/Utils/ComponentNameAliasesTests.cs — xUnit [Theory] + [Fact] coverage for known aliases, canonical pass-through, unknown names, null/empty/whitespace, case-insensitivity, whitespace trimming, null/empty document, mixed-name document, and components without names. No Rhino/Grasshopper references (per testing-and-build rule).
  • docs/Tools/index.md — extended gh_put row to describe the new pre-pass.
  • THIRD_PARTY_NOTICES.md — new file documenting attribution and MIT licence text for brookstalley/cordyceps, whose alias map served as the starting point for Aliases. Source header in ComponentNameAliases.cs carries the same notice.

Branching note

Branched from feature/2.0.0-text2json and targeted at the same branch (no feature/2.0.0 exists on remote yet).

Breaking Changes

None. Pure additive change; existing GhJSON documents with canonical names flow through unchanged.

Testing Done

  • xUnit unit tests cover all branches of Resolve and Normalize. Tests do not require Rhino/Grasshopper activation, so they run on the standard CI matrix that already executes SmartHopper.Core.Grasshopper.Tests.
  • Manual review confirms gh_put's using SmartHopper.Core.Grasshopper.Utils.Canvas; import is already present from CanvasAccess; _gh_generate gets the using added.
  • Verified GhJsonComponent.Name is settable and GhJsonDocument.Components items are mutable in place (read-only list of mutable items), so in-place rewriting is safe.

Checklist

  • This PR is focused on a single feature or bug fix
  • Version in Solution.props was updated, if necessary, and follows semantic versioning (no bump needed; behaviour-additive within an existing pre-release)
  • CHANGELOG.md has been updated
  • PR title follows Conventional Commits format
  • PR description follows the template

Link to Devin session: https://app.devin.ai/sessions/870109f6b69b413480b5c763f3c42bbc
Requested by: @marc-romu

Adds SmartHopper.Core.Grasshopper.Utils.Canvas.ComponentNameAliases, a
small loose-name resolver that maps common informal component names
emitted by LLMs ("csharp", "slider", "python", "py", "pt",
"ln", "crv", "xy", "rect", ...) to canonical Grasshopper names
("C# Script", "Number Slider", "Python 3 Script", "Point",
"Line", "Curve", "XY Plane", "Rectangle", ...).

The resolver is applied transparently:
- gh_put: ComponentNameAliases.Normalize(document) runs after
  GhJson.FromJson and before GhJson.Fix, so the canonical name is
  visible to GhJSON validation and to GhJSON.Grasshopper.Put.
- _gh_generate: ComponentNameAliases.Resolve(name) is applied when each
  GhJsonComponent is constructed, so the GhJSON document returned to the
  LLM already contains canonical names ready to be passed to gh_put.

Schema-layer concerns (Put semantics, name resolution against the
component server, validation, migration) stay in ghjson-dotnet. The new
helper is pure string substitution and lives in SmartHopper as an
orchestration-layer pre-pass.

Includes xUnit tests in SmartHopper.Core.Grasshopper.Tests that exercise
Resolve and Normalize for known aliases, canonical names, unknown
names, whitespace, case-insensitivity, and document mutation. No
Rhino or Grasshopper runtime is required for the tests.

Alias map adapted from brookstalley/cordyceps (MIT) with attribution
preserved in the source file header. Added THIRD_PARTY_NOTICES.md
documenting the attribution and the Cordyceps MIT licence text.
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@marc-romu
Copy link
Copy Markdown
Member

I suggest migrating this feature to GhJSON-dotnet

devin-ai-integration Bot added a commit that referenced this pull request May 17, 2026
Land phase 1 of the MCP server design (docs/Architecture/mcp-server.md) so
external MCP clients (Claude Desktop, Cursor, VS Code, Claude Code) can
discover and call SmartHopper's existing AI tools over a local loopback
HTTP/JSON-RPC server. No SSE, no embedded resources, no LAN exposure;
those remain in phases 2-5.

SmartHopper.Infrastructure/Mcp/
  - McpServer:           HttpListener bound to 127.0.0.1 + [::1], origin
                         guard, optional bearer token, 256 KB request cap,
                         no payload logging.
  - JsonRpcDispatcher:   initialize, tools/list, tools/call,
                         notifications/initialized, ping.
                         Method-not-found stubs for resources/* and
                         prompts/*. SemaphoreSlim gate serializes
                         concurrent requests.
  - AIToolMcpAdapter:    bridges AIToolManager to MCP tool descriptors.
                         Allow-list (McpServerOptions.EnabledTools);
                         mutating-tools-off filter by name prefix
                         (gh_put, gh_move, script_*, ...).
                         Executes via AIToolCall.Exec().
  - McpServerLifecycle:  ref-counted singleton per port to support
                         multiple components on the same port without
                         tearing down the server while still in use.
  - McpServerOptions, McpToolDescriptor, McpToolCallResult: config /
                         result types.

SmartHopper.Components/Mcp/SmartHopperMcpServerComponent
  Opt-in GH component. Inputs: Enable (bool), Port (int, default 26929),
  BearerToken (string, optional), ExposeMutatingTools (bool, default
  false). Outputs: Url, Status.

SmartHopper.Infrastructure.Tests/Mcp/
  xUnit coverage (no Rhino refs):
    - AIToolMcpAdapterTests:  descriptor filtering, schema parsing,
                              executor wiring, error propagation.
    - JsonRpcDispatcherTests: initialize / tools/list / tools/call /
                              unknown method / invalid JSON /
                              notification / missing tool name.
    - McpServerOptionsTests:  defaults and Clone semantics.

GhJSON-first: the MCP adapter never imports ghjson-dotnet. AITool
implementations already own their GhJSON marshalling via
architects-toolkit/ghjson-dotnet; MCP forwards AIReturn.Body verbatim.

Cordyceps (MIT) attribution is recorded in per-file headers under
SmartHopper.Infrastructure/Mcp/. THIRD_PARTY_NOTICES.md is extended via
PR #474 and will pick up this attribution when both PRs land in
feature/2.0.0-text2json.

Phase 1 design-doc status flipped from "design draft" to "phase 1
implemented" in docs/Architecture/mcp-server.md.
@devin-ai-integration devin-ai-integration Bot deleted the devin/1778713014-tier2-aliases branch May 18, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant