diff options
| author | adamdottv <[email protected]> | 2025-05-28 10:12:08 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-05-28 15:36:35 -0500 |
| commit | 5e738ce7d3b66b88a407a1bcc53a5169d0f4a904 (patch) | |
| tree | 5c7a6ac13ebb60946e7c88f7d0e04df766edf94a /cmd | |
| parent | 641e9ff6642ef7b55928ef235f54d53822045baf (diff) | |
| download | opencode-5e738ce7d3b66b88a407a1bcc53a5169d0f4a904.tar.gz opencode-5e738ce7d3b66b88a407a1bcc53a5169d0f4a904.zip | |
wip: refactoring tui
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/non_interactive_mode.go | 292 | ||||
| -rw-r--r-- | cmd/root.go | 65 |
2 files changed, 5 insertions, 352 deletions
diff --git a/cmd/non_interactive_mode.go b/cmd/non_interactive_mode.go deleted file mode 100644 index 5023839c3..000000000 --- a/cmd/non_interactive_mode.go +++ /dev/null @@ -1,292 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "io" - "os" - "sync" - "time" - - "log/slog" - - charmlog "github.com/charmbracelet/log" - "github.com/sst/opencode/internal/app" - "github.com/sst/opencode/internal/config" - "github.com/sst/opencode/internal/db" - "github.com/sst/opencode/internal/format" - "github.com/sst/opencode/internal/llm/agent" - "github.com/sst/opencode/internal/llm/tools" - "github.com/sst/opencode/internal/message" - "github.com/sst/opencode/internal/permission" - "github.com/sst/opencode/internal/tui/components/spinner" - "github.com/sst/opencode/internal/tui/theme" -) - -// syncWriter is a thread-safe writer that prevents interleaved output -type syncWriter struct { - w io.Writer - mu sync.Mutex -} - -// Write implements io.Writer -func (sw *syncWriter) Write(p []byte) (n int, err error) { - sw.mu.Lock() - defer sw.mu.Unlock() - return sw.w.Write(p) -} - -// newSyncWriter creates a new synchronized writer -func newSyncWriter(w io.Writer) io.Writer { - return &syncWriter{w: w} -} - -// filterTools filters the provided tools based on allowed or excluded tool names -func filterTools(allTools []tools.BaseTool, allowedTools, excludedTools []string) []tools.BaseTool { - // If neither allowed nor excluded tools are specified, return all tools - if len(allowedTools) == 0 && len(excludedTools) == 0 { - return allTools - } - - // Create a map for faster lookups - allowedMap := make(map[string]bool) - for _, name := range allowedTools { - allowedMap[name] = true - } - - excludedMap := make(map[string]bool) - for _, name := range excludedTools { - excludedMap[name] = true - } - - var filteredTools []tools.BaseTool - - for _, tool := range allTools { - toolName := tool.Info().Name - - // If we have an allowed list, only include tools in that list - if len(allowedTools) > 0 { - if allowedMap[toolName] { - filteredTools = append(filteredTools, tool) - } - } else if len(excludedTools) > 0 { - // If we have an excluded list, include all tools except those in the list - if !excludedMap[toolName] { - filteredTools = append(filteredTools, tool) - } - } - } - - return filteredTools -} - -// handleNonInteractiveMode processes a single prompt in non-interactive mode -func handleNonInteractiveMode(ctx context.Context, prompt string, outputFormat format.OutputFormat, quiet bool, verbose bool, allowedTools, excludedTools []string) error { - // Initial log message using standard slog - slog.Info("Running in non-interactive mode", "prompt", prompt, "format", outputFormat, "quiet", quiet, "verbose", verbose, - "allowedTools", allowedTools, "excludedTools", excludedTools) - - // Sanity check for mutually exclusive flags - if quiet && verbose { - return fmt.Errorf("--quiet and --verbose flags cannot be used together") - } - - // Set up logging to stderr if verbose mode is enabled - if verbose { - // Create a synchronized writer to prevent interleaved output - syncWriter := newSyncWriter(os.Stderr) - - // Create a charmbracelet/log logger that writes to the synchronized writer - charmLogger := charmlog.NewWithOptions(syncWriter, charmlog.Options{ - Level: charmlog.DebugLevel, - ReportCaller: true, - ReportTimestamp: true, - TimeFormat: time.RFC3339, - Prefix: "OpenCode", - }) - - // Set the global logger for charmbracelet/log - charmlog.SetDefault(charmLogger) - - // Create a slog handler that uses charmbracelet/log - // This will forward all slog logs to charmbracelet/log - slog.SetDefault(slog.New(charmLogger)) - - // Log a message to confirm verbose logging is enabled - charmLogger.Info("Verbose logging enabled") - } - - // Start spinner if not in quiet mode - var s *spinner.Spinner - if !quiet { - // Get the current theme to style the spinner - currentTheme := theme.CurrentTheme() - - // Create a themed spinner - if currentTheme != nil { - // Use the primary color from the theme - s = spinner.NewThemedSpinner("Thinking...", currentTheme.Primary()) - } else { - // Fallback to default spinner if no theme is available - s = spinner.NewSpinner("Thinking...") - } - - s.Start() - defer s.Stop() - } - - // Connect DB, this will also run migrations - conn, err := db.Connect() - if err != nil { - return err - } - - // Create a context with cancellation - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // Create the app - app, err := app.New(ctx, conn) - if err != nil { - slog.Error("Failed to create app", "error", err) - return err - } - - // Create a new session for this prompt - session, err := app.Sessions.Create(ctx, "Non-interactive prompt") - if err != nil { - return fmt.Errorf("failed to create session: %w", err) - } - - // Set the session as current - app.CurrentSession = &session - - // Auto-approve all permissions for this session - permission.AutoApproveSession(ctx, session.ID) - - // Create the user message - _, err = app.Messages.Create(ctx, session.ID, message.CreateMessageParams{ - Role: message.User, - Parts: []message.ContentPart{message.TextContent{Text: prompt}}, - }) - if err != nil { - return fmt.Errorf("failed to create message: %w", err) - } - - // If tool restrictions are specified, create a new agent with filtered tools - if len(allowedTools) > 0 || len(excludedTools) > 0 { - // Initialize MCP tools synchronously to ensure they're included in filtering - mcpCtx, mcpCancel := context.WithTimeout(ctx, 10*time.Second) - agent.GetMcpTools(mcpCtx, app.Permissions) - mcpCancel() - - // Get all available tools including MCP tools - allTools := agent.PrimaryAgentTools( - app.Permissions, - app.Sessions, - app.Messages, - app.History, - app.LSPClients, - ) - - // Filter tools based on allowed/excluded lists - filteredTools := filterTools(allTools, allowedTools, excludedTools) - - // Log the filtered tools for debugging - var toolNames []string - for _, tool := range filteredTools { - toolNames = append(toolNames, tool.Info().Name) - } - slog.Debug("Using filtered tools", "count", len(filteredTools), "tools", toolNames) - - // Create a new agent with the filtered tools - restrictedAgent, err := agent.NewAgent( - config.AgentPrimary, - app.Sessions, - app.Messages, - filteredTools, - ) - if err != nil { - return fmt.Errorf("failed to create restricted agent: %w", err) - } - - // Use the restricted agent for this request - eventCh, err := restrictedAgent.Run(ctx, session.ID, prompt) - if err != nil { - return fmt.Errorf("failed to run restricted agent: %w", err) - } - - // Wait for the response - var response message.Message - for event := range eventCh { - if event.Err() != nil { - return fmt.Errorf("agent error: %w", event.Err()) - } - response = event.Response() - } - - // Format and print the output - content := "" - if textContent := response.Content(); textContent != nil { - content = textContent.Text - } - - formattedOutput, err := format.FormatOutput(content, outputFormat) - if err != nil { - return fmt.Errorf("failed to format output: %w", err) - } - - // Stop spinner before printing output - if !quiet && s != nil { - s.Stop() - } - - // Print the formatted output to stdout - fmt.Println(formattedOutput) - - // Shutdown the app - app.Shutdown() - - return nil - } - - // Run the default agent if no tool restrictions - eventCh, err := app.PrimaryAgent.Run(ctx, session.ID, prompt) - if err != nil { - return fmt.Errorf("failed to run agent: %w", err) - } - - // Wait for the response - var response message.Message - for event := range eventCh { - if event.Err() != nil { - return fmt.Errorf("agent error: %w", event.Err()) - } - response = event.Response() - } - - // Get the text content from the response - content := "" - if textContent := response.Content(); textContent != nil { - content = textContent.Text - } - - // Format the output according to the specified format - formattedOutput, err := format.FormatOutput(content, outputFormat) - if err != nil { - return fmt.Errorf("failed to format output: %w", err) - } - - // Stop spinner before printing output - if !quiet && s != nil { - s.Stop() - } - - // Print the formatted output to stdout - fmt.Println(formattedOutput) - - // Shutdown the app - app.Shutdown() - - return nil -} diff --git a/cmd/root.go b/cmd/root.go index a6b82c409..7c7795552 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,9 +15,6 @@ import ( "github.com/spf13/cobra" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/config" - "github.com/sst/opencode/internal/db" - "github.com/sst/opencode/internal/format" - "github.com/sst/opencode/internal/llm/agent" "github.com/sst/opencode/internal/logging" "github.com/sst/opencode/internal/lsp/discovery" "github.com/sst/opencode/internal/pubsub" @@ -90,52 +87,17 @@ to assist developers in writing, debugging, and understanding code directly from return err } - // Check if we're in non-interactive mode - prompt, _ := cmd.Flags().GetString("prompt") - - // Check for piped input if no prompt was provided via flag - if prompt == "" { - pipedInput, hasPipedInput := checkStdinPipe() - if hasPipedInput { - prompt = pipedInput - } - } - - // If we have a prompt (either from flag or piped input), run in non-interactive mode - if prompt != "" { - outputFormatStr, _ := cmd.Flags().GetString("output-format") - outputFormat := format.OutputFormat(outputFormatStr) - if !outputFormat.IsValid() { - return fmt.Errorf("invalid output format: %s", outputFormatStr) - } - - quiet, _ := cmd.Flags().GetBool("quiet") - verbose, _ := cmd.Flags().GetBool("verbose") - - // Get tool restriction flags - allowedTools, _ := cmd.Flags().GetStringSlice("allowedTools") - excludedTools, _ := cmd.Flags().GetStringSlice("excludedTools") - - return handleNonInteractiveMode(cmd.Context(), prompt, outputFormat, quiet, verbose, allowedTools, excludedTools) - } - // Run LSP auto-discovery if err := discovery.IntegrateLSPServers(cwd); err != nil { slog.Warn("Failed to auto-discover LSP servers", "error", err) // Continue anyway, this is not a fatal error } - // Connect DB, this will also run migrations - conn, err := db.Connect() - if err != nil { - return err - } - // Create main context for the application ctx, cancel := context.WithCancel(context.Background()) defer cancel() - app, err := app.New(ctx, conn) + app, err := app.New(ctx) if err != nil { slog.Error("Failed to create app", "error", err) return err @@ -149,9 +111,6 @@ to assist developers in writing, debugging, and understanding code directly from tea.WithAltScreen(), ) - // Initialize MCP tools in the background - initMCPTools(ctx, app) - // Setup the subscriptions, this will send services events to the TUI ch, cancelSubs := setupSubscriptions(app, ctx) @@ -234,20 +193,6 @@ func attemptTUIRecovery(program *tea.Program) { program.Quit() } -func initMCPTools(ctx context.Context, app *app.App) { - go func() { - defer logging.RecoverPanic("MCP-goroutine", nil) - - // Create a context with timeout for the initial MCP tools fetch - ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() - - // Set this up once with proper error handling - agent.GetMcpTools(ctxWithTimeout, app.Permissions) - slog.Info("MCP message handling goroutine exiting") - }() -} - func setupSubscriber[T any]( ctx context.Context, wg *sync.WaitGroup, @@ -298,10 +243,10 @@ func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg, wg := sync.WaitGroup{} ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context - setupSubscriber(ctx, &wg, "logging", app.Logs.Subscribe, ch) - setupSubscriber(ctx, &wg, "sessions", app.Sessions.Subscribe, ch) - setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch) - setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch) + // setupSubscriber(ctx, &wg, "logging", app.Logs.Subscribe, ch) + // setupSubscriber(ctx, &wg, "sessions", app.Sessions.Subscribe, ch) + // setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch) + // setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch) setupSubscriber(ctx, &wg, "status", app.Status.Subscribe, ch) cleanupFunc := func() { |
