agenttool

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 15 Imported by: 13

README

agenttool

The tool contract for Go agents over Open Responses: what a tool is, how a typed Go function becomes one, how its JSON Schema is generated and validated, how a batch of calls executes, and MCP adapters in both directions. It is the contract that agentturn runs and that any other loop can run; no loop is imported here.

  • The root module depends on openresponses and the standard library.
  • mcpclient and mcpserver are nested modules on the official MCP Go SDK.

Install

go get github.com/ChristopherDavenport/agenttool

Go 1.25 or later.

A tool

type ReadFileArgs struct {
	Path     string `json:"path" desc:"Absolute path to read"`
	MaxBytes int    `json:"max_bytes,omitempty" desc:"Stop after this many bytes"`
}

var ReadFile = agenttool.New("read_file", "Read a file from disk",
	func(ctx context.Context, a ReadFileArgs) (string, error) {
		b, err := os.ReadFile(a.Path)
		return string(b), err
	})

New reflects the schema from the argument struct at registration time. The generator understands json, desc and enum tags, nested structs, slices, maps, pointers and time.Time, and produces the strict form on request (WithStrict(): every field required, additionalProperties: false, pointers nullable). Arguments are validated against that schema before the function runs, so a missing required property or a value outside an enum reaches the model as an error it can retry on rather than a zero value.

The return type decides what the model sees: a string as text, openresponses.Contents as parts, so image-returning tools need no second constructor, and anything else as JSON. Errors returned from the function become error outputs; a tool never encodes an error as content.

The contract

type Tool interface {
	Name() string
	Description() string
	Parameters() json.RawMessage
	Execute(ctx context.Context, call Call) (Result, error)
}

Call carries the call ID, the raw arguments and an optional progress callback; Result carries the output the model sees, app-only Details, and a Terminate hint. Two optional interfaces refine a tool: Sequential forces a batch containing it to run one call at a time, and Strict marks its schema strict. Func builds a tool from plain values for schemas that come from elsewhere; Set is a list with lookup; Definition produces the openresponses.FunctionTool for a request.

Executor runs a batch: parallel up to a bound, or sequential when asked, with progress forwarded and completions yielded in completion order from the caller's goroutine. A loop that owns its own scheduling needs only the interface.

MCP

The native contract is the centre and MCP is an edge: an MCP tool is a name, a description, an input schema and a call over a transport, which is a subset of Tool. Both adapters use github.com/modelcontextprotocol/go-sdk and map, nothing more.

// Consume: a remote server's tools as Tool values.
s, err := mcpclient.Connect(ctx, &mcp.CommandTransport{Command: cmd}, mcpclient.WithPrefix("fs"))
tools := s.Tools() // fs__read, fs__write, ...

// Serve: Go tools over MCP.
server := mcpserver.NewServer("my-tools", "0.1.0", ReadFile)
server.Run(ctx, &mcp.StdioTransport{})

mcpclient maps text, image, audio and resource content to output parts, isError to a returned error, progress notifications to the call's progress callback, and refreshes its snapshot on tool-list-changed. mcpserver validates arguments against each tool's schema, maps outputs back to MCP content and forwards progress when the request carries a token. make interop checks both against the upstream reference server and the MCP Inspector over stdio.

Development

make check    # gofmt, vet, deps, staticcheck, govulncheck, race tests, every module
make interop  # upstream MCP interoperability; needs npx and the network

See CONTRIBUTING.md.

License

MIT. See LICENSE.

Documentation

Overview

Package agenttool is the tool contract for Go agents over Open Responses: what a tool is, how a typed Go function becomes one, how its JSON Schema is generated and validated, and how a batch of calls executes.

The package imports openresponses and the standard library only, so a tool written with New can be handed to any loop that speaks Open Responses. agentturn is one such loop and depends on this module; the reverse never holds, and a test enforces the boundary. The MCP adapters, mcpclient and mcpserver, are nested modules that map to and from this contract.

type ReadFileArgs struct {
	Path     string `json:"path" desc:"Absolute path to read"`
	MaxBytes int    `json:"max_bytes,omitempty" desc:"Stop after this many bytes"`
}

var ReadFile = agenttool.New("read_file", "Read a file from disk",
	func(ctx context.Context, a ReadFileArgs) (string, error) { ... })

Errors returned from Execute become error outputs the model sees; a tool never encodes an error as normal content.

Index

Constants

View Source
const DefaultMaxParallel = 8

DefaultMaxParallel bounds concurrent tool calls in a batch when the caller sets no limit.

Variables

This section is empty.

Functions

func Decode

func Decode[T any](raw json.RawMessage) (T, error)

Decode unmarshals the raw arguments into T. Empty arguments decode as an empty object. Errors are phrased for the model.

func Definition

func Definition(t Tool) *openresponses.FunctionTool

Definition builds the function tool that describes t on a request.

func IsSequential

func IsSequential(t Tool) bool

IsSequential reports whether t asks to run alone.

func IsStrict

func IsStrict(t Tool) bool

IsStrict reports whether t's schema is strict.

func Progress

func Progress(ctx context.Context, r Result)

Progress reports progress on the call attached to ctx. It is a no-op when there is no call or the caller did not ask for updates.

func SchemaFor

func SchemaFor[T any](strict bool) (json.RawMessage, error)

SchemaFor returns the JSON Schema for T. See SchemaOf.

func SchemaOf

func SchemaOf(t reflect.Type, strict bool) (json.RawMessage, error)

SchemaOf reflects a JSON Schema object for t, which must be a struct or a pointer to one, or implement Schemer.

Exported fields become properties named by their json tag. A "desc" tag becomes the description and an "enum" tag, comma separated, becomes the enum. Embedded structs are flattened. Supported kinds are bool, the integer and float kinds, string, slices and arrays, maps with string keys, nested structs, pointers, time.Time (a date-time string), []byte (a string), json.RawMessage and interfaces (any value) and types implementing encoding.TextMarshaler (a string).

In strict mode every property is required, additionalProperties is false on every object, pointer fields are nullable, and maps are rejected because strict mode cannot express them.

Outside strict mode a field is optional when its tag says omitempty or omitzero or when it is a pointer.

func WithCall

func WithCall(ctx context.Context, call Call) context.Context

WithCall attaches the call to ctx so the tool function can find it with CallFrom. Typed.Execute does this before calling the function.

Types

type Call

type Call struct {
	// ID is the call_id of the function_call item.
	ID string
	// Args is the raw JSON arguments object as the model wrote it.
	Args json.RawMessage
	// OnUpdate, when set, receives progress before the final result. It
	// may be called from the tool's goroutine; the caller serialises it.
	OnUpdate func(Result)
}

Call is one invocation of a tool.

func CallFrom

func CallFrom(ctx context.Context) (Call, bool)

CallFrom returns the call attached to ctx, if any.

func (Call) Update

func (c Call) Update(r Result)

Update reports progress to the caller when it asked for it.

type Event

type Event struct {
	Index  int
	Final  bool
	Result Result
	Err    error
}

Event is one step of a batch: a progress update when Final is false, otherwise the completion of the job at Index. Completions arrive in completion order, not job order.

type Executor

type Executor struct {
	// MaxParallel bounds concurrency; zero means [DefaultMaxParallel].
	MaxParallel int
	// Sequential forces every batch to run one job at a time in order.
	// A batch containing a [Sequential] tool runs that way regardless.
	Sequential bool
}

Executor runs batches of tool calls.

func (Executor) Execute

func (e Executor) Execute(ctx context.Context, jobs []Job) iter.Seq[Event]

Execute runs jobs and yields their events from the caller's goroutine, so a consumer never sees two events at once. Progress updates from a tool that calls Call.OnUpdate are forwarded as non-final events; the executor installs its own OnUpdate and chains to the one on the job, if any, from the yielding goroutine. A tool that panics completes with an error. Breaking out of the loop cancels the batch and waits for running tools to return.

func (Executor) Results

func (e Executor) Results(ctx context.Context, jobs []Job) ([]Result, []error)

Results collects the final results of a batch in job order. It is the convenience over Executor.Execute for callers that do not need progress.

type Func

type Func struct {
	ToolName        string
	ToolDescription string
	Schema          json.RawMessage
	Fn              func(ctx context.Context, call Call) (Result, error)
	// RunAlone marks the tool [Sequential].
	RunAlone bool
	// StrictSchema marks the tool [Strict].
	StrictSchema bool
}

Func is a Tool built from plain values and a function; the untyped counterpart of New for tools whose schema comes from elsewhere, such as a remote server.

func (*Func) Description

func (f *Func) Description() string

Description returns the tool description.

func (*Func) Execute

func (f *Func) Execute(ctx context.Context, call Call) (Result, error)

Execute calls Fn.

func (*Func) Name

func (f *Func) Name() string

Name returns the tool name.

func (*Func) Parameters

func (f *Func) Parameters() json.RawMessage

Parameters returns the schema.

func (*Func) Sequential

func (f *Func) Sequential() bool

Sequential reports RunAlone.

func (*Func) Strict

func (f *Func) Strict() bool

Strict reports StrictSchema.

type Job

type Job struct {
	Tool Tool
	Call Call
}

Job is one call of a batch: the tool to run and the call to run it with.

type NoArgs

type NoArgs struct{}

NoArgs is the argument type of a tool that takes no arguments.

type Option

type Option func(*typedOptions)

Option configures a tool built by New.

func WithParameters

func WithParameters(schema json.RawMessage) Option

WithParameters replaces the reflected schema with schema. Arguments are then not validated before decoding, because the tool cannot know what the schema promises.

func WithSequential

func WithSequential() Option

WithSequential marks the tool Sequential.

func WithStrict

func WithStrict() Option

WithStrict generates the schema under the strict rules and sets the strict flag on the function tool.

func WithoutValidation

func WithoutValidation() Option

WithoutValidation skips the argument check against the reflected schema, leaving the decoder as the only guard.

type Property

type Property struct {
	Name   string
	Schema *Schema
}

Property is one named member of an object schema.

type Result

type Result struct {
	// Output is what the model sees.
	Output openresponses.FunctionCallOutputData
	// Details is app-only data for subscribers and fronts. It is never
	// sent to the model.
	Details any
	// Terminate hints that the loop should stop after this batch instead
	// of calling the model again. The loop honours it only when every
	// result in the batch sets it.
	Terminate bool
}

Result is what a tool produced.

func ErrorResult

func ErrorResult(err error) Result

ErrorResult builds the output the model sees when a tool fails. The text form is "Error: <message>" so the model can tell it apart from a normal result and retry.

func Output

func Output(v any) (Result, error)

Output converts a Go value into a Result following the rules of New.

func Parts

func Parts(parts ...openresponses.Content) Result

Parts builds a result whose output is a list of content parts.

func Text

func Text(s string) Result

Text builds a result whose output is a string.

type Schema

type Schema struct {
	// Type is a JSON Schema type name, or empty for any value.
	Type string
	// Nullable adds "null" to the type, as strict mode requires for
	// optional fields.
	Nullable    bool
	Description string
	Format      string
	Enum        []any
	Properties  []Property
	Required    []string
	// AdditionalProperties is emitted when set: false, or a schema for
	// map values.
	AdditionalProperties *Schema
	NoAdditional         bool
	Items                *Schema
}

Schema is a JSON Schema fragment as the generator builds it. Keys are emitted in a fixed order and properties keep struct field order, so the output is stable across runs and readable in a request.

func Reflect

func Reflect(t reflect.Type, strict bool) (*Schema, error)

Reflect returns the schema tree SchemaOf serialises, for callers that want to validate with it. t must be a struct or a pointer to one; Schemer is not consulted.

func (*Schema) MarshalJSON

func (s *Schema) MarshalJSON() ([]byte, error)

MarshalJSON emits the schema with a fixed key order.

func (*Schema) Validate

func (s *Schema) Validate(v any) error

Validate checks a decoded JSON value (maps, slices, strings, json.Number or float64, bools, nil) against s: its type, enum, required properties, additionalProperties when false, and its items and properties recursively. A schema with no Type accepts anything.

func (*Schema) ValidateJSON

func (s *Schema) ValidateJSON(raw json.RawMessage) error

ValidateJSON checks raw against s. Empty raw is an empty object.

type Schemer

type Schemer interface {
	JSONSchema() json.RawMessage
}

Schemer is implemented by argument types that supply their own JSON Schema instead of the reflected one.

type Sequential

type Sequential interface {
	Sequential() bool
}

Sequential is implemented by tools that must not run alongside other tools in the same batch. When any tool in a batch reports true, the whole batch runs one call at a time in the model's order.

type Set

type Set []Tool

Set is a list of tools with lookup by name.

func (Set) Definitions

func (s Set) Definitions() openresponses.Tools

Definitions returns the function tools for a request, in order.

func (Set) Lookup

func (s Set) Lookup(name string) (Tool, bool)

Lookup returns the tool named name.

func (Set) Validate

func (s Set) Validate() error

Validate reports an empty or duplicate name.

type Strict

type Strict interface {
	Strict() bool
}

Strict is implemented by tools whose schema was generated under the strict rules (every field required, additionalProperties false, optional fields nullable). The flag is set on the function tool.

type Tool

type Tool interface {
	Name() string
	Description() string
	// Parameters is the JSON Schema of the arguments object. nil means
	// the tool takes no arguments.
	Parameters() json.RawMessage
	Execute(ctx context.Context, call Call) (Result, error)
}

Tool is something the model can call. Name and Parameters become the function tool on the request; Execute runs one call.

func New

func New[Args, Out any](name, description string, fn func(context.Context, Args) (Out, error), opts ...Option) Tool

New builds a Tool from a typed function. The schema is reflected from Args at registration time (see SchemaOf); a type that cannot be expressed panics here rather than at call time, like a bad regexp in regexp.MustCompile. Call.Args is validated against that schema, so a missing required property, a wrong type or a value outside an enum is returned as an error the model can retry on, then decoded into Args with the standard decoder. Properties the schema does not name are ignored, or rejected under WithStrict, whose schema says so.

Out maps to the output the model sees: a string passes through as text, openresponses.Contents goes out as parts, a Result or an openresponses.FunctionCallOutputData is used as is, and anything else is marshalled to JSON.

The function can reach its Call through CallFrom on the context, for the call ID or to report progress.

type Typed

type Typed[Args, Out any] struct {
	// contains filtered or unexported fields
}

Typed is the Tool returned by New.

func (*Typed[Args, Out]) Description

func (t *Typed[Args, Out]) Description() string

Description returns the tool description.

func (*Typed[Args, Out]) Execute

func (t *Typed[Args, Out]) Execute(ctx context.Context, call Call) (Result, error)

Execute validates and decodes the arguments, calls the function and converts the output.

func (*Typed[Args, Out]) Name

func (t *Typed[Args, Out]) Name() string

Name returns the tool name.

func (*Typed[Args, Out]) Parameters

func (t *Typed[Args, Out]) Parameters() json.RawMessage

Parameters returns the argument schema.

func (*Typed[Args, Out]) Sequential

func (t *Typed[Args, Out]) Sequential() bool

Sequential reports whether the tool runs alone.

func (*Typed[Args, Out]) Strict

func (t *Typed[Args, Out]) Strict() bool

Strict reports whether the schema is strict.

type ValidationError

type ValidationError struct {
	// Path locates the offending value: "" for the root, otherwise a
	// dotted property path with [i] for array elements.
	Path string
	Msg  string
}

ValidationError reports arguments that do not satisfy a schema. Its message is phrased for the model, which sees it as the error output and can retry.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error returns "invalid arguments: <path>: <msg>".

Directories

Path Synopsis
mcpclient module
mcpserver module

Jump to

Keyboard shortcuts

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