Skip to main content

Implementing a New Tool

A tool reads the Parts of a run and acts on the ones it handles, usually Blocks. You build it on tool.BaseTool, set a handler for each Part type you want, and register it.

Using BaseTool​

Build a tool.BaseTool and set handler function fields for the Part types you want to process. Parts you don't handle pass through unchanged. There are two families of handler.

For Block parts, set exactly ONE capability-typed handler: the parameter type bounds what the tool may write (immutability model, E-03):

  • Annotate(tool.BlockView) error: read-only; writes only overlays, annotations, and properties.
  • Produce(tool.VariantView) error: reads source, writes target.
  • Transform(tool.BlockView) (tool.EditPlan, error): a read-only edit producer: returns an edit plan, and the framework applier rewrites the source, rebasing surviving overlays, vaulting secrets, and bounds-checking, atomically. The flow's placement pass validates where a transformer may sit.

For the non-Block parts (Data, Media, Layer/Group start/end), set the untyped Handle*Fn fields, which use tool.PartHandler = func(part *model.Part) (*model.Part, error): these receive the streaming Part and type-assert the resource they care about.

package mytool

import (
"strings"

"github.com/neokapi/neokapi/core/model"
"github.com/neokapi/neokapi/core/tool"
)

func NewUppercaseTool() *tool.BaseTool {
t := &tool.BaseTool{
ToolName: "uppercase",
ToolDescription: "Converts source text to uppercase",
}
// Writes a target, so it sets Produce (the view bounds it to target writes).
t.Produce = func(v tool.VariantView) error {
if !v.Translatable() {
return nil
}
v.SetTargetText(model.LocaleEnglish, strings.ToUpper(v.SourceText()))
return nil
}
return t
}

Tool Categories​

CategoryResponsibilityExamples
TransformModify content in-placecase change, search/replace, redaction
EnrichAdd metadata or overlayssegmentation, content-memory leveraging, AI translation, terminology
ValidateCheck quality without modifyingrule-based checks, word count, spell check
ConvertTransform representationsEncoding conversion, line break normalization

Overriding Process​

If you need full control over the processing loop (for example, to accumulate state across many Parts, or to emit more Parts than you consume), define a named type that embeds tool.BaseTool and override Process directly:

type MyTool struct {
tool.BaseTool
}

func (t *MyTool) Process(ctx context.Context, in <-chan *model.Part, out chan<- *model.Part) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case part, ok := <-in:
if !ok {
return nil
}
// Custom processing logic
out <- part
}
}
}

Registration​

Register your tool in a ToolRegistry, mapping a name to a factory:

reg := registry.NewToolRegistry()
reg.Register("uppercase", func() tool.Tool {
return NewUppercaseTool()
})

Use RegisterWithSchema instead to attach a parameter schema, and SetConfigFactory so a YAML step's config: reaches the tool; see Tool Authoring with Parameter Schemas.

Built-in Tools​

The framework's built-in tools are registered with their parameter schemas. The Tool Reference is generated from those schemas and lists every tool in the current build with its name, description and parameters. For how the built-ins map to the kinds of work above, see Tools.

Schema-Driven CLI Flags​

Built-in tools take their CLI flags from their config structs: each field with a schema:"..." tag becomes a flag, and schema:"-" excludes a field. The NewToolFromConfig pattern lets the flow engine build a tool from YAML configuration by mapping config keys to struct fields.

Registering Built-in Tools​

All built-in tools can be registered into a registry at once, each with its parameter schema:

import (
"github.com/neokapi/neokapi/core/registry"
"github.com/neokapi/neokapi/core/tools"
)

toolReg := registry.NewToolRegistry()
tools.RegisterAll(toolReg)

Individual tools can also be constructed directly. Each takes a config struct (see the Tool Reference for every field):

// Segmentation with default SRX-like rules
segTool := tools.NewSegmentationTool(&tools.SegmentationConfig{})

// Rule-based check: configured via per-rule flags on RuleCheckConfig
checkTool := tools.NewRuleCheckTool(tools.NewRuleCheckConfig(model.LocaleID("fr")))

// Content-memory leverage with a custom fuzzy threshold and a memory provider
memoryTool := tools.NewMemoryLeverageTool(&tools.MemoryLeverageConfig{
TargetLocale: "fr",
FuzzyThreshold: 80, // 0-100
Memory: memoryProvider,
})

The terms package offers two library tools for embedding in Go, each taking a Terminology alongside its config. Neither is a registered flow tool: the tool a YAML flow or kapi exec names is term-check, and when a tool's schema declares that it requires a terms store, the runner appends term-lookup and term-enforce after it with the run's locales.

import "github.com/neokapi/neokapi/terms"

// Term lookup: scans source text and attaches terminology annotations
termLookupTool := terms.NewTermLookupTool(tb, terms.TermLookupConfig{
SourceLocale: "en",
TargetLocale: "fr",
})

// Term enforce: verifies translations use the preferred terminology
termEnforceTool := terms.NewTermEnforceTool(tb, terms.TermEnforceConfig{
SourceLocale: "en",
TargetLocale: "fr",
})