all: support openai-compatible models

The support is rather minimal at this point:
Only hard-coded models, only -unsafe, only -skabandaddr="".

The "shared" LLM package is strongly Claude-flavored.

We can fix all of this and more over time, if we are inspired to.
(Maybe we'll switch to https://github.com/maruel/genai?)

The goal for now is to get the rough structure in place.
I've rebased and rebuilt this more times than I care to remember.
diff --git a/claudetool/bash.go b/claudetool/bash.go
index b3b8b03..882dddf 100644
--- a/claudetool/bash.go
+++ b/claudetool/bash.go
@@ -13,8 +13,8 @@
 	"syscall"
 	"time"
 
-	"sketch.dev/ant"
 	"sketch.dev/claudetool/bashkit"
+	"sketch.dev/llm"
 )
 
 // PermissionCallback is a function type for checking if a command is allowed to run
@@ -27,15 +27,15 @@
 }
 
 // NewBashTool creates a new Bash tool with optional permission callback
-func NewBashTool(checkPermission PermissionCallback) *ant.Tool {
+func NewBashTool(checkPermission PermissionCallback) *llm.Tool {
 	tool := &BashTool{
 		CheckPermission: checkPermission,
 	}
 
-	return &ant.Tool{
+	return &llm.Tool{
 		Name:        bashName,
 		Description: strings.TrimSpace(bashDescription),
-		InputSchema: ant.MustSchema(bashInputSchema),
+		InputSchema: llm.MustSchema(bashInputSchema),
 		Run:         tool.Run,
 	}
 }
diff --git a/claudetool/differential.go b/claudetool/differential.go
index a6b3413..d76209a 100644
--- a/claudetool/differential.go
+++ b/claudetool/differential.go
@@ -20,18 +20,18 @@
 	"time"
 
 	"golang.org/x/tools/go/packages"
-	"sketch.dev/ant"
+	"sketch.dev/llm"
 )
 
 // This file does differential quality analysis of a commit relative to a base commit.
 
 // Tool returns a tool spec for a CodeReview tool backed by r.
-func (r *CodeReviewer) Tool() *ant.Tool {
-	spec := &ant.Tool{
+func (r *CodeReviewer) Tool() *llm.Tool {
+	spec := &llm.Tool{
 		Name:        "codereview",
 		Description: `Run an automated code review.`,
 		// If you modify this, update the termui template for prettier rendering.
-		InputSchema: ant.MustSchema(`{"type": "object", "properties": {}}`),
+		InputSchema: llm.MustSchema(`{"type": "object", "properties": {}}`),
 		Run:         r.Run,
 	}
 	return spec
@@ -663,7 +663,7 @@
 // testStatus represents the status of a test in a given commit
 type testStatus int
 
-//go:generate go tool stringer -type=testStatus -trimprefix=testStatus
+//go:generate go tool golang.org/x/tools/cmd/stringer -type=testStatus -trimprefix=testStatus
 const (
 	testStatusUnknown testStatus = iota
 	testStatusPass
diff --git a/claudetool/edit.go b/claudetool/edit.go
index df83139..50084b7 100644
--- a/claudetool/edit.go
+++ b/claudetool/edit.go
@@ -18,7 +18,7 @@
 	"path/filepath"
 	"strings"
 
-	"sketch.dev/ant"
+	"sketch.dev/llm"
 )
 
 // Constants for the AnthropicEditTool
@@ -59,7 +59,7 @@
 var fileHistory = make(map[string][]string)
 
 // AnthropicEditTool is a tool for viewing, creating, and editing files
-var AnthropicEditTool = &ant.Tool{
+var AnthropicEditTool = &llm.Tool{
 	// Note that Type is model-dependent, and would be different for Claude 3.5, for example.
 	Type: "text_editor_20250124",
 	Name: editName,
diff --git a/claudetool/keyword.go b/claudetool/keyword.go
index a99e3cd..8c693be 100644
--- a/claudetool/keyword.go
+++ b/claudetool/keyword.go
@@ -9,16 +9,17 @@
 	"os/exec"
 	"strings"
 
-	"sketch.dev/ant"
+	"sketch.dev/llm"
+	"sketch.dev/llm/conversation"
 )
 
 // The Keyword tool provides keyword search.
 // TODO: use an embedding model + re-ranker or otherwise do something nicer than this kludge.
 // TODO: if we can get this fast enough, do it on the fly while the user is typing their prompt.
-var Keyword = &ant.Tool{
+var Keyword = &llm.Tool{
 	Name:        keywordName,
 	Description: keywordDescription,
-	InputSchema: ant.MustSchema(keywordInputSchema),
+	InputSchema: llm.MustSchema(keywordInputSchema),
 	Run:         keywordRun,
 }
 
@@ -122,16 +123,16 @@
 		keep = keep[:len(keep)-1]
 	}
 
-	info := ant.ToolCallInfoFromContext(ctx)
+	info := conversation.ToolCallInfoFromContext(ctx)
 	convo := info.Convo.SubConvo()
 	convo.SystemPrompt = strings.TrimSpace(keywordSystemPrompt)
 
-	initialMessage := ant.Message{
-		Role: ant.MessageRoleUser,
-		Content: []ant.Content{
-			ant.StringContent("<pwd>\n" + wd + "\n</pwd>"),
-			ant.StringContent("<ripgrep_results>\n" + out + "\n</ripgrep_results>"),
-			ant.StringContent("<query>\n" + input.Query + "\n</query>"),
+	initialMessage := llm.Message{
+		Role: llm.MessageRoleUser,
+		Content: []llm.Content{
+			llm.StringContent("<pwd>\n" + wd + "\n</pwd>"),
+			llm.StringContent("<ripgrep_results>\n" + out + "\n</ripgrep_results>"),
+			llm.StringContent("<query>\n" + input.Query + "\n</query>"),
 		},
 	}
 
diff --git a/claudetool/patch.go b/claudetool/patch.go
index 9254319..0886e66 100644
--- a/claudetool/patch.go
+++ b/claudetool/patch.go
@@ -13,16 +13,16 @@
 	"path/filepath"
 	"strings"
 
-	"sketch.dev/ant"
 	"sketch.dev/claudetool/editbuf"
 	"sketch.dev/claudetool/patchkit"
+	"sketch.dev/llm"
 )
 
 // Patch is a tool for precise text modifications in files.
-var Patch = &ant.Tool{
+var Patch = &llm.Tool{
 	Name:        PatchName,
 	Description: strings.TrimSpace(PatchDescription),
-	InputSchema: ant.MustSchema(PatchInputSchema),
+	InputSchema: llm.MustSchema(PatchInputSchema),
 	Run:         PatchRun,
 }
 
diff --git a/claudetool/think.go b/claudetool/think.go
index 293cc0b..69aac3c 100644
--- a/claudetool/think.go
+++ b/claudetool/think.go
@@ -4,14 +4,14 @@
 	"context"
 	"encoding/json"
 
-	"sketch.dev/ant"
+	"sketch.dev/llm"
 )
 
 // The Think tool provides space to think.
-var Think = &ant.Tool{
+var Think = &llm.Tool{
 	Name:        thinkName,
 	Description: thinkDescription,
-	InputSchema: ant.MustSchema(thinkInputSchema),
+	InputSchema: llm.MustSchema(thinkInputSchema),
 	Run:         thinkRun,
 }