|
| 1 | +# Goa Tools Plugin |
| 2 | + |
| 3 | +Many systems built with Goa expose **tools**: structured commands that other |
| 4 | +components (LLM workflows, background jobs, admin dashboards) invoke. A tool has |
| 5 | +a name plus validated payload and result schemas. Without a central definition it |
| 6 | +is easy for services to drift—each consumer recreates its own structs, codecs, |
| 7 | +and validation rules. |
| 8 | + |
| 9 | +The Tools plugin keeps those contracts in sync. Add the DSL to your Goa design, |
| 10 | +and the plugin generates a single, canonical set of artefacts—Go structs, JSON |
| 11 | +Schema, JSON codecs, and a registry—that every consumer can share. |
| 12 | + |
| 13 | +## Why use it? |
| 14 | + |
| 15 | +Use the plugin whenever the same tool must flow through multiple subsystems: |
| 16 | + |
| 17 | +- LLM orchestration or chat loops that call back into services. |
| 18 | +- Temporal payload converters, session persistence, or job workers. |
| 19 | +- Administrative or diagnostics tooling that inspects past tool invocations. |
| 20 | +- Multiple services that would otherwise hand-roll serializers. |
| 21 | + |
| 22 | +Define the tool once, regenerate, and every consumer speaks the same schema. |
| 23 | + |
| 24 | +## Generated assets |
| 25 | + |
| 26 | +Running `goa gen` produces the following files under `gen/tools/`: |
| 27 | + |
| 28 | +| File | Purpose | |
| 29 | +|---------------|---------------------------------------------------------------------------------------------------| |
| 30 | +| `types.go` | Go structs for tool payload/result values defined via the DSL (pure tools only). | |
| 31 | +| `schemas.go` | Embedded JSON Schema literals for every payload/result. | |
| 32 | +| `codecs.go` | Typed marshal/unmarshal helpers with inlined Goa validation, plus name-driven generic codecs. | |
| 33 | +| `registry.go` | A catalogue of `tools.ToolSpec` entries (name, service, set, codecs, schemas) with helper queries. | |
| 34 | + |
| 35 | +Runtime helpers (`tools/codec.go`, `tools/toolspec.go`) live in this module, so |
| 36 | +generated code simply imports `goa.design/plugins/v3/tools` for codecs or the |
| 37 | +registry types. |
| 38 | + |
| 39 | +## DSL overview |
| 40 | + |
| 41 | +```go |
| 42 | +import . "goa.design/plugins/v3/tools/dsl" |
| 43 | + |
| 44 | +Service("inventory", func() { |
| 45 | + ToolSet("ops", func() { |
| 46 | + Tool("lookup_item", func() { |
| 47 | + Payload(func() { |
| 48 | + Attribute("sku", String, "Inventory SKU", func() { MinLength(1) }) |
| 49 | + Required("sku") |
| 50 | + }) |
| 51 | + Result(func() { |
| 52 | + Attribute("found", Boolean, "True when item exists") |
| 53 | + Attribute("description", String, "Optional description") |
| 54 | + Required("found") |
| 55 | + }) |
| 56 | + }) |
| 57 | + |
| 58 | + ToolFromMethod("ListRecentItems") // reuse existing Goa method |
| 59 | + }) |
| 60 | +}) |
| 61 | +``` |
| 62 | + |
| 63 | +- `ToolSet(name, func())` scopes tools to a Goa service. |
| 64 | +- `Tool(name, func())` defines a standalone tool (the DSL mirrors Goa `Method`). |
| 65 | +- `ToolFromMethod(method, optionalName)` reuses an existing service method so |
| 66 | +you only maintain one payload/result definition. |
| 67 | + |
| 68 | +Pure tool bodies execute during DSL evaluation so the generator sees the final |
| 69 | +payload/result attributes. Method-derived tools are tagged so the collector can |
| 70 | +reuse the service-generated types, including custom `struct:pkg:path` locations. |
| 71 | + |
| 72 | +## Consuming the generated artefacts |
| 73 | + |
| 74 | +```go |
| 75 | +import tooldefs "github.com/example/project/gen/tools" |
| 76 | + |
| 77 | +codec, ok := tooldefs.PayloadCodec("lookup_item") |
| 78 | +if !ok { |
| 79 | + return fmt.Errorf("unknown tool") |
| 80 | +} |
| 81 | +raw, err := codec.ToJSON(&tooldefs.LookupItemPayload{Sku: "ABC"}) |
| 82 | +``` |
| 83 | + |
| 84 | +- `PayloadCodec` / `ResultCodec` provide generic JSON codecs backed by the typed helpers. |
| 85 | +- Typed helpers (`MarshalLookupItemPayload`, `UnmarshalLookupItemPayload`, `ValidateLookupItemPayload`, …) |
| 86 | + include the Goa-generated validation logic. |
| 87 | +- `ToolRegistry`, `Names`, `Spec`, `PayloadSchema`, `ResultSchema` expose |
| 88 | + metadata for admin or debugging views. |
| 89 | + |
| 90 | +## Development & tests |
| 91 | + |
| 92 | +- `go test ./tools/...` exercises the generator and DSL integration. |
| 93 | +- `tools/examples/simple` demonstrates a minimal design; run |
| 94 | + `goa gen github.com/example/tools-simple/design` in that directory to inspect |
| 95 | + the output. |
| 96 | + |
| 97 | +## FAQ |
| 98 | + |
| 99 | +**What is a tool?** A named command with a validated payload/result that other |
| 100 | +components can call (e.g. `lookup_item`). |
| 101 | + |
| 102 | +**Why not rely on Goa methods alone?** Goa methods generate transport handlers |
| 103 | +(HTTP/gRPC). Tools are often transport-agnostic—invoked by LLMs or background |
| 104 | +workers—and may not have direct endpoints. The plugin lets you model those |
| 105 | +contracts without adding extra handlers. |
| 106 | + |
| 107 | +**Do I always get new structs?** Pure tools (defined via `Tool`) generate new |
| 108 | +structs. Tools created with `ToolFromMethod` reuse the service’s existing |
| 109 | +payload/result types, including custom `struct:pkg:path` locations. |
0 commit comments