chore: remove dead code, unexport internal symbols, clean up stale comments

- Remove never-called functions: ListChildSessions, NewMessageEntryFromRaw,
  ProviderPool.Stats/PoolStats, CLI.DisplayToolCallMessage
- Remove deprecated ValidateModel (migrate callers to LookupModel)
- Remove deprecated colon-separated model format shim
- Unexport package-internal symbols: EstimateTokens, GetRequiredEnvVars,
  GeneratePKCE, ErrNoClipboardTool, ThinkingBudgetTokens, NewMessageRenderer
- Remove stale TAS-15/TAS-16 placeholder comments (both fully implemented)
- Fix misleading 'temporary approach' comment in clipboard_darwin.go
- Replace interface{} with any in extension examples
- Simplify auto-commit.go dead variable (CombinedOutput → Run)
This commit is contained in:
Ed Zynda
2026-03-19 17:25:53 +03:00
parent a97cd47ced
commit af486133a5
21 changed files with 83 additions and 180 deletions
+1 -2
View File
@@ -23,8 +23,7 @@ import (
func Init(api ext.API) {
api.OnSessionShutdown(func(_ ext.SessionShutdownEvent, ctx ext.Context) {
// Check for staged changes.
diff, err := exec.Command("git", "diff", "--cached", "--quiet").CombinedOutput()
_ = diff
err := exec.Command("git", "diff", "--cached", "--quiet").Run()
if err == nil {
return // exit code 0 means no staged changes
}
+50 -50
View File
@@ -23,14 +23,14 @@ import (
// ──────────────────────────────────────────────
type RelayConfig struct {
Version int `json:"version"`
Enabled bool `json:"enabled"`
BotToken string `json:"botToken"`
BotID int64 `json:"botId"`
BotUsername string `json:"botUsername"`
ChatID int64 `json:"chatId"`
AllowedUserIDs []int64 `json:"allowedUserIds"`
LastValidatedAt string `json:"lastValidatedAt"`
Version int `json:"version"`
Enabled bool `json:"enabled"`
BotToken string `json:"botToken"`
BotID int64 `json:"botId"`
BotUsername string `json:"botUsername"`
ChatID int64 `json:"chatId"`
AllowedUserIDs []int64 `json:"allowedUserIds"`
LastValidatedAt string `json:"lastValidatedAt"`
}
type TelegramUser struct {
@@ -53,17 +53,17 @@ type TelegramChatMember struct {
}
type TelegramMessage struct {
MessageID int `json:"message_id"`
Date int64 `json:"date"`
Text string `json:"text"`
Caption string `json:"caption"`
From TelegramUser `json:"from"`
Chat TelegramChat `json:"chat"`
EditDate int64 `json:"edit_date"`
MessageID int `json:"message_id"`
Date int64 `json:"date"`
Text string `json:"text"`
Caption string `json:"caption"`
From TelegramUser `json:"from"`
Chat TelegramChat `json:"chat"`
EditDate int64 `json:"edit_date"`
}
type TelegramUpdate struct {
UpdateID int64 `json:"update_id"`
UpdateID int64 `json:"update_id"`
Message *TelegramMessage `json:"message"`
EditedMessage *TelegramMessage `json:"edited_message"`
}
@@ -91,13 +91,13 @@ type RenderAction struct {
}
type ActiveRunState struct {
ID int
StartedAt time.Time
StepCount int
ProgressMessageID int
LastRenderedText string
Actions []RenderAction
LastAssistantText string
ID int
StartedAt time.Time
StepCount int
ProgressMessageID int
LastRenderedText string
Actions []RenderAction
LastAssistantText string
LastAssistantError bool
}
@@ -137,16 +137,16 @@ var (
config *RelayConfig
// Relay connection
pollLoopActive bool
pollGeneration int
pollStopCh chan struct{}
lastAPISuccessAt time.Time
retryActive bool
retryAttempt int
retryLogPath string
currentOffset int64
offsetInitialized bool
isConnecting bool
pollLoopActive bool
pollGeneration int
pollStopCh chan struct{}
lastAPISuccessAt time.Time
retryActive bool
retryAttempt int
retryLogPath string
currentOffset int64
offsetInitialized bool
isConnecting bool
// Spinner
spinnerIndex int
@@ -169,8 +169,8 @@ var (
pendingTest *PendingTest
// Latest context for background goroutines
latestCtx ext.Context
latestCtxSet bool
latestCtx ext.Context
latestCtxSet bool
// Debug mode
debugMode bool
@@ -255,7 +255,7 @@ func createRetryLogPath() string {
return filepath.Join(failureLogDir(), stamp+".log")
}
func appendFailureLog(path string, entry map[string]interface{}) {
func appendFailureLog(path string, entry map[string]any) {
dir := filepath.Dir(path)
os.MkdirAll(dir, 0755)
data, _ := json.Marshal(entry)
@@ -271,7 +271,7 @@ func appendFailureLog(path string, entry map[string]interface{}) {
// Telegram Bot API client
// ──────────────────────────────────────────────
func telegramRequest(token string, method string, body map[string]interface{}, timeoutSec int) (json.RawMessage, error) {
func telegramRequest(token string, method string, body map[string]any, timeoutSec int) (json.RawMessage, error) {
url := fmt.Sprintf("%s/bot%s/%s", telegramAPIBase, token, method)
payload, _ := json.Marshal(body)
client := &http.Client{Timeout: time.Duration(timeoutSec) * time.Second}
@@ -295,7 +295,7 @@ func telegramRequest(token string, method string, body map[string]interface{}, t
}
func tgGetMe(token string) (*TelegramUser, error) {
result, err := telegramRequest(token, "getMe", map[string]interface{}{}, 15)
result, err := telegramRequest(token, "getMe", map[string]any{}, 15)
if err != nil {
return nil, err
}
@@ -307,7 +307,7 @@ func tgGetMe(token string) (*TelegramUser, error) {
}
func tgGetChat(token string, chatID int64) (*TelegramChat, error) {
result, err := telegramRequest(token, "getChat", map[string]interface{}{
result, err := telegramRequest(token, "getChat", map[string]any{
"chat_id": chatID,
}, 15)
if err != nil {
@@ -321,7 +321,7 @@ func tgGetChat(token string, chatID int64) (*TelegramChat, error) {
}
func tgGetChatMember(token string, chatID int64, userID int64) (*TelegramChatMember, error) {
result, err := telegramRequest(token, "getChatMember", map[string]interface{}{
result, err := telegramRequest(token, "getChatMember", map[string]any{
"chat_id": chatID,
"user_id": userID,
}, 15)
@@ -336,7 +336,7 @@ func tgGetChatMember(token string, chatID int64, userID int64) (*TelegramChatMem
}
func tgGetUpdates(token string, offset int64, hasOffset bool, timeoutSeconds int, clientTimeoutSec int) ([]TelegramUpdate, error) {
body := map[string]interface{}{
body := map[string]any{
"timeout": timeoutSeconds,
"allowed_updates": []string{"message", "edited_message"},
}
@@ -355,7 +355,7 @@ func tgGetUpdates(token string, offset int64, hasOffset bool, timeoutSeconds int
}
func tgSendMessage(token string, chatID int64, text string) (*TelegramMessage, error) {
result, err := telegramRequest(token, "sendMessage", map[string]interface{}{
result, err := telegramRequest(token, "sendMessage", map[string]any{
"chat_id": chatID,
"text": text,
"disable_web_page_preview": true,
@@ -371,9 +371,9 @@ func tgSendMessage(token string, chatID int64, text string) (*TelegramMessage, e
}
func tgEditMessageText(token string, chatID int64, messageID int, text string) (*TelegramMessage, error) {
result, err := telegramRequest(token, "editMessageText", map[string]interface{}{
result, err := telegramRequest(token, "editMessageText", map[string]any{
"chat_id": chatID,
"message_id": messageID,
"message_id": messageID,
"text": text,
"disable_web_page_preview": true,
}, 30)
@@ -480,7 +480,7 @@ func handleAPIFailure(err error, operation string) {
attempt := retryAttempt
mu.Unlock()
appendFailureLog(logPath, map[string]interface{}{
appendFailureLog(logPath, map[string]any{
"timestamp": time.Now().Format(time.RFC3339),
"operation": operation,
"attempt": attempt,
@@ -880,10 +880,10 @@ func formatElapsed(d time.Duration) string {
}
func summarizeToolAction(toolName string, inputJSON string) string {
var args map[string]interface{}
var args map[string]any
json.Unmarshal([]byte(inputJSON), &args)
if args == nil {
args = make(map[string]interface{})
args = make(map[string]any)
}
getStr := func(key string, fallback string) string {
if v, ok := args[key]; ok {
@@ -919,10 +919,10 @@ func summarizeToolResult(toolName string, inputJSON string, isError bool) string
if isError {
return "failed " + summarizeToolAction(toolName, inputJSON)
}
var args map[string]interface{}
var args map[string]any
json.Unmarshal([]byte(inputJSON), &args)
if args == nil {
args = make(map[string]interface{})
args = make(map[string]any)
}
getStr := func(key string, fallback string) string {
if v, ok := args[key]; ok {
@@ -1718,7 +1718,7 @@ func runConnectFlow(ctx ext.Context) {
Enabled: enableNow,
BotToken: token,
BotID: me.ID,
BotUsername: me.Username,
BotUsername: me.Username,
ChatID: resolved.chatID,
AllowedUserIDs: resolved.allowedUserIDs,
LastValidatedAt: time.Now().Format(time.RFC3339),
+2 -2
View File
@@ -28,7 +28,7 @@ func Init(api ext.API) {
DisplayName: "File",
BorderColor: "#89b4fa", // Catppuccin blue
RenderHeader: func(toolArgs string, width int) string {
var args map[string]interface{}
var args map[string]any
if err := json.Unmarshal([]byte(toolArgs), &args); err != nil {
return ""
}
@@ -72,7 +72,7 @@ func Init(api ext.API) {
Background: "#1e1e2e", // Dark background
BorderColor: "#a6e3a1", // Catppuccin green
RenderHeader: func(toolArgs string, width int) string {
var args map[string]interface{}
var args map[string]any
if err := json.Unmarshal([]byte(toolArgs), &args); err != nil {
return ""
}
+3 -3
View File
@@ -49,12 +49,12 @@ func NewOAuthClient() *OAuthClient {
}
}
// GeneratePKCE generates a cryptographically secure PKCE verifier and challenge pair
// generatePKCE generates a cryptographically secure PKCE verifier and challenge pair
// for the OAuth 2.0 PKCE flow. The verifier is a random 32-byte string encoded as
// base64url, and the challenge is the SHA256 hash of the verifier, also base64url encoded.
// Returns the verifier (to be stored securely), challenge (to be sent with auth request),
// and any error encountered during generation.
func GeneratePKCE() (verifier, challenge string, err error) {
func generatePKCE() (verifier, challenge string, err error) {
// Generate 32 bytes of random data
verifierBytes := make([]byte, 32)
if _, err := rand.Read(verifierBytes); err != nil {
@@ -76,7 +76,7 @@ func GeneratePKCE() (verifier, challenge string, err error) {
// and PKCE challenge. Returns an AuthData structure containing the URL for user
// authentication and the PKCE verifier for the subsequent code exchange.
func (c *OAuthClient) GetAuthorizationURL() (*AuthData, error) {
verifier, challenge, err := GeneratePKCE()
verifier, challenge, err := generatePKCE()
if err != nil {
return nil, fmt.Errorf("failed to generate PKCE: %w", err)
}
+2 -2
View File
@@ -71,5 +71,5 @@ func DetectMediaType(data []byte) string {
// ErrNoImage is returned when the clipboard does not contain image data.
var ErrNoImage = fmt.Errorf("no image data on clipboard")
// ErrNoClipboardTool is returned when no suitable clipboard tool is found.
var ErrNoClipboardTool = fmt.Errorf("no clipboard tool available (install xclip, wl-paste, or use macOS)")
// errNoClipboardTool is returned when no suitable clipboard tool is found.
var errNoClipboardTool = fmt.Errorf("no clipboard tool available (install xclip, wl-paste, or use macOS)")
+2 -3
View File
@@ -7,9 +7,8 @@ import (
)
// ReadImage reads image data from the system clipboard on macOS.
// It uses osascript to check if the clipboard contains an image and then
// reads the data using a temporary approach. If the clipboard contains
// an image, it writes it to stdout as PNG data.
// It uses osascript to check if the clipboard contains an image via
// NSPasteboard and writes it to stdout as PNG data.
func ReadImage() (*ImageData, error) {
// Use osascript to write clipboard image to stdout via a pipe.
// The script checks if the clipboard has a «class PNGf» item.
+1 -1
View File
@@ -41,7 +41,7 @@ func ReadImage() (*ImageData, error) {
return nil, ErrNoImage
}
return nil, ErrNoClipboardTool
return nil, errNoClipboardTool
}
// readWithXclip reads image data using xclip.
+1 -1
View File
@@ -5,5 +5,5 @@ package clipboard
// ReadImage reads image data from the system clipboard on Windows.
// Windows clipboard image support is not yet implemented.
func ReadImage() (*ImageData, error) {
return nil, ErrNoClipboardTool
return nil, errNoClipboardTool
}
+3 -3
View File
@@ -19,8 +19,8 @@ import (
// Token estimation
// ---------------------------------------------------------------------------
// EstimateTokens provides a rough token count (~4 chars per token).
func EstimateTokens(text string) int {
// estimateTokens provides a rough token count (~4 chars per token).
func estimateTokens(text string) int {
return len(text) / 4
}
@@ -40,7 +40,7 @@ func estimateSingleMessageTokens(msg fantasy.Message) int {
total := 0
for _, part := range msg.Content {
if tp, ok := part.(fantasy.TextPart); ok {
total += EstimateTokens(tp.Text)
total += estimateTokens(tp.Text)
}
}
return total
+2 -2
View File
@@ -36,9 +36,9 @@ func TestEstimateTokens(t *testing.T) {
{"hello world", 2}, // 11 / 4 = 2
}
for _, tt := range tests {
got := EstimateTokens(tt.text)
got := estimateTokens(tt.text)
if got != tt.want {
t.Errorf("EstimateTokens(%q) = %d, want %d", tt.text, got, tt.want)
t.Errorf("estimateTokens(%q) = %d, want %d", tt.text, got, tt.want)
}
}
}
-25
View File
@@ -166,28 +166,3 @@ func (p *ProviderPool) Close() {
}
p.mu.Unlock()
}
// Stats returns current pool statistics.
func (p *ProviderPool) Stats() PoolStats {
p.mu.RLock()
defer p.mu.RUnlock()
stats := PoolStats{
TotalProviders: len(p.providers),
}
for _, pp := range p.providers {
if pp.refs > 0 {
stats.ActiveProviders++
} else {
stats.IdleProviders++
}
}
return stats
}
// PoolStats contains provider pool statistics.
type PoolStats struct {
TotalProviders int
ActiveProviders int
IdleProviders int
}
+4 -14
View File
@@ -49,7 +49,7 @@ func resolveModelAlias(provider, modelName string) string {
}
if resolved, exists := aliasMap[modelName]; exists {
if _, err := registry.ValidateModel(provider, resolved); err == nil {
if registry.LookupModel(provider, resolved) != nil {
return resolved
}
}
@@ -73,8 +73,8 @@ func ThinkingLevels() []ThinkingLevel {
return []ThinkingLevel{ThinkingOff, ThinkingMinimal, ThinkingLow, ThinkingMedium, ThinkingHigh}
}
// ThinkingBudgetTokens returns the token budget for a thinking level, or 0 for "off".
func ThinkingBudgetTokens(level ThinkingLevel) int64 {
// thinkingBudgetTokens returns the token budget for a thinking level, or 0 for "off".
func thinkingBudgetTokens(level ThinkingLevel) int64 {
switch level {
case ThinkingMinimal:
return 1024
@@ -162,16 +162,6 @@ func ParseModelString(modelString string) (provider, model string, err error) {
return "", "", fmt.Errorf("invalid model format %q: expected provider/model (e.g. anthropic/claude-sonnet-4-5)", modelString)
}
// Legacy colon-separated format
if strings.Contains(modelString, ":") {
parts := strings.SplitN(modelString, ":", 2)
if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
fmt.Fprintf(os.Stderr, "Warning: model format %q uses deprecated colon separator. Use %s/%s instead.\n",
modelString, parts[0], parts[1])
return parts[0], parts[1], nil
}
}
return "", "", fmt.Errorf("invalid model format %q: expected provider/model (e.g. anthropic/claude-sonnet-4-5)", modelString)
}
@@ -489,7 +479,7 @@ func buildAnthropicProviderOptions(config *ProviderConfig, modelName string) fan
return nil
}
budget := ThinkingBudgetTokens(config.ThinkingLevel)
budget := thinkingBudgetTokens(config.ThinkingLevel)
if budget == 0 {
return nil
}
+3 -19
View File
@@ -147,24 +147,8 @@ func (r *ModelsRegistry) LookupModel(provider, modelID string) *ModelInfo {
return &modelInfo
}
// ValidateModel validates if a model exists and returns detailed information.
// Deprecated: Use LookupModel instead — it returns nil for unknown models
// rather than an error, letting the provider API be the authority.
func (r *ModelsRegistry) ValidateModel(provider, modelID string) (*ModelInfo, error) {
if info := r.LookupModel(provider, modelID); info != nil {
return info, nil
}
providerInfo, exists := r.providers[provider]
if !exists {
return nil, fmt.Errorf("unsupported provider: %s", provider)
}
return nil, fmt.Errorf("model %s not found for provider %s", modelID, providerInfo.ID)
}
// GetRequiredEnvVars returns the required environment variables for a provider.
func (r *ModelsRegistry) GetRequiredEnvVars(provider string) ([]string, error) {
// getRequiredEnvVars returns the required environment variables for a provider.
func (r *ModelsRegistry) getRequiredEnvVars(provider string) ([]string, error) {
providerInfo, exists := r.providers[provider]
if !exists {
return nil, fmt.Errorf("unsupported provider: %s", provider)
@@ -194,7 +178,7 @@ func (r *ModelsRegistry) ValidateEnvironment(provider string, apiKey string) err
}
}
envVars, err := r.GetRequiredEnvVars(provider)
envVars, err := r.getRequiredEnvVars(provider)
if err != nil {
// Unknown provider — nothing to validate
return nil
-11
View File
@@ -144,17 +144,6 @@ func NewMessageEntry(parentID string, msg message.Message) (*MessageEntry, error
}, nil
}
// NewMessageEntryFromRaw creates a MessageEntry with pre-marshaled parts.
func NewMessageEntryFromRaw(parentID, role string, parts json.RawMessage, model, provider string) *MessageEntry {
return &MessageEntry{
Entry: NewEntry(EntryTypeMessage, parentID),
Role: role,
Parts: parts,
Model: model,
Provider: provider,
}
}
// NewModelChangeEntry creates a ModelChangeEntry.
func NewModelChangeEntry(parentID, provider, modelID string) *ModelChangeEntry {
return &ModelChangeEntry{
-24
View File
@@ -253,27 +253,3 @@ func extractTextPreview(partsJSON json.RawMessage) string {
func DeleteSession(path string) error {
return os.Remove(path)
}
// ListChildSessions returns all sessions that have the given session ID as
// their parent. This is useful for finding subagent sessions spawned from
// a parent session. Results are sorted by creation time (newest first).
func ListChildSessions(parentID string) ([]SessionInfo, error) {
if parentID == "" {
return nil, nil
}
allSessions, err := ListAllSessions()
if err != nil {
return nil, err
}
var children []SessionInfo
for _, s := range allSessions {
if s.ParentSessionID == parentID {
children = append(children, s)
}
}
// Already sorted by modification time from ListAllSessions
return children, nil
}
+1 -8
View File
@@ -36,7 +36,7 @@ func NewCLI(debug bool, compact bool) (*CLI, error) {
if compact {
cli.renderer = NewCompactRenderer(cli.width, debug)
} else {
cli.renderer = NewMessageRenderer(cli.width, debug)
cli.renderer = newMessageRenderer(cli.width, debug)
}
return cli, nil
@@ -108,13 +108,6 @@ func (c *CLI) DisplayAssistantMessageWithModel(message, modelName string) error
return nil
}
// DisplayToolCallMessage is a no-op retained for backward compatibility. Tool
// calls are now rendered as part of the unified tool block in DisplayToolMessage,
// which combines the invocation header with the execution result.
func (c *CLI) DisplayToolCallMessage(toolName, toolArgs string) {
// No-op: unified tool blocks are rendered in DisplayToolMessage.
}
// DisplayToolMessage renders and displays the complete result of a tool execution,
// including the tool name, arguments, and result. The isError parameter determines
// whether the result should be displayed as an error or success message.
+3 -3
View File
@@ -51,8 +51,8 @@ func CreateUsageTracker(modelString, providerAPIKey string) *UsageTracker {
}
registry := models.GetGlobalRegistry()
modelInfo, err := registry.ValidateModel(provider, model)
if err != nil {
modelInfo := registry.LookupModel(provider, model)
if modelInfo == nil {
return nil
}
@@ -94,7 +94,7 @@ func SetupCLI(opts *CLISetupOptions) (*CLI, error) {
// Skip usage tracking for ollama as it's not in models.dev
if provider != "ollama" {
registry := models.GetGlobalRegistry()
if modelInfo, err := registry.ValidateModel(provider, model); err == nil {
if modelInfo := registry.LookupModel(provider, model); modelInfo != nil {
// Check if OAuth credentials are being used for Anthropic models
isOAuth := false
if provider == "anthropic" {
+2 -2
View File
@@ -156,10 +156,10 @@ type MessageRenderer struct {
getToolRenderer func(toolName string) *ToolRendererData
}
// NewMessageRenderer creates and initializes a new MessageRenderer with the specified
// newMessageRenderer creates and initializes a new MessageRenderer with the specified
// terminal width and debug mode setting. The width parameter determines line wrapping
// and layout calculations.
func NewMessageRenderer(width int, debug bool) *MessageRenderer {
func newMessageRenderer(width int, debug bool) *MessageRenderer {
return &MessageRenderer{
width: width,
debug: debug,
+1 -3
View File
@@ -372,11 +372,9 @@ type AppModel struct {
appCtrl AppController
// input is the child input component (slash commands + autocomplete).
// Placeholder until InputComponent is implemented in TAS-15.
input inputComponentIface
// stream is the child streaming display component (spinner + streaming text).
// Placeholder until StreamComponent is implemented in TAS-16.
stream streamComponentIface
// renderer renders completed messages for tea.Println output. It is either
@@ -593,7 +591,7 @@ func NewAppModel(appCtrl AppController, opts AppModelOptions) *AppModel {
cr.getToolRenderer = opts.GetToolRenderer
rdr = cr
} else {
mr := NewMessageRenderer(width, false)
mr := newMessageRenderer(width, false)
mr.getToolRenderer = opts.GetToolRenderer
rdr = mr
}
+1 -1
View File
@@ -116,7 +116,7 @@ func newTestAppModel(ctrl AppController) (*AppModel, *stubStreamComponent, *stub
appCtrl: ctrl,
stream: stream,
input: input,
renderer: NewMessageRenderer(80, false),
renderer: newMessageRenderer(80, false),
compactMode: false,
modelName: "test-model",
width: 80,
+1 -1
View File
@@ -222,7 +222,7 @@ func NewStreamComponent(compactMode bool, width int, modelName string) *StreamCo
spinnerFrames: knightRiderFrames(),
compactMode: compactMode,
modelName: modelName,
messageRenderer: NewMessageRenderer(width, false),
messageRenderer: newMessageRenderer(width, false),
compactRenderer: NewCompactRenderer(width, false),
width: width,
}