Documentation
¶
Overview ¶
Package dsl provides an agent DSL plugin for niro-stream.
It parses two separate definitions:
- Agent definition (one JSON): tools, schemas, and agents. Parse with ParseDSL, validate with Validate, compile with Compile to obtain a Runner.
- Workflow definition (separate JSON): how agents are composed (fan, race, sequence). Parse with ParseWorkflow, validate with ValidateWorkflow, compile to run with orchestrate primitives.
Install with:
go get github.com/alexedtionweb/niro-stream/plugin/dsl
The Runner uses RunContext (Get/Set) for template expansion and expr conditions; set session, event, history and custom vars per invoke. Runner.Stream returns a niro.Stream for composition with orchestrate.Fan, Race, Sequence.
Template strings: All description and prompt text is resolved as Go text/template with RunContext as data. Use ExecuteTemplate(tpl, data) or ExpandTemplate(tpl, runCtx) for custom expansion.
Tools and niro: Tools are a single composable responsibility. Compile produces a shared tools.Toolset (from DSL tools + CompileOptions.ToolHandlers/ToolTypes). Per agent run, resolveAgentTools filters by when/unless, expands descriptions, and yields AgentTools (Toolset, ToolChoice, ToolStreamOptions). Optional WithToolsetTransform and WithToolOptions let you customize per agent (e.g. add HITL approver, override max rounds). The runner wires to niro as: req = toolset.Apply(req) then tools.NewToolingProvider(provider, toolset, opts).Generate(ctx, req). So the same tools.Toolset and ToolingProvider contract used outside the DSL is used inside it.
Package layout: types/parse/validate (definition); compile (compileTools, compileAgents → NiroDefinition); runner (Stream/Run, filterTools, toolset expansion); cache (GetOrCompute); expr (EvalCondition, CompileRunContextExpr); template (ExecuteTemplate, getParsedTemplate); tooltype_*.go (http, handoff).
Tool type registry: register primitives (e.g. "http") or custom types. In the DSL, each tool spec can include "type": "http" (or any registered type name). Omit "type" or use "code" for tools implemented via CompileOptions.ToolHandlers. Built-in "http": URL (Go template with args), method, headers, args → JSON Schema. Add types with RegisterToolType or CompileOptions.ToolTypes (option overrides default).
Index ¶
- func BuildHTTPTool(name string, spec json.RawMessage) (tools.ToolDefinition, error)
- func BuildHandoffTool(name string, spec json.RawMessage) (tools.ToolDefinition, error)
- func CompileRunContextExpr(exprStr string) error
- func EvalCondition(exprStr string, runCtx *RunContext) (bool, error)
- func EvalResponseCondition(exprStr string, response map[string]any) (bool, error)
- func ExecuteTemplate(templateStr string, data map[string]any) (string, error)
- func ExpandTemplate(templateStr string, runCtx *RunContext) (string, error)
- func GetAs[T any](runCtx *RunContext, key string) (T, bool)
- func GetOrCompute[K comparable, V any](mu *sync.RWMutex, cache *map[K]V, key K, build func() (V, error)) (V, error)
- func RegisterToolType(typeName string, b ToolTypeBuilder)
- func Validate(def *DSLDefinition) error
- func ValidateWorkflow(wf *WorkflowDefinition, agentNames map[string]struct{}) error
- func WithRunContext(ctx context.Context, runCtx *RunContext) context.Context
- type AgentConfig
- type AgentToolRef
- type AgentTools
- type CompileOptions
- type CompiledAgentConfig
- type CompiledWorkflow
- type DSLDefinition
- type HTTPToolSpec
- type HandoffSpec
- type LimitsConfig
- type ModelConfig
- type NiroDefinition
- type OutputConfig
- type PromptConfig
- type RunContext
- type Runner
- func (r *Runner) BindWorkflows(workflows map[string]*CompiledWorkflow)
- func (r *Runner) Fan(ctx context.Context, runCtx *RunContext, sessionID string, ...) *niro.Stream
- func (r *Runner) FanCollect(ctx context.Context, runCtx *RunContext, sessionID string, agentNames []string) (texts []string, usages []niro.Usage, err error)
- func (r *Runner) FanThen(ctx context.Context, runCtx *RunContext, sessionID string, ...) (*niro.Stream, string, niro.Usage, error)
- func (r *Runner) Race(ctx context.Context, runCtx *RunContext, sessionID string, ...) (string, niro.Usage, error)
- func (r *Runner) Run(ctx context.Context, runCtx *RunContext, agentName string, sessionID string) (string, error)
- func (r *Runner) RunHandoff(ctx context.Context, history []niro.Message, ...) (reply string, usage niro.Usage, err error)
- func (r *Runner) Sequence(ctx context.Context, runCtx *RunContext, sessionID string, ...) (*niro.Stream, error)
- func (r *Runner) Stream(ctx context.Context, runCtx *RunContext, agentName string, sessionID string) (*niro.Stream, error)
- type RunnerOption
- type ToolTypeBuilder
- type ToolTypeBuilderFunc
- type WorkflowDefinition
- type WorkflowNode
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildHTTPTool ¶
func BuildHTTPTool(name string, spec json.RawMessage) (tools.ToolDefinition, error)
BuildHTTPTool builds a tools.ToolDefinition for a type "http" tool.
func BuildHandoffTool ¶
func BuildHandoffTool(name string, spec json.RawMessage) (tools.ToolDefinition, error)
BuildHandoffTool builds a tools.ToolDefinition for a type "handoff" tool. The handler returns tools.HandoffSignal so the tool loop emits a handoff frame.
func CompileRunContextExpr ¶
CompileRunContextExpr compiles a when/unless expression; use from validation so env matches runtime.
func EvalCondition ¶
func EvalCondition(exprStr string, runCtx *RunContext) (bool, error)
EvalCondition evaluates a when/unless expression with the given RunContext as env. Returns true if the condition passes (for "when": include tool; for "unless": exclude if true). If the expression returns a bool, that value is used; otherwise the result is treated as truthy (e.g. non-empty string, non-nil, non-zero number) so conditions like "event.text" work.
func EvalResponseCondition ¶
EvalResponseCondition compiles and runs an expression with env {"$": response}. Used for HTTP tool cases: when is evaluated against response (status, body).
func ExecuteTemplate ¶
ExecuteTemplate parses the template (with caching) and executes it with data. Single entry point for template execution.
func ExpandTemplate ¶
func ExpandTemplate(templateStr string, runCtx *RunContext) (string, error)
ExpandTemplate executes the template with RunContext as data (prompts, tool descriptions).
func GetAs ¶
func GetAs[T any](runCtx *RunContext, key string) (T, bool)
GetAs returns the value for key from runCtx as type T and whether it was set and convertible. Use when the expected type is known to avoid manual type assertions.
func GetOrCompute ¶
func GetOrCompute[K comparable, V any](mu *sync.RWMutex, cache *map[K]V, key K, build func() (V, error)) (V, error)
GetOrCompute returns the value for key from cache, or computes it with build() and stores it. Uses double-checked locking so compilation/parsing is done once per key.
func RegisterToolType ¶
func RegisterToolType(typeName string, b ToolTypeBuilder)
RegisterToolType registers a tool type globally (e.g. "http"). Typically called from init() or at startup. Compile uses both registered types and CompileOptions.ToolTypes (option overrides take precedence).
func Validate ¶
func Validate(def *DSLDefinition) error
Validate checks that the agent definition is semantically valid: structure, references (tools, schemas, handoff agents), and optional expr syntax. Call after ParseDSL and before Compile.
func ValidateWorkflow ¶
func ValidateWorkflow(wf *WorkflowDefinition, agentNames map[string]struct{}) error
ValidateWorkflow checks that the workflow references only existing agent names. agentNames is the set of agent names from the compiled agent definition (e.g. keys of NiroDefinition.Agents).
func WithRunContext ¶
func WithRunContext(ctx context.Context, runCtx *RunContext) context.Context
WithRunContext attaches runCtx to ctx so tool handlers (e.g. HTTP) can expand URL/headers with the same template data (session, event, etc.) as prompts.
Types ¶
type AgentConfig ¶
type AgentConfig struct {
Model string `json:"model"`
ModelConfig ModelConfig `json:"model_config"`
Prompt PromptConfig `json:"prompt"`
Tools []AgentToolRef `json:"tools"`
ToolChoice string `json:"tool_choice"`
Output OutputConfig `json:"output"`
Limits LimitsConfig `json:"limits"`
}
AgentConfig is the configuration for one agent in the DSL.
type AgentToolRef ¶
type AgentToolRef struct {
Name string `json:"name"`
When string `json:"when,omitempty"`
Unless string `json:"unless,omitempty"`
}
AgentToolRef references a tool by name with optional when/unless (expr expressions).
type AgentTools ¶
type AgentTools struct {
Toolset *tools.Toolset
ToolChoice niro.ToolChoice
Options tools.ToolStreamOptions
}
AgentTools is the resolved tool responsibility for one agent run: the toolset to attach to the request, tool choice, and tool stream options. Composable with niro: toolset.Apply(req) then ToolingProvider(provider, toolset, opts).Generate(ctx, req).
type CompileOptions ¶
type CompileOptions struct {
// ToolHandlers maps tool name to handler for "code" type tools. Used when a tool
// has no "type" or type "code" (args only, handler from here).
ToolHandlers map[string]tools.ToolHandler
// ToolTypes maps type name to builder (e.g. "http" -> HTTPToolType). Merged with
// default registry (RegisterToolType). Option entries override defaults.
ToolTypes map[string]ToolTypeBuilder
}
CompileOptions configures compilation of the agent definition.
type CompiledAgentConfig ¶
type CompiledAgentConfig struct {
Model string
Options niro.Options
PromptTemplate string
ToolRefs []AgentToolRef
ToolChoice niro.ToolChoice
Limits LimitsConfig
Output OutputConfig
}
CompiledAgentConfig is the compiled config for one agent (used by Runner to build Request).
type CompiledWorkflow ¶
type CompiledWorkflow struct {
Type string // "fan" | "race" | "sequence" | "fan_then"
Agents []string // agent names (for fan_then: parallel agents)
ThenAgent string // for fan_then: synthesizer agent
// contains filtered or unexported fields
}
CompiledWorkflow is a runnable workflow (fan, race, sequence, fan_then) that uses a Runner.
func (*CompiledWorkflow) BindRunner ¶
func (c *CompiledWorkflow) BindRunner(r *Runner)
BindRunner attaches a Runner to this workflow so Run can execute.
type DSLDefinition ¶
type DSLDefinition struct {
// Tools is map of tool name -> raw spec. Each spec can have "type" (e.g. "http", "code")
// and type-specific fields. Omit "type" or use "code" for custom handlers from ToolHandlers.
Tools map[string]json.RawMessage `json:"tools"`
Schemas map[string]json.RawMessage `json:"schemas"`
Agents map[string]AgentConfig `json:"agents"`
}
DSLDefinition is the top-level agent definition (tools, schemas, agents). Parse from JSON with ParseDSL; validate with Validate; compile with Compile.
func ParseDSL ¶
func ParseDSL(data []byte) (*DSLDefinition, error)
ParseDSL unmarshals agent definition JSON into DSLDefinition. No semantic validation is performed; call Validate after ParseDSL.
func ParseDSLFile ¶
func ParseDSLFile(path string) (*DSLDefinition, error)
ParseDSLFile reads and parses an agent definition file (JSON).
func (*DSLDefinition) Compile ¶
func (def *DSLDefinition) Compile(opts CompileOptions) (*NiroDefinition, error)
Compile converts a validated DSLDefinition into NiroDefinition (per-agent config + Toolset).
type HTTPToolSpec ¶
type HTTPToolSpec struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`
Input json.RawMessage `json:"input"`
Output string `json:"output,omitempty"` // "text" | "json" (default): text = raw body; json = cases/parse or decode
Method string `json:"method"`
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
Timeout string `json:"timeout,omitempty"`
Cases []httpCase `json:"cases,omitempty"`
Retry *struct {
MaxRetries int `json:"max_retries"`
Backoff string `json:"backoff"`
} `json:"retry,omitempty"`
}
HTTPToolSpec is the DSL shape for type "http" tools (flat: no schema/http wrappers). Input = tool param JSON Schema. Method, url, headers, timeout at top level. All string fields are Go text/template with RunContext + call args as data. Output: "text" = return raw response body as string; "json" or omit = use cases/parse or decode JSON.
type HandoffSpec ¶
type HandoffSpec struct {
Type string `json:"type"`
Description string `json:"description"`
Allow []string `json:"allow,omitempty"` // shorthand: target enum when args not used
Args map[string]any `json:"args,omitempty"` // full args; if present, used for schema
}
HandoffSpec is the DSL shape for type "handoff" tools. Handoff is a tool like any other in the DSL: define it in agents.json with type "handoff", description, and args/allow; the model calls it; the handler returns tools.HandoffSignal so the tool loop emits a handoff frame. The runner executes the target via RunHandoff. Description is a template string (expanded at request time with RunContext). Either provide args.target with type, description, enum, or use allow as shorthand for target enum.
type LimitsConfig ¶
type LimitsConfig struct {
MaxToolCalls int `json:"max_tool_calls"`
MaxParallelTools int `json:"max_parallel_tools"`
}
LimitsConfig enforces max tool calls and optional max parallel tools.
type ModelConfig ¶
type ModelConfig struct {
Temperature *float64 `json:"temperature"`
MaxTokens int `json:"max_tokens"`
ThinkingBudget *int `json:"thinking_budget"`
}
ModelConfig maps to niro.Options (temperature, max_tokens, thinking_budget).
type NiroDefinition ¶
type NiroDefinition struct {
// Agents holds compiled config per agent name.
Agents map[string]*CompiledAgentConfig
// Toolset is the shared toolset from DSL tools + optional handlers.
Toolset *tools.Toolset
}
NiroDefinition is the compiled agent definition: per-agent config and optional Toolset.
type OutputConfig ¶
type OutputConfig struct {
Allow []string `json:"allow"`
Modalities []string `json:"modalities"`
Schemas map[string]string `json:"schemas"`
Stream bool `json:"stream"`
}
OutputConfig describes allowed output types, modalities, and schema refs.
type PromptConfig ¶
type PromptConfig struct {
Template string `json:"template"`
}
PromptConfig holds the system prompt; Template is a Go text/template string (RunContext as data).
type RunContext ¶
type RunContext struct {
// contains filtered or unexported fields
}
RunContext holds user-defined variables for template expansion and expr evaluation. Reserved keys: "session", "event", "history". Caller may set any additional keys. Both the prompt template and when/unless conditions read from RunContext. Safe for concurrent use if each invocation uses its own RunContext.
func RunContextFrom ¶
func RunContextFrom(ctx context.Context) *RunContext
RunContextFrom returns the RunContext attached to ctx by WithRunContext, or nil.
func (*RunContext) Get ¶
func (c *RunContext) Get(key string) (any, bool)
Get returns the value for key and whether it was set.
func (*RunContext) Set ¶
func (c *RunContext) Set(key string, value any)
Set sets a variable by key. Use reserved keys "session", "event", "history" for template and expr; set custom keys as needed (e.g. "user").
func (*RunContext) Snapshot ¶
func (c *RunContext) Snapshot() map[string]any
Snapshot returns a copy of the context as a map (e.g. for Sequence step cloning).
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner runs agents from a compiled NiroDefinition using a provider. Stream returns a niro.Stream for composition with orchestrate.Fan/Race/Sequence. Tools are a composable responsibility; handoff targets are resolved from JSON (workflows + agents) via BindWorkflows and RunHandoff.
func NewRunner ¶
func NewRunner(nd *NiroDefinition, provider niro.Provider, opts ...RunnerOption) *Runner
NewRunner creates a Runner that uses the given provider for all agents. Options can customize tools per agent (WithToolsetTransform, WithToolOptions).
func (*Runner) BindWorkflows ¶
func (r *Runner) BindWorkflows(workflows map[string]*CompiledWorkflow)
BindWorkflows registers compiled workflows (from workflow.json) so RunHandoff can execute them by name. Call after compiling workflows and binding each to this runner: for _, cw := range workflows { cw.BindRunner(runner) }; runner.BindWorkflows(workflows).
func (*Runner) Fan ¶
func (r *Runner) Fan(ctx context.Context, runCtx *RunContext, sessionID string, agentNames ...string) *niro.Stream
Fan runs the given agents in parallel and merges their streams (orchestrate.Fan).
func (*Runner) FanCollect ¶
func (r *Runner) FanCollect(ctx context.Context, runCtx *RunContext, sessionID string, agentNames []string) (texts []string, usages []niro.Usage, err error)
FanCollect runs the given agents in parallel and returns each agent's full text and usage. Results are indexed to match agentNames order regardless of completion order.
func (*Runner) FanThen ¶
func (r *Runner) FanThen(ctx context.Context, runCtx *RunContext, sessionID string, parallelAgents []string, thenAgent string) (*niro.Stream, string, niro.Usage, error)
FanThen runs the given agents in parallel, collects their responses, then runs the synthesizer agent with those responses as context. Returns the synthesizer's stream and combined usage.
func (*Runner) Race ¶
func (r *Runner) Race(ctx context.Context, runCtx *RunContext, sessionID string, agentNames ...string) (string, niro.Usage, error)
Race runs the given agents in parallel and returns the first successful text and usage.
func (*Runner) Run ¶
func (r *Runner) Run(ctx context.Context, runCtx *RunContext, agentName string, sessionID string) (string, error)
Run runs the agent and collects the full text response.
func (*Runner) RunHandoff ¶
func (r *Runner) RunHandoff(ctx context.Context, history []niro.Message, input, classifierReply, handoffTarget, sessionID string) (reply string, usage niro.Usage, err error)
RunHandoff runs the handoff target (workflow or agent) from JSON and returns the reply and usage. Targets are resolved from workflows (BindWorkflows) then agents (NiroDefinition.Agents). history and input are the conversation so far and the user message; classifierReply is the classifier agent's text before handoff.
func (*Runner) Sequence ¶
func (r *Runner) Sequence(ctx context.Context, runCtx *RunContext, sessionID string, agentNames ...string) (*niro.Stream, error)
Sequence runs the given agents in order; each step receives the previous step's collected text as input.
func (*Runner) Stream ¶
func (r *Runner) Stream(ctx context.Context, runCtx *RunContext, agentName string, sessionID string) (*niro.Stream, error)
Stream runs the named agent and returns a stream. RunContext supplies session, event, history and custom vars for template expansion and when/unless evaluation. Wiring: resolveAgentTools → optional toolset transform → req = toolset.Apply(req) → ToolingProvider.Generate(ctx, req).
type RunnerOption ¶
type RunnerOption func(*Runner)
RunnerOption customizes the runner (e.g. toolset transform, tool stream options per agent).
func WithHook ¶
func WithHook(h hook.Hook) RunnerOption
WithHook attaches a telemetry/observability hook to the runner. The hook fires OnGenerateStart before each provider call, OnFrame/OnToolCall/OnToolResult per frame, and OnGenerateEnd when the stream is exhausted. Use hook.Compose to combine multiple hooks (e.g. Langfuse + Datadog).
func WithOutputSink ¶
func WithOutputSink(sink *output.Sink) RunnerOption
WithOutputSink routes LLM output to the given sink as the stream is consumed. Response text, thinking (KindCustom "thinking"/"reasoning"), tool calls, and custom frames are dispatched to the sink's callbacks in a single pass (stream-first, no extra buffering). The returned stream still forwards all frames unchanged so handoff and history logic work as before.
func WithToolOptions ¶
func WithToolOptions(fn func(agentName string) tools.ToolStreamOptions) RunnerOption
WithToolOptions overrides tool stream options per agent (max rounds, timeout, approver, etc.).
func WithToolsetTransform ¶
func WithToolsetTransform(fn func(agentName string, base *tools.Toolset) *tools.Toolset) RunnerOption
WithToolsetTransform runs after resolving the agent's toolset; use to add approver, hooks, or replace.
type ToolTypeBuilder ¶
type ToolTypeBuilder interface {
// Build returns a ToolDefinition for the given tool name and spec.
// The spec is the raw JSON value for this tool from the DSL (e.g. {"type":"http","url":"...","args":{...}}).
Build(name string, spec json.RawMessage) (tools.ToolDefinition, error)
}
ToolTypeBuilder builds a tools.ToolDefinition from a tool's raw DSL spec. Register with RegisterToolType or pass via CompileOptions.ToolTypes so that tools with "type": "http" (or custom type names) are built by the corresponding builder.
type ToolTypeBuilderFunc ¶
type ToolTypeBuilderFunc func(name string, spec json.RawMessage) (tools.ToolDefinition, error)
ToolTypeBuilderFunc adapts a function to ToolTypeBuilder.
func (ToolTypeBuilderFunc) Build ¶
func (f ToolTypeBuilderFunc) Build(name string, spec json.RawMessage) (tools.ToolDefinition, error)
type WorkflowDefinition ¶
type WorkflowDefinition struct {
// Single-workflow form: name, description, type, agents
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"` // "fan" | "race" | "sequence"
Agents []string `json:"agents,omitempty"`
// Multi-workflow form: workflows map
Workflows map[string]WorkflowNode `json:"workflows,omitempty"`
}
WorkflowDefinition is the top-level workflow definition (separate JSON from agent def). Can represent a single workflow or a map of named workflows.
func ParseWorkflow ¶
func ParseWorkflow(data []byte) (*WorkflowDefinition, error)
ParseWorkflow unmarshals workflow definition JSON. No semantic validation; call ValidateWorkflow after.
func ParseWorkflowFile ¶
func ParseWorkflowFile(path string) (*WorkflowDefinition, error)
ParseWorkflowFile reads and parses a workflow definition file (JSON).
func (*WorkflowDefinition) CompileWorkflow ¶
func (wf *WorkflowDefinition) CompileWorkflow(agentNames map[string]struct{}) (map[string]*CompiledWorkflow, error)
CompileWorkflow compiles a workflow definition into a runnable, given the set of valid agent names. Typically call after compiling the agent definition so agentNames = keys of NiroDefinition.Agents.
type WorkflowNode ¶
type WorkflowNode struct {
Description string `json:"description,omitempty"`
Type string `json:"type"` // "fan" | "race" | "sequence" | "fan_then"
Agent string `json:"agent,omitempty"` // single agent by name (entry point)
Agents []string `json:"agents,omitempty"` // agent names (for fan/sequence/fan_then)
ThenAgent string `json:"then_agent,omitempty"` // for fan_then: agent that synthesizes parallel results
}
WorkflowNode describes one workflow: type and list of agent names. For a single-agent entry (e.g. chat), use Agent instead of Agents to avoid the array pattern.