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

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

					s := toolCall.PrettyPrint()
					a.doc.AddBlock(ui.NewAgentTextBlock().SetText(fmt.Sprintf("  Running: %s\n", s)).SetColor(ui.ColorGreen))
					// 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)
						optionsBlock.SetOptions([]string{"1", "2", "3"})
						a.doc.AddBlock(optionsBlock)

						selectedChoice, err := optionsBlock.Observable().Wait()
						if err != nil {
							if err == io.EOF {
								// Use hit control-D, or was piping and we reached the end of stdin.
								// Not a "big" problem
								return nil
							}
							return fmt.Errorf("reading input: %w", err)
						}

						switch selectedChoice {
						case "1":
							// Proceed with the operation
						case "2":
							a.SkipPermissions = true
						case "3":
							a.doc.AddBlock(ui.NewAgentTextBlock().SetText("Operation cancelled by user."))
							return nil
						default:
							// This case should technically not be reachable due to AskForConfirmation loop
							err := fmt.Errorf("invalid confirmation choice: %q", selectedChoice)
							log.Error(err, "Invalid choice received from AskForConfirmation")
							a.doc.AddBlock(ui.NewErrorBlock().SetText("Invalid choice received. Cancelling operation."))
							return err
						}
					}

					ctx := journal.ContextWithRecorder(ctx, a.Recorder)
					output, err := toolCall.InvokeTool(ctx, tools.InvokeToolOptions{
						WorkDir: a.workDir,
					})
					if err != nil {
						return fmt.Errorf("executing action: %w", err)

Review comment (security):
I think we could ask permission to run a "group" of functions at once.  I don't think we can allow evaluation of some but not all functions anyway, the LLM will complain if the number of function responses does not match the number of function requests.  (Although _maybe_ we can work around that by returning an error like "The user denied permission to run the function")  But in any case, it seems like it might be better UX to show all the functions at once!

Code after:
						a.doc.AddBlock(agentTextBlock)
					}
					agentTextBlock.AppendText(text)
				}

				// Check if it's a function call
				if calls, ok := part.AsFunctionCalls(); ok && len(calls) > 0 {
					log.Info("function calls", "calls", calls)
					functionCalls = append(functionCalls, calls...)
				}
			}
		}

		// 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)
			}

			s := toolCall.PrettyPrint()
			a.doc.AddBlock(ui.NewAgentTextBlock().SetText(fmt.Sprintf("  Running: %s\n", s)).SetColor(ui.ColorGreen))
			// 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)
				optionsBlock.SetOptions([]string{"1", "2", "3"})
				a.doc.AddBlock(optionsBlock)

				selectedChoice, err := optionsBlock.Observable().Wait()
				if err != nil {
					if err == io.EOF {
						// Use hit control-D, or was piping and we reached the end of stdin.
						// Not a "big" problem
						return nil
					}
					return fmt.Errorf("reading input: %w", err)
				}

				switch selectedChoice {
				case "1":
					// Proceed with the operation
				case "2":
					a.SkipPermissions = true
				case "3":
					a.doc.AddBlock(ui.NewAgentTextBlock().SetText("Operation cancelled by user."))