callbacks

package
v0.9.13 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package callbacks provides lightweight callback types for AI agent tool operations.

This package contains the callback interfaces and data types used by tools without importing AI SDK dependencies. Packages that need to provide callback implementations (like clonemanager and changemanager) can import this package without pulling in anthropic-sdk-go or google.golang.org/genai.

For the full tool provider pattern with AI SDK integration, import the parent toolcall package instead.

Worktree Callbacks

WorktreeCallbacks provides file operations on a git worktree:

cb := callbacks.WorktreeCallbacks{
	ReadFile: func(ctx context.Context, path string, offset int64, limit int) (ReadResult, error) {
		// Read file from worktree with offset/limit windowing
	},
	WriteFile: func(ctx context.Context, path, content string, mode os.FileMode) error {
		// Write file to worktree and stage it
	},
	// ... other callbacks (DeleteFile, MoveFile, CopyFile, etc.)
}

Finding Callbacks

FindingCallbacks provides access to CI failure information:

cb := callbacks.FindingCallbacks{
	Findings: findings, // List of findings for lookup by extensions
	GetDetails: func(ctx context.Context, kind FindingKind, id string) (string, error) {
		// Return pre-fetched finding details
	},
	GetLogs: func(ctx context.Context, kind FindingKind, id string) (string, error) {
		// Fetch and return logs for the finding
	},
}

Result Validators

ResultValidator inspects a result an agent submitted via its terminal submit tool and returns findings describing why it is not acceptable; an empty return accepts the result. Executors run every registered validator concurrently (ValidateResult) when the model calls the submit tool, and reject the submission back to the model (RejectionResult) when any validator returns findings — keeping the agent loop going until a submission passes:

validator := func(ctx context.Context, r Report, reasoning string) ([]callbacks.Finding, error) {
	if r.Answer == "" {
		return []callbacks.Finding{{
			Kind:       callbacks.FindingKindReview,
			Identifier: "empty-answer",
			Details:    "the answer field is empty",
		}}, nil
	}
	return nil, nil
}

History Callbacks

HistoryCallbacks provides access to commit history and file diffs:

cb := callbacks.HistoryCallbacks{
	ListCommits: func(ctx context.Context, offset, limit int) (CommitListResult, error) {
		// List commits since base ref with pagination
	},
	GetFileDiff: func(ctx context.Context, path, start, end string, offset int64, limit int) (FileDiffResult, error) {
		// Return paginated unified diff for a file over a commit range
	},
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func RejectionResult added in v0.7.59

func RejectionResult(submitToolName string, findings []Finding) map[string]any

RejectionResult builds the tool result a submit tool returns to the model when validators rejected its submission. The findings ride alongside the error text so the model sees exactly what to address before calling the submit tool again.

Types

type CommitFile

type CommitFile struct {
	// Path is the file path relative to the repository root.
	Path string

	// OldPath is the previous path for renamed files. Empty otherwise.
	OldPath string

	// Type is the change type: "added", "modified", "deleted", or "renamed".
	Type string

	// DiffSize is the size of this file's unified diff in bytes.
	DiffSize int
}

CommitFile describes a file changed in a single commit.

Example

ExampleCommitFile demonstrates the CommitFile type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	file := callbacks.CommitFile{
		Path:     "main.go",
		OldPath:  "",
		Type:     "modified",
		DiffSize: 512,
	}

	fmt.Printf("Path: %s\n", file.Path)
	fmt.Printf("Type: %s\n", file.Type)
	fmt.Printf("Diff size: %d bytes\n", file.DiffSize)

}
Output:
Path: main.go
Type: modified
Diff size: 512 bytes

type CommitInfo

type CommitInfo struct {
	// SHA is the short (7-character) commit hash.
	SHA string

	// Message is the full commit message.
	Message string

	// Files is the list of files changed in this commit.
	Files []CommitFile
}

CommitInfo describes a single commit in the history.

Example

ExampleCommitInfo demonstrates the CommitInfo type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	commit := callbacks.CommitInfo{
		SHA:     "abc1234",
		Message: "feat: add feature",
		Files: []callbacks.CommitFile{{
			Path:     "main.go",
			Type:     "modified",
			DiffSize: 256,
		}},
	}

	fmt.Printf("SHA: %s\n", commit.SHA)
	fmt.Printf("Message: %s\n", commit.Message)
	fmt.Printf("Files changed: %d\n", len(commit.Files))

}
Output:
SHA: abc1234
Message: feat: add feature
Files changed: 1

type CommitListResult

type CommitListResult struct {
	// Commits is the list of commits in this page.
	Commits []CommitInfo

	// NextOffset is the item offset to resume listing from.
	// Nil when there are no more commits.
	NextOffset *int

	// Total is the total number of commits in the range.
	Total int
}

CommitListResult is the result of a ListCommits callback call.

Example

ExampleCommitListResult demonstrates the CommitListResult type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	result := callbacks.CommitListResult{
		Commits: []callbacks.CommitInfo{{
			SHA:     "abc1234",
			Message: "feat: add feature",
		}},
		NextOffset: nil,
		Total:      1,
	}

	fmt.Printf("Commits: %d\n", len(result.Commits))
	fmt.Printf("Total: %d\n", result.Total)
	fmt.Printf("Has more: %v\n", result.NextOffset != nil)

}
Output:
Commits: 1
Total: 1
Has more: false

type DirEntry

type DirEntry struct {
	// Name is the entry name (no path prefix).
	Name string

	// Size is the file size in bytes (0 for directories and symlinks).
	Size int64

	// Mode is the file mode (e.g., 0644, 0755).
	Mode os.FileMode

	// Type is "file", "directory", or "symlink".
	Type string

	// Target is the symlink target, empty for non-symlinks.
	Target string
}

DirEntry represents a single entry in a directory listing.

Example

ExampleDirEntry demonstrates the DirEntry type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	entry := callbacks.DirEntry{
		Name:   "example.go",
		Size:   1024,
		Mode:   0644,
		Type:   "file",
		Target: "",
	}

	fmt.Printf("Name: %s\n", entry.Name)
	fmt.Printf("Size: %d bytes\n", entry.Size)
	fmt.Printf("Mode: %o\n", entry.Mode)
	fmt.Printf("Type: %s\n", entry.Type)

}
Output:
Name: example.go
Size: 1024 bytes
Mode: 644
Type: file

type EditResult

type EditResult struct {
	// Replacements is the number of occurrences that were replaced.
	Replacements int
}

EditResult is the result of an EditFile callback call.

Example

ExampleEditResult demonstrates the EditResult type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	result := callbacks.EditResult{
		Replacements: 3,
	}

	fmt.Printf("Replacements: %d\n", result.Replacements)

}
Output:
Replacements: 3

type FileDiffResult

type FileDiffResult struct {
	// Diff is the unified diff text for this page.
	Diff string

	// NextOffset is the byte offset to resume reading from.
	// Nil when the entire diff has been returned.
	NextOffset *int64

	// Remaining is the number of bytes remaining after NextOffset.
	Remaining int64
}

FileDiffResult is the result of a GetFileDiff callback call.

Example

ExampleFileDiffResult demonstrates the FileDiffResult type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	result := callbacks.FileDiffResult{
		Diff:       "diff --git a/main.go b/main.go\n......\n",
		NextOffset: nil,
		Remaining:  0,
	}

	fmt.Printf("Diff length: %d bytes\n", len(result.Diff))
	fmt.Printf("Has more: %v\n", result.NextOffset != nil)

}
Output:
Diff length: 38 bytes
Has more: false

type Finding

type Finding struct {
	// Kind identifies the type of finding.
	Kind FindingKind `json:"kind" xml:"kind"`

	// Identifier is an opaque string that uniquely identifies this finding.
	Identifier string `json:"identifier" xml:"identifier"`

	// Name is a short human-readable label for the finding, suitable for
	// rendering by downstream consumers. For CI checks it is the check
	// run name (e.g. "Lint"). For review findings it is a path:line
	// locator or author handle. Empty when the construction site has no
	// natural label (callers should fall back to Identifier or render
	// "(unnamed)" — both are valid).
	Name string `json:"name,omitempty" xml:"name,omitempty"`

	// Details contains pre-fetched information about the finding.
	// For CI checks: name, status, conclusion, title, summary, text, detailsUrl
	Details string `json:"details" xml:"details"`

	// DetailsURL is the URL to the finding's details page (e.g., GitHub Actions job URL).
	// Used by GetLogs to fetch logs for the finding.
	DetailsURL string `json:"details_url" xml:"details_url"`
}

Finding represents an issue that needs to be addressed. All details are populated at query time to avoid subsequent API calls.

Example

ExampleFinding demonstrates the Finding type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	finding := callbacks.Finding{
		Kind:       callbacks.FindingKindCICheck,
		Identifier: "job-123",
		Details:    "Build failed",
		DetailsURL: "https://example.com/job/123",
	}

	fmt.Printf("Kind: %s\n", finding.Kind)
	fmt.Printf("ID: %s\n", finding.Identifier)
	fmt.Printf("Details: %s\n", finding.Details)

}
Output:
Kind: ciCheck
ID: job-123
Details: Build failed

func ValidateResult added in v0.7.59

func ValidateResult[Response any](ctx context.Context, validators []ResultValidator[Response], response Response, reasoning string) ([]Finding, error)

ValidateResult runs the validators concurrently against a submitted result and returns their findings concatenated in validator registration order. An empty return means every validator accepted the result. The first validator error cancels the remaining validators and is returned; findings from validators that succeeded are discarded in that case.

Example

ExampleValidateResult demonstrates running result validators against a submitted result and building the rejection returned to the model.

package main

import (
	"context"
	"fmt"
	"strings"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	ctx := context.Background()

	type report struct {
		Answer string
	}

	// Validators run concurrently; each returns findings describing why the
	// result is unacceptable, or nothing to accept it.
	validators := []callbacks.ResultValidator[report]{
		func(_ context.Context, r report, _ string) ([]callbacks.Finding, error) {
			if r.Answer == "" {
				return []callbacks.Finding{{
					Kind:       callbacks.FindingKindReview,
					Identifier: "empty-answer",
					Details:    "the answer field is empty",
				}}, nil
			}
			return nil, nil
		},
	}

	findings, err := callbacks.ValidateResult(ctx, validators, report{}, "the result is complete")
	if err != nil {
		fmt.Printf("validator failed: %v\n", err)
		return
	}

	if len(findings) > 0 {
		rejection := callbacks.RejectionResult("submit_result", findings)
		fmt.Printf("rejected with %d finding(s)\n", len(findings))
		fmt.Printf("error mentions tool: %v\n", strings.Contains(rejection["error"].(string), "submit_result"))
		return
	}
	fmt.Println("accepted")

}
Output:
rejected with 1 finding(s)
error mentions tool: true

type FindingCallbacks

type FindingCallbacks struct {
	// Findings is the list of findings available in this context.
	// This allows extensions to access finding metadata (like DetailsURL)
	// without requiring a lookup callback.
	Findings []Finding

	// GetDetails retrieves detailed information about a finding.
	// Since details are pre-fetched in the GraphQL query, this just
	// looks up the finding by identifier and returns its Details field.
	GetDetails func(ctx context.Context, kind FindingKind, identifier string) (string, error)

	// GetLogs fetches logs for a finding (e.g., GitHub Actions job logs).
	// Returns the cleaned log content as a string.
	GetLogs func(ctx context.Context, kind FindingKind, identifier string) (string, error)

	// Resolve marks a review thread finding as resolved by calling the
	// GitHub resolveReviewThread mutation.
	Resolve func(ctx context.Context, identifier string) error

	// Retry triggers a retry of a failed finding (e.g., rerunning a flaky CI check).
	Retry func(ctx context.Context, kind FindingKind, identifier string) error
}

FindingCallbacks provides callbacks for fetching finding details.

Example

ExampleFindingCallbacks demonstrates how to use FindingCallbacks for accessing CI failure information.

package main

import (
	"context"
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	ctx := context.Background()

	findings := []callbacks.Finding{{
		Kind:       callbacks.FindingKindCICheck,
		Identifier: "test-job-123",
		Details:    "Test failed: assertion error",
		DetailsURL: "https://example.com/job/123",
	}, {
		Kind:       callbacks.FindingKindReview,
		Identifier: "review-456",
		Details:    "Code review feedback",
		DetailsURL: "https://example.com/review/456",
	}}

	cb := callbacks.FindingCallbacks{
		Findings: findings,
		GetDetails: func(ctx context.Context, kind callbacks.FindingKind, identifier string) (string, error) {
			for _, f := range findings {
				if f.Kind == kind && f.Identifier == identifier {
					return f.Details, nil
				}
			}
			return "", fmt.Errorf("finding not found")
		},
		GetLogs: func(ctx context.Context, kind callbacks.FindingKind, identifier string) (string, error) {
			return "log output for " + identifier, nil
		},
	}

	// Check if callbacks are available
	fmt.Printf("Has GetDetails: %v\n", cb.HasGetDetails())
	fmt.Printf("Has GetLogs: %v\n", cb.HasGetLogs())

	// Look up a finding
	finding := cb.GetFinding(callbacks.FindingKindCICheck, "test-job-123")
	if finding != nil {
		fmt.Printf("Found: %s\n", finding.Identifier)
	}

	// Get details and logs
	details, _ := cb.GetDetails(ctx, callbacks.FindingKindCICheck, "test-job-123")
	fmt.Println(details)

	logs, _ := cb.GetLogs(ctx, callbacks.FindingKindCICheck, "test-job-123")
	fmt.Println(logs)

}
Output:
Has GetDetails: true
Has GetLogs: true
Found: test-job-123
Test failed: assertion error
log output for test-job-123

func (FindingCallbacks) GetFinding

func (f FindingCallbacks) GetFinding(kind FindingKind, identifier string) *Finding

GetFinding looks up a finding by kind and identifier. Returns nil if not found.

Example

ExampleFindingCallbacks_GetFinding demonstrates the GetFinding method.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	cb := callbacks.FindingCallbacks{
		Findings: []callbacks.Finding{{
			Kind:       callbacks.FindingKindCICheck,
			Identifier: "job-123",
			Details:    "Build failed",
		}, {
			Kind:       callbacks.FindingKindReview,
			Identifier: "review-456",
			Details:    "Code review",
		}},
	}

	// Find existing finding
	finding := cb.GetFinding(callbacks.FindingKindCICheck, "job-123")
	if finding != nil {
		fmt.Printf("Found: %s\n", finding.Details)
	}

	// Try to find non-existent finding
	notFound := cb.GetFinding(callbacks.FindingKindCICheck, "job-999")
	fmt.Printf("Not found: %v\n", notFound == nil)

}
Output:
Found: Build failed
Not found: true

func (FindingCallbacks) HasGetDetails

func (f FindingCallbacks) HasGetDetails() bool

HasGetDetails returns true if the GetDetails callback is available.

Example

ExampleFindingCallbacks_HasGetDetails demonstrates the HasGetDetails method.

package main

import (
	"context"
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	cb := callbacks.FindingCallbacks{
		GetDetails: func(ctx context.Context, kind callbacks.FindingKind, identifier string) (string, error) {
			return "details", nil
		},
	}

	fmt.Printf("Has GetDetails: %v\n", cb.HasGetDetails())

	cbEmpty := callbacks.FindingCallbacks{}
	fmt.Printf("Empty has GetDetails: %v\n", cbEmpty.HasGetDetails())

}
Output:
Has GetDetails: true
Empty has GetDetails: false

func (FindingCallbacks) HasGetLogs

func (f FindingCallbacks) HasGetLogs() bool

HasGetLogs returns true if the GetLogs callback is available.

Example

ExampleFindingCallbacks_HasGetLogs demonstrates the HasGetLogs method.

package main

import (
	"context"
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	cb := callbacks.FindingCallbacks{
		GetLogs: func(ctx context.Context, kind callbacks.FindingKind, identifier string) (string, error) {
			return "logs", nil
		},
	}

	fmt.Printf("Has GetLogs: %v\n", cb.HasGetLogs())

	cbEmpty := callbacks.FindingCallbacks{}
	fmt.Printf("Empty has GetLogs: %v\n", cbEmpty.HasGetLogs())

}
Output:
Has GetLogs: true
Empty has GetLogs: false

func (FindingCallbacks) HasResolve added in v0.2.0

func (f FindingCallbacks) HasResolve() bool

HasResolve returns true if the Resolve callback is available.

func (FindingCallbacks) HasRetry added in v0.2.0

func (f FindingCallbacks) HasRetry() bool

HasRetry returns true if the Retry callback is available.

type FindingKind

type FindingKind string

FindingKind identifies the type of finding.

Example

ExampleFindingKind demonstrates the FindingKind constants.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	ciCheck := callbacks.FindingKindCICheck
	review := callbacks.FindingKindReview

	fmt.Printf("CI Check: %s\n", ciCheck)
	fmt.Printf("Review: %s\n", review)

}
Output:
CI Check: ciCheck
Review: review
const (
	// FindingKindCICheck indicates a CI check failure.
	FindingKindCICheck FindingKind = "ciCheck"

	// FindingKindReview indicates a code review with feedback.
	FindingKindReview FindingKind = "review"
)

type HistoryCallbacks

type HistoryCallbacks struct {
	// ListCommits lists commits from the base ref to HEAD in reverse
	// chronological order. Each commit includes its changed files with
	// diff sizes. Results are paginated by offset and limit.
	ListCommits func(ctx context.Context, offset, limit int) (CommitListResult, error)

	// GetFileDiff returns the unified diff for a single file over a
	// commit range. Pass empty start for base ref, empty end for HEAD.
	// Results are paginated by byte offset and limit.
	GetFileDiff func(ctx context.Context, path, start, end string, offset int64, limit int) (FileDiffResult, error)
}

HistoryCallbacks provides callback functions for reading commit history relative to a fixed base ref determined at construction time.

Example

ExampleHistoryCallbacks demonstrates how to use HistoryCallbacks for accessing commit history.

package main

import (
	"context"
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	ctx := context.Background()

	cb := callbacks.HistoryCallbacks{
		ListCommits: func(ctx context.Context, offset, limit int) (callbacks.CommitListResult, error) {
			return callbacks.CommitListResult{
				Commits: []callbacks.CommitInfo{{
					SHA:     "abc1234",
					Message: "feat: add new feature",
					Files: []callbacks.CommitFile{{
						Path:     "main.go",
						Type:     "modified",
						DiffSize: 256,
					}},
				}},
				NextOffset: nil,
				Total:      1,
			}, nil
		},
		GetFileDiff: func(ctx context.Context, path, start, end string, offset int64, limit int) (callbacks.FileDiffResult, error) {
			return callbacks.FileDiffResult{
				Diff:       "diff --git a/main.go b/main.go\n......\n",
				NextOffset: nil,
				Remaining:  0,
			}, nil
		},
	}

	// List commits
	result, _ := cb.ListCommits(ctx, 0, 20)
	fmt.Printf("Total commits: %d\n", result.Total)
	if len(result.Commits) > 0 {
		fmt.Printf("First commit: %s\n", result.Commits[0].SHA)
		fmt.Printf("Message: %s\n", result.Commits[0].Message)
		if len(result.Commits[0].Files) > 0 {
			fmt.Printf("Changed file: %s (%s)\n", result.Commits[0].Files[0].Path, result.Commits[0].Files[0].Type)
		}
	}

	// Get file diff
	diffResult, _ := cb.GetFileDiff(ctx, "main.go", "", "", 0, 20000)
	fmt.Printf("Diff length: %d bytes\n", len(diffResult.Diff))

}
Output:
Total commits: 1
First commit: abc1234
Message: feat: add new feature
Changed file: main.go (modified)
Diff length: 38 bytes

type ListResult

type ListResult struct {
	// Entries is the list of directory entries in this page.
	Entries []DirEntry

	// NextOffset is the item offset to resume listing from.
	// Nil when there are no more entries.
	NextOffset *int

	// Remaining is the number of entries remaining after NextOffset.
	// 0 when there are no more entries.
	Remaining int
}

ListResult is the result of a ListDirectory callback call.

Example

ExampleListResult demonstrates the ListResult type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	result := callbacks.ListResult{
		Entries: []callbacks.DirEntry{{
			Name: "file1.go",
			Size: 512,
			Mode: 0644,
			Type: "file",
		}, {
			Name: "file2.go",
			Size: 1024,
			Mode: 0644,
			Type: "file",
		}},
		NextOffset: nil,
		Remaining:  0,
	}

	fmt.Printf("Entries: %d\n", len(result.Entries))
	fmt.Printf("Has more: %v\n", result.NextOffset != nil)

}
Output:
Entries: 2
Has more: false

type Match

type Match struct {
	// Path is the file path relative to the worktree root.
	Path string `json:"path"`

	// Offset is the byte offset of the match in the file.
	Offset int64 `json:"offset"`

	// Length is the byte length of the matched text.
	Length int `json:"length"`
}

Match represents a search hit from SearchCodebase.

Example

ExampleMatch demonstrates the Match type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	match := callbacks.Match{
		Path:   "main.go",
		Offset: 256,
		Length: 10,
	}

	fmt.Printf("Path: %s\n", match.Path)
	fmt.Printf("Offset: %d\n", match.Offset)
	fmt.Printf("Length: %d\n", match.Length)

}
Output:
Path: main.go
Offset: 256
Length: 10

type ReadResult

type ReadResult struct {
	// Content is the file content read from the given offset.
	Content string

	// NextOffset is the byte offset to resume reading from.
	// Nil when EOF was reached.
	NextOffset *int64

	// Remaining is the number of bytes remaining after NextOffset.
	// 0 when EOF was reached.
	Remaining int64
}

ReadResult is the result of a ReadFile callback call.

Example

ExampleReadResult demonstrates the ReadResult type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	nextOffset := int64(1024)
	result := callbacks.ReadResult{
		Content:    "file content",
		NextOffset: &nextOffset,
		Remaining:  512,
	}

	fmt.Printf("Content: %s\n", result.Content)
	fmt.Printf("Next offset: %d\n", *result.NextOffset)
	fmt.Printf("Remaining: %d bytes\n", result.Remaining)

}
Output:
Content: file content
Next offset: 1024
Remaining: 512 bytes

type ResultValidator added in v0.7.59

type ResultValidator[Response any] func(ctx context.Context, response Response, reasoning string) ([]Finding, error)

ResultValidator inspects a result an agent submitted via its terminal submit tool and returns findings describing why the result is not acceptable. An empty return means the validator accepts the result.

Validators are registered on an executor (one or more, via its WithResultValidator option) and run when the model calls the submit tool with a payload that parses into the Response type. When any validator returns findings, the result is rejected: it does not become the run's final result, and the findings are returned to the model as the submit tool's result so it can address them and submit again. When every validator returns no findings, the result commits and the run ends.

The reasoning argument is the model's own justification for why it believes the result is complete and accurate (the submit tool's universal reasoning parameter) — useful input for validators that judge the result rather than check invariants.

A non-nil error reports that the validator itself failed (for example an upstream API error), not that the result is unacceptable; it aborts the run. Validators must be safe for concurrent use: they run in parallel with each other. The executors begin evaluating a submission only after the turn's other tool handlers have completed, so validators that read state those handlers produce (worktrees, files) observe the finished state rather than racing them.

type SearchResult

type SearchResult struct {
	// Matches is the list of search hits in this page.
	Matches []Match

	// NextOffset is the item offset to resume searching from.
	// Nil when there are no more matches.
	NextOffset *int

	// HasMore indicates that additional matches exist beyond this page.
	HasMore bool
}

SearchResult is the result of a SearchCodebase callback call.

Example

ExampleSearchResult demonstrates the SearchResult type.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	result := callbacks.SearchResult{
		Matches: []callbacks.Match{{
			Path:   "main.go",
			Offset: 100,
			Length: 5,
		}},
		NextOffset: nil,
		HasMore:    false,
	}

	fmt.Printf("Matches: %d\n", len(result.Matches))
	fmt.Printf("Has more: %v\n", result.HasMore)

}
Output:
Matches: 1
Has more: false

type WorktreeCallbacks

type WorktreeCallbacks struct {
	// ReadFile reads content from a file starting at the given byte offset,
	// returning up to limit bytes. Pass limit=-1 to read the entire file.
	ReadFile func(ctx context.Context, path string, offset int64, limit int) (ReadResult, error)

	// WriteFile creates or overwrites a file with the given content and mode.
	WriteFile func(ctx context.Context, path, content string, mode os.FileMode) error

	// DeleteFile removes a file.
	DeleteFile func(ctx context.Context, path string) error

	// MoveFile moves/renames a file from src to dst.
	MoveFile func(ctx context.Context, src, dst string) error

	// CopyFile copies a file from src to dst.
	CopyFile func(ctx context.Context, src, dst string) error

	// CreateSymlink creates a symbolic link at path pointing to target.
	CreateSymlink func(ctx context.Context, path, target string) error

	// Chmod changes the file mode of the file at path.
	Chmod func(ctx context.Context, path string, mode os.FileMode) error

	// ListDirectory lists directory entries with metadata. The filter
	// parameter supports glob patterns (with * wildcards) or exact name
	// matching. Results are paginated by offset and limit.
	ListDirectory func(ctx context.Context, path, filter string, offset, limit int) (ListResult, error)

	// EditFile replaces occurrences of oldString with newString in the file
	// at path. When replaceAll is false, oldString must appear exactly once
	// in the file. Changes are automatically staged.
	EditFile func(ctx context.Context, path, oldString, newString string, replaceAll bool) (EditResult, error)

	// SearchCodebase searches for a regex pattern across files. The filter
	// parameter supports glob patterns (with * wildcards) or exact filename
	// matching. Results are paginated by offset and limit. Match results
	// contain byte offsets into files (no content).
	SearchCodebase func(ctx context.Context, path, pattern, filter string, offset, limit int) (SearchResult, error)
}

WorktreeCallbacks provides callback functions for file operations on a worktree. Write, delete, move, copy, symlink, and chmod operations automatically stage changes when backed by a git worktree.

Example

ExampleWorktreeCallbacks demonstrates how to use WorktreeCallbacks for file operations.

package main

import (
	"context"
	"fmt"
	"os"

	"chainguard.dev/driftlessaf/agents/toolcall/callbacks"
)

func main() {
	ctx := context.Background()

	cb := callbacks.WorktreeCallbacks{
		ReadFile: func(ctx context.Context, path string, offset int64, limit int) (callbacks.ReadResult, error) {
			return callbacks.ReadResult{
				Content:    "file content",
				NextOffset: nil,
				Remaining:  0,
			}, nil
		},
		WriteFile: func(ctx context.Context, path, content string, mode os.FileMode) error {
			fmt.Printf("Writing to %s\n", path)
			return nil
		},
		DeleteFile: func(ctx context.Context, path string) error {
			fmt.Printf("Deleting %s\n", path)
			return nil
		},
		MoveFile: func(ctx context.Context, src, dst string) error {
			fmt.Printf("Moving %s to %s\n", src, dst)
			return nil
		},
		CopyFile: func(ctx context.Context, src, dst string) error {
			fmt.Printf("Copying %s to %s\n", src, dst)
			return nil
		},
		CreateSymlink: func(ctx context.Context, path, target string) error {
			fmt.Printf("Creating symlink %s -> %s\n", path, target)
			return nil
		},
		Chmod: func(ctx context.Context, path string, mode os.FileMode) error {
			fmt.Printf("Changing mode of %s to %o\n", path, mode)
			return nil
		},
		ListDirectory: func(ctx context.Context, path, filter string, offset, limit int) (callbacks.ListResult, error) {
			return callbacks.ListResult{
				Entries: []callbacks.DirEntry{{
					Name: "example.go",
					Size: 1024,
					Mode: 0644,
					Type: "file",
				}},
				NextOffset: nil,
				Remaining:  0,
			}, nil
		},
		EditFile: func(ctx context.Context, path, oldString, newString string, replaceAll bool) (callbacks.EditResult, error) {
			return callbacks.EditResult{
				Replacements: 1,
			}, nil
		},
		SearchCodebase: func(ctx context.Context, path, pattern, filter string, offset, limit int) (callbacks.SearchResult, error) {
			return callbacks.SearchResult{
				Matches: []callbacks.Match{{
					Path:   "example.go",
					Offset: 100,
					Length: 10,
				}},
				NextOffset: nil,
				HasMore:    false,
			}, nil
		},
	}

	// Use the callbacks
	result, _ := cb.ReadFile(ctx, "example.go", 0, -1)
	fmt.Println(result.Content)

	_ = cb.WriteFile(ctx, "new.go", "package main", 0644)
	_ = cb.DeleteFile(ctx, "old.go")
	_ = cb.MoveFile(ctx, "src.go", "dst.go")
	_ = cb.CopyFile(ctx, "original.go", "copy.go")
	_ = cb.CreateSymlink(ctx, "link", "target")
	_ = cb.Chmod(ctx, "script.sh", 0755)

	listResult, _ := cb.ListDirectory(ctx, ".", "", 0, 50)
	fmt.Printf("Found %d entries\n", len(listResult.Entries))

	editResult, _ := cb.EditFile(ctx, "file.go", "old", "new", false)
	fmt.Printf("Replaced %d occurrences\n", editResult.Replacements)

	searchResult, _ := cb.SearchCodebase(ctx, ".", "pattern", "*.go", 0, 50)
	fmt.Printf("Found %d matches\n", len(searchResult.Matches))

}
Output:
file content
Writing to new.go
Deleting old.go
Moving src.go to dst.go
Copying original.go to copy.go
Creating symlink link -> target
Changing mode of script.sh to 755
Found 1 entries
Replaced 1 occurrences
Found 1 matches

func LocalWorktree added in v0.7.2

func LocalWorktree(r *os.Root) WorktreeCallbacks

LocalWorktree returns a WorktreeCallbacks backed by r, an os.Root that sandboxes all file operations within a single directory tree. Path traversal outside the root is prevented by the os.Root implementation.

Jump to

Keyboard shortcuts

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