dsl

package module
v0.0.0-...-dc0116d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 8, 2026 License: MIT Imports: 21 Imported by: 0

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

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

func CompileRunContextExpr(exprStr string) error

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

func EvalResponseCondition(exprStr string, response map[string]any) (bool, error)

EvalResponseCondition compiles and runs an expression with env {"$": response}. Used for HTTP tool cases: when is evaluated against response (status, body).

func ExecuteTemplate

func ExecuteTemplate(templateStr string, data map[string]any) (string, error)

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.

func (*CompiledWorkflow) Run

func (c *CompiledWorkflow) Run(ctx context.Context, runCtx *RunContext, sessionID string) (*niro.Stream, string, niro.Usage, error)

Run executes the workflow with the given Runner and RunContext. The Runner must be set via BindRunner before calling Run.

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 NewRunContext

func NewRunContext() *RunContext

NewRunContext creates an empty run context.

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

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL