Repository: GoogleCloudPlatform/kubectl-ai
File: pkg/agent/conversation.go (Go)

Code before:
		}

		// TODO(droot): Run all function calls in parallel
		// (may have to specify in the prompt to make these function calls independent)
		for _, call := range functionCalls {
			toolCall, err := a.Tools.ParseToolInvocation(ctx, call.Name, call.Arguments)
			if err != nil {
				return fmt.Errorf("building tool call: %w", err)
			}

			// Check if this is a kubectl or bash command that might be interactive
			isInteractive := false
			var errMsg string
			if call.Name == "kubectl" || call.Name == "bash" {
				if command, ok := call.Arguments["command"].(string); ok {
					isInteractive, errMsg = tools.IsInteractiveCommand(command)
				}
			}

			// If interactive, show error and skip to next iteration
			if isInteractive {
				// Use ErrorBlock for interactive command errors
				errorBlock := ui.NewErrorBlock().SetText(fmt.Sprintf("  %s\n", errMsg))
				a.doc.AddBlock(errorBlock)

				currChatContent = append(currChatContent, gollm.FunctionCallResult{
					ID:     call.ID,
					Name:   call.Name,
					Result: map[string]any{"error": errMsg},
				})
				continue
			}

			// Only show "Running" message and proceed with execution for non-interactive commands
			s := toolCall.PrettyPrint()
			a.doc.AddBlock(ui.NewFunctionCallRequestBlock().SetText(fmt.Sprintf("  Running: %s\n", s)))

			var result any
			var invokeErr error

			// Execute the command
			result, invokeErr = toolCall.InvokeTool(ctx, tools.InvokeToolOptions{
				Kubeconfig: a.Kubeconfig,
				WorkDir:    a.workDir,
			})
			if invokeErr != nil {
				return fmt.Errorf("executing action: %w", invokeErr)
			}

			// Convert result to map for the LLM
			resultMap, err := tools.ToolResultToMap(result)

Review comment (suggestion):
So models that doesn't support tool-use yet, we need to condition this logic on `EnableToolUseShim` (deleted lines in line 274....).

So if `EnableToolUseShim` --> Just append as text response else proper FunctionCallResult (like you have today).


I think `EnableToolUseShim` can be pushed at the gollm layer, but that is some for other day. But I am also hoping that we will probably drop this soon because most models will support tool-use by default.

Code after:

		// TODO(droot): Run all function calls in parallel
		// (may have to specify in the prompt to make these function calls independent)
		// NOTE: Currently, function calls are executed sequentially.
		// Suggestion: Use goroutines and sync.WaitGroup to parallelize execution if tool calls are independent.
		// Be careful with shared state and UI updates if running in parallel.

		for _, call := range functionCalls {
			toolCall, err := a.Tools.ParseToolInvocation(ctx, call.Name, call.Arguments)
			if err != nil {
				return fmt.Errorf("building tool call: %w", err)
			}

			// Check if the command is interactive using the tool's implementation
			isInteractive, err := toolCall.GetTool().IsInteractive(call.Arguments)
			klog.Infof("isInteractive: %t, err: %v, CallArguments: %+v", isInteractive, err, call.Arguments)

			// If interactive, handle based on whether we're using tool-use shim
			if isInteractive {
				// Show error block for both shim enabled and disabled modes
				errorBlock := ui.NewErrorBlock().SetText(fmt.Sprintf("  %s\n", err.Error()))
				a.doc.AddBlock(errorBlock)

				if a.EnableToolUseShim {
					// Add the error as an observation
					observation := fmt.Sprintf("Result of running %q:\n%s", call.Name, err.Error())
					currChatContent = append(currChatContent, observation)
				} else {
					// For models with tool-use support (shim disabled), use proper FunctionCallResult
					// Note: This assumes the model supports sending FunctionCallResult
					currChatContent = append(currChatContent, gollm.FunctionCallResult{
						ID:     call.ID,
						Name:   call.Name,
						Result: map[string]any{"error": err.Error()},
					})
				}
				continue // Skip execution for interactive commands
			}

			// Only show "Running" message and proceed with execution for non-interactive commands
			s := toolCall.PrettyPrint()
			a.doc.AddBlock(ui.NewFunctionCallRequestBlock().SetText(fmt.Sprintf("  Running: %s\n", s)))

			// Ask for confirmation only if SkipPermissions is false AND the tool modifies resources.
			if !a.SkipPermissions && call.Arguments["modifies_resource"] != "no" {
				confirmationPrompt := `  Do you want to proceed ?
  1) Yes
  2) Yes, and don't ask me again
  3) No`

				optionsBlock := ui.NewInputOptionBlock().SetPrompt(confirmationPrompt)