Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1568c97
fix(anthropic): anthropic with vertex works with service account json…
Cali0707 Mar 18, 2026
d749d13
fix(anthropic): ToolChoiceNone should send tool_choice:{type:"none"} …
andreynering Mar 18, 2026
09d2b74
feat: support `text/*` files (#100)
caarlos0 Mar 18, 2026
f910b4c
feat(agent): allow empty prompt when messages exist (#179)
andreynering Mar 18, 2026
d120cc3
v0.15.0
andreynering Mar 18, 2026
6bb474f
fix: migrate the openai sdk to our internal fork
andreynering Mar 19, 2026
2556dbc
test: add integration tests for avian
andreynering Mar 19, 2026
dff62fa
fix(openai): skip reasoning items in Responses API replay (#181)
kylecarbs Mar 19, 2026
43df1e4
chore: revert change to skip user-agent for openrouter
andreynering Mar 13, 2026
b00cf24
chore: remove unused test function
andreynering Mar 19, 2026
c3f0da5
test(openrouter): simplify list of providers and models to test
andreynering Mar 19, 2026
5e4a443
test: re-record openrouter fixtures
andreynering Mar 19, 2026
57940a9
v0.15.1
andreynering Mar 19, 2026
ee77281
fix(providers/openai): skip ephemeral replay items
ibetitsmike Mar 20, 2026
236fedf
fix(providertests/testdata): update summary thinking fixtures
ibetitsmike Mar 20, 2026
277f9fb
feat(bedrock): add WithBaseURL option
aleksclark Mar 12, 2026
8924b01
fix(bedrock): apply base URL override after bedrock.WithConfig
aleksclark Mar 12, 2026
aa7e82f
fix(bedrock): don't default baseURL to anthropic API when using bedrock
aleksclark Mar 13, 2026
238e34d
fix: address tool calls with empty arguments in copilot (#156)
mavaa Mar 20, 2026
a5bee40
fix(openai): relax tool call validation for ollama compatibility (#113)
Gustave-241021 Mar 20, 2026
ca0e707
v0.16.0
andreynering Mar 20, 2026
eec5a32
chore(deps): bump the all group with 2 updates (#183)
dependabot[bot] Mar 23, 2026
bdfda5e
chore(deps): bump github.com/ardanlabs/kronk in the kronk group (#184)
dependabot[bot] Mar 23, 2026
0cab8bf
feat: anthropic computer use (#185)
hugodutka Mar 24, 2026
46820ff
chore(examples): go mod tidy
andreynering Mar 24, 2026
99e504c
v0.17.0
andreynering Mar 24, 2026
4620329
fix(providers/openai): emit source parts for Responses API streaming …
kylecarbs Mar 25, 2026
ebc9cb1
chore: run modernize (#188)
andreynering Mar 25, 2026
d13521a
chore(openai): add missing constants and checks for some thinking eff…
ibetitsmike Mar 25, 2026
11a1e0f
v0.17.1
andreynering Mar 25, 2026
112927d
chore: downgrade to Go 1.25
kylecarbs Mar 11, 2026
1f7dae5
fix(anthropic): extract usage-mapping helper, add streaming usage tests
ibetitsmike Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 131 additions & 34 deletions agent.go

Large diffs are not rendered by default.

546 changes: 537 additions & 9 deletions agent_test.go

Large diffs are not rendered by default.

62 changes: 61 additions & 1 deletion content.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fantasy

import "encoding/json"
import (
"context"
"encoding/json"
)

// ProviderOptionsData is an interface for provider-specific options data.
// All implementations MUST also implement encoding/json.Marshaler and
Expand Down Expand Up @@ -512,6 +515,16 @@ func (f FunctionTool) GetName() string {
return f.Name
}

// ProviderTool is a tool whose schema and wire format are defined by
// the model provider. Both pure provider-executed tools
// (ProviderDefinedTool) and client-executed provider tools
// (ExecutableProviderTool) implement this interface. The unexported
// method seals this interface to the types in this package.
// External packages should use NewExecutableProviderTool instead.
type ProviderTool interface {
providerDefinedTool() ProviderDefinedTool
}

// ProviderDefinedTool represents the configuration of a tool that is defined by the provider.
type ProviderDefinedTool struct {
// ID of the tool. Should follow the format `<provider-name>.<unique-tool-name>`.
Expand All @@ -532,6 +545,53 @@ func (p ProviderDefinedTool) GetName() string {
return p.Name
}

func (p ProviderDefinedTool) providerDefinedTool() ProviderDefinedTool {
return p
}

// ExecutableProviderTool pairs a ProviderDefinedTool with a
// client-side execution function. Use this for provider-defined tools
// that require local execution (e.g. Anthropic computer use). Register
// it via WithProviderDefinedTools.
type ExecutableProviderTool struct {
pdt ProviderDefinedTool
run func(ctx context.Context, call ToolCall) (ToolResponse, error)
}

func (e ExecutableProviderTool) providerDefinedTool() ProviderDefinedTool {
return e.pdt
}

// GetType returns the type of the underlying ProviderDefinedTool.
func (e ExecutableProviderTool) GetType() ToolType {
return e.pdt.GetType()
}

// GetName returns the name of the underlying ProviderDefinedTool.
func (e ExecutableProviderTool) GetName() string {
return e.pdt.GetName()
}

// Definition returns the underlying ProviderDefinedTool.
func (e ExecutableProviderTool) Definition() ProviderDefinedTool {
return e.pdt
}

// Run executes the tool's client-side function.
func (e ExecutableProviderTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
return e.run(ctx, call)
}

// NewExecutableProviderTool creates a provider-defined tool with
// client-side execution. The tool is sent to the API using the
// provider's native wire format, but executed locally by run.
func NewExecutableProviderTool(
pdt ProviderDefinedTool,
run func(ctx context.Context, call ToolCall) (ToolResponse, error),
) ExecutableProviderTool {
return ExecutableProviderTool{pdt: pdt, run: run}
}

// NewUserMessage creates a new user message with the given prompt and optional files.
func NewUserMessage(prompt string, files ...FilePart) Message {
content := make([]MessagePart, 0, len(files)+1)
Expand Down
103 changes: 103 additions & 0 deletions examples/computer-use/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

// This example demonstrates Anthropic computer use with the agent
// helper. It shows how to:
//
// 1. Wire up the provider, model, and computer use tool.
// 2. Register the tool via WithProviderDefinedTools so the agent
// handles the tool-call loop automatically.
// 3. Parse incoming tool calls with ParseComputerUseInput inside
// the Run function.
// 4. Return results (screenshots, errors) back to the agent.

import (
"bytes"
"context"
"fmt"
"image"
"image/color"
"image/png"
"os"

"charm.land/fantasy"
"charm.land/fantasy/providers/anthropic"
)

// takeScreenshot is a stub that simulates capturing a screenshot.
// In a real implementation this would capture the virtual display
// and return raw PNG bytes.
func takeScreenshot() ([]byte, error) {
// Generate a valid 1x1 black PNG as a placeholder.
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
img.Set(0, 0, color.Black)
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

func main() {
// Set up the Anthropic provider.
provider, err := anthropic.New(anthropic.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")))
if err != nil {
fmt.Fprintln(os.Stderr, "could not create provider:", err)
os.Exit(1)
}

ctx := context.Background()

// Pick the model.
model, err := provider.LanguageModel(ctx, "claude-opus-4-6")
if err != nil {
fmt.Fprintln(os.Stderr, "could not get language model:", err)
os.Exit(1)
}

// Create a computer use tool with a Run function that executes
// actions and returns screenshots.
computerTool := anthropic.NewComputerUseTool(anthropic.ComputerUseToolOptions{
DisplayWidthPx: 1920,
DisplayHeightPx: 1080,
ToolVersion: anthropic.ComputerUse20251124,
}, func(ctx context.Context, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
action, err := anthropic.ParseComputerUseInput(call.Input)
if err != nil {
return fantasy.ToolResponse{}, fmt.Errorf("parse computer use input: %w", err)
}

fmt.Printf("Action: %s\n", action.Action)

// In production you would execute the action (click,
// type, scroll, etc.) against the virtual display and
// then capture a screenshot.
png, err := takeScreenshot()
if err != nil {
return fantasy.ToolResponse{}, fmt.Errorf("take screenshot: %w", err)
}
return fantasy.NewImageResponse(png, "image/png"), nil
})

// Build an agent with the computer use tool. The agent handles
// the tool-call loop: it sends the prompt, executes any tool
// calls the model returns, feeds the results back, and repeats
// until the model stops requesting tools.
agent := fantasy.NewAgent(model,
fantasy.WithProviderDefinedTools(computerTool),
fantasy.WithStopConditions(fantasy.StepCountIs(10)),
)

result, err := agent.Generate(ctx, fantasy.AgentCall{
Prompt: "Take a screenshot of the desktop",
})
if err != nil {
fmt.Fprintln(os.Stderr, "agent error:", err)
os.Exit(1)
}

fmt.Println("Agent finished.")
fmt.Printf("Steps: %d\n", len(result.Steps))
if text := result.Response.Content.Text(); text != "" {
fmt.Println("Claude said:", text)
}
}
78 changes: 39 additions & 39 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,25 @@ require (
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect
cloud.google.com/go/storage v1.60.0 // indirect
cloud.google.com/go/storage v1.61.3 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/ardanlabs/kronk v1.20.8 // indirect
github.com/ardanlabs/kronk v1.21.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
Expand All @@ -49,6 +49,7 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/openai-go v0.0.0-20260319145158-d0740cc34266 // indirect
github.com/charmbracelet/x/ansi v0.10.2 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 // indirect
Expand All @@ -69,20 +70,20 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.18.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.71 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.8.4 // indirect
github.com/hashicorp/go-getter v1.8.5 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hybridgroup/yzma v1.10.1-0.20260224163708-1d64fab421f1 // indirect
github.com/hybridgroup/yzma v1.11.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/jupiterrider/ffi v0.6.0 // indirect
github.com/kaptinlin/go-i18n v0.2.12 // indirect
github.com/kaptinlin/jsonpointer v0.4.17 // indirect
github.com/kaptinlin/jsonschema v0.7.5 // indirect
github.com/kaptinlin/jsonschema v0.7.6 // indirect
github.com/kaptinlin/messageformat-go v0.4.18 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
Expand All @@ -93,7 +94,6 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nikolalohinski/gonja/v2 v2.7.0 // indirect
github.com/openai/openai-go/v3 v3.28.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
Expand All @@ -110,31 +110,31 @@ require (
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.40.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/net v0.51.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/api v0.269.0 // indirect
google.golang.org/genai v1.50.0 // indirect
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/api v0.271.0 // indirect
google.golang.org/genai v1.51.0 // indirect
google.golang.org/genproto v0.0.0-20260311181403-84a4fc48630c // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
Loading