Skip to content

Commit d4a1bef

Browse files
committed
Add tools plugin
1 parent 46cdf8e commit d4a1bef

26 files changed

Lines changed: 2003 additions & 0 deletions

tools/README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.

tools/codec.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package tools
2+
3+
type (
4+
// JSONCodec is a generic interface for marshaling and unmarshaling JSON values
5+
// for tool payloads and results. Generated code in service toolsets uses JSONCodec
6+
// to encode Go structs into canonical JSON (for sending tool payloads to LLMs or APIs)
7+
// and decode JSON responses into the appropriate Go struct types. For each tool,
8+
// generated code provides strongly-typed JSONCodec values (e.g., JSONCodec[*MyPayload])
9+
// to ensure compile-time type safety when serializing and deserializing tool inputs
10+
// and outputs.
11+
JSONCodec[T any] struct {
12+
ToJSON func(v T) ([]byte, error)
13+
FromJSON func(data []byte) (T, error)
14+
}
15+
)

0 commit comments

Comments
 (0)