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 ¶
- func RejectionResult(submitToolName string, findings []Finding) map[string]any
- type CommitFile
- type CommitInfo
- type CommitListResult
- type DirEntry
- type EditResult
- type FileDiffResult
- type Finding
- type FindingCallbacks
- type FindingKind
- type HistoryCallbacks
- type ListResult
- type Match
- type ReadResult
- type ResultValidator
- type SearchResult
- type WorktreeCallbacks
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RejectionResult ¶ added in v0.7.59
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.