Functions/GenXdev.AI.Queries/Invoke-AIPowershellCommand.ps1

<##############################################################################
Part of PowerShell module : GenXdev.AI.Queries
Original cmdlet filename : Invoke-AIPowershellCommand.ps1
Original author : René Vaessen / GenXdev
Version : 3.26.2026
################################################################################
Copyright (c) 2026 René Vaessen / GenXdev

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
################################################################################>


###############################################################################
<#
.SYNOPSIS
Generates and executes PowerShell commands using AI assistance.

.DESCRIPTION
Uses AI models to generate PowerShell commands based on
natural language queries. The function can either send commands directly to
the PowerShell window or copy them to the clipboard. It leverages AI models
to interpret natural language and convert it into executable PowerShell
commands with comprehensive parameter support for various AI backends.

.PARAMETER Query
The natural language description of what you want to accomplish. The AI will
convert this into an appropriate PowerShell command.

.PARAMETER Instructions
Additional instructions for the AI model to customize command generation
behavior and provide context-specific guidance.

.PARAMETER Temperature
Controls the randomness in the AI's response generation. Values range from
0.0 (more focused/deterministic) to 1.0 (more creative/random).

.PARAMETER LLMQueryType
The type of LLM query to perform. Determines the AI model's behavior and
response style for different use cases.

.PARAMETER Model
The model identifier or pattern to use for AI operations. Can be a name or
partial path with support for pattern matching.

.PARAMETER ApiEndpoint
The API endpoint URL for AI operations when using external AI services.

.PARAMETER ApiKey
The API key for authenticated AI operations with external services.

.PARAMETER TimeoutSeconds
The timeout in seconds for AI operations to prevent hanging requests.

.PARAMETER PreferencesDatabasePath
Database path for preference data files to store AI configuration settings.

.PARAMETER SendKeyEscape
Escape control characters and modifiers when sending keys to the PowerShell
window.

.PARAMETER SendKeyHoldKeyboardFocus
Hold keyboard focus on target window when sending keys to the PowerShell
window.

.PARAMETER SendKeyUseShiftEnter
Use Shift+Enter instead of Enter when sending keys to the PowerShell window.

.PARAMETER SendKeyDelayMilliSeconds
Delay between different input strings in milliseconds when sending keys to
the PowerShell window.

.PARAMETER Clipboard
When specified, copies the generated command to clipboard instead of
executing it directly.

.PARAMETER SessionOnly
Use alternative settings stored in session for AI preferences instead of
persistent configuration.

.PARAMETER ClearSession
Clear alternative settings stored in session for AI preferences and reset
to defaults.

.PARAMETER SkipSession
Store settings only in persistent preferences without affecting session
state.

.EXAMPLE
Invoke-AIPowershellCommand -Query "list all running processes"

Generates a PowerShell command to list running processes

.EXAMPLE
hint "list files modified today"

Uses the alias to generate a command for finding files modified today.

.EXAMPLE
Invoke-AIPowershellCommand -Query "stop service" -Clipboard

Generates a command to stop a service and copies it to clipboard.
#>

function Invoke-AIPowershellCommand {

    [CmdletBinding()]
    [OutputType([void])]
    [Alias('hint')]
    param (
        ###########################################################################
        [Parameter(
            Position = 0,
            Mandatory = $true,
            HelpMessage = 'The natural language query to generate a command for'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $PowershellCmdline,
        ###########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'The type of LLM query'
        )]
        [ValidateSet(
            'SimpleIntelligence',
            'Knowledge',
            'Pictures',
            'TextTranslation',
            'Coding',
            'ToolUse'
        )]
        [string] $LLMQueryType = 'Coding',
        ###########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ('The model identifier or pattern to use for AI ' +
                'operations')
        )]
        [string] $Model,
        ###########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'The API endpoint URL for AI operations'
        )]
        [string] $ApiEndpoint,
        ###########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'The API key for authenticated AI operations'
        )]
        [string] $ApiKey,
        ###################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Indicates that LLM has no support for JSON schemas'
        )]
        [string] $NoSupportForJsonSchema
    )

    begin {

        # read compact cmdlet index for AI candidate scanning (~2KB vs 2MB)
        $indexPath = "$($MyInvocation.MyCommand.Module.ModuleBase)\usefull-cmdlets.txt"
        $cmdletIndex = if (Microsoft.PowerShell.Management\Test-Path -LiteralPath $indexPath) {
            Microsoft.PowerShell.Management\Get-Content -LiteralPath $indexPath -Raw -Encoding UTF8
        }
        else {
            ''
        }

        Microsoft.PowerShell.Utility\Write-Verbose ('Initializing AI command ' +
            'generation with tool-call workflow')

        # expose Get-Help as a tool function — LLM calls this to verify syntax
        $ExposedCmdLets = @(
            @{
                Name          = 'Microsoft.PowerShell.Core\Get-Help'
                Description   = ('Retrieves full help for a PowerShell cmdlet ' +
                    'including syntax, parameter names, types, and examples.')
                AllowedParams = @('Name=string')
                ForcedParams  = @(
                    @{ Name = 'Full'; Value = $true }
                )
                OutputText    = $true
                Confirm       = $false
            }
        )

        # Get-Help should never require user confirmation
        $NoConfirmationToolFunctionNames = @('Get-Help')

        # build instruction template
        $Instructions = @"

⛔ CRITICAL PROTOCOL — FOLLOW THESE STEPS IN ORDER ⛔

You are generating a PowerShell command to help the user. Follow these steps
exactly — do NOT skip ahead.

---

STEP 1 — IS IT A TYPO?

Only apply this step if the user's message LOOKS LIKE a PowerShell command
(contains a Verb-Noun pattern like "Get-Something" or has "-Parameter"
switches). If the user typed natural language like "find duplicate files",
skip to Step 2.

If it IS a command with an obvious typo or minor syntax error, fix ONLY the
typo and output the corrected command immediately. Do NOT search for
alternative cmdlets.

Examples:
- User types "Get-ChildIem" → fix to "Get-ChildItem"
- User types "Get-ChildItem -Recurse -Dept 3" → fix "-Dept" to "-Depth"
- User types "find duplicate files in this folder" → SKIP TO STEP 2

---

STEP 2 — SCAN FOR MATCHING CMDLETS:

Below is a list of available GenXdev cmdlets with their descriptions. Scan it
and identify 1-3 cmdlets that could solve the user's task.

$cmdletIndex

If a GenXdev cmdlet clearly fits, proceed to Step 3. If nothing matches,
proceed to Step 4.

---

STEP 3 — VERIFY PARAMETERS VIA Get-Help:

CALL the Get-Help tool on your chosen candidate(s). This is MANDATORY. You
do NOT know what parameters exist until you see the syntax block.

Rules:
- Every parameter name MUST appear in the Get-Help syntax block.
- For enum parameters showing {Value1 | Value2 | ...}, use EXACTLY one of
  those values. No approximations.
- If a parameter is NOT in the syntax block, it DOES NOT EXIST.
- Example: Open-MediaFile has -NoRecurse, NOT -Recurse. Do not assume.

After Get-Help returns, build the working command and output it.

---

STEP 4 — FALLBACK: GENERAL KNOWLEDGE:

No GenXdev cmdlet fits. Use your general PowerShell knowledge to suggest
the best standard PowerShell command for the task.

---

OUTPUT FORMAT — raw JSON only, no markdown fences:
{
    ""explanation"": ""What this command does or what was fixed"",
    ""suggestedCmdline"": ""The exact PowerShell command to run""
}

"@

        $ResponseFormat = @{
            type        = 'json_schema'
            json_schema = @{
                name   = 'text_transformation_response'
                strict = $true
                schema = @{
                    type       = 'object'
                    properties = @{
                        explanation      = @{
                            type        = 'string'
                            description = 'explanation what cmdline accomplises or what is improved to fix the error'
                        };
                        suggestedCmdline = @{
                            type        = 'string'
                            description = 'The transformed text output'
                        }
                    }
                    required   = @('explanation', 'suggestedCmdline')
                }
            }
        } | Microsoft.PowerShell.Utility\ConvertTo-Json -Depth 10

        # copy matching parameters to invoke transformation function
        $invocationParams = GenXdev\Copy-IdenticalParamValues `
            -BoundParameters $PSBoundParameters `
            -FunctionName 'GenXdev\Invoke-LLMQuery' `
            -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable -Scope Local)

    }

    process {

        if (($PowershellCmdline.Trim() -eq "") -or (($PowershellCmdline.Trim() -eq "^U"))) {
            return;
        }

        # log the query being processed for debugging purposes
        Microsoft.PowerShell.Utility\Write-Verbose ('Generating PowerShell ' +
            "command for query: $PowershellCmdline")

        # invoke LLM with Get-Help exposed as a tool for parameter verification
        # suppress tool chain output — Get-Help noise is distracting here
        $command = GenXdev\Invoke-LLMQuery @invocationParams `
            -Query $PowershellCmdline `
            -Instructions $Instructions `
            -ResponseFormat $ResponseFormat `
            -ExposedCmdLets $ExposedCmdLets `
            -NoConfirmationToolFunctionNames $NoConfirmationToolFunctionNames `
            -Temperature 0.1 `
            -QuietToolChainInvocations

        # Invoke-LLMQuery already extracts clean JSON from the response
        # via IndexOf/LastIndexOf. Parse and validate the required fields.
        try {
            $result = $command |
                Microsoft.PowerShell.Utility\ConvertFrom-Json -ErrorAction Stop

            if ((-not $result) -or
                [string]::IsNullOrWhiteSpace($result.suggestedCmdline) -or
                [string]::IsNullOrWhiteSpace($result.explanation)) {

                throw "AI response missing required fields"
            }
        }
        catch {
            Microsoft.PowerShell.Utility\Write-Error "'$PowershellCmdline' could not be resolved using AI"
            return
        }

        $explanation = $result.explanation
        $suggestedCmd = $result.suggestedCmdline

        if ([string]::IsNullOrWhiteSpace($suggestedCmd)) {
            Microsoft.PowerShell.Utility\Write-Warning 'AI did not return a valid command to execute.'
            return
        }

        $yellow = "`e[33m"
        $cyan = "`e[36m"
        $reset = "`e[0m"

        # show explanation and command to the user
        $userChoice = $host.ui.PromptForChoice(
            'Are you sure you want to perform this action?',
            @"
--
$yellow$suggestedCmd$reset
--
$cyan$explanation$reset
--
"@
, @('&No', '&Yes'), 1);

        if ($userChoice -eq 0) {

            return
        }

        Microsoft.PowerShell.Utility\Invoke-Expression $suggestedCmd
    }

    end {
    }
    ###############################################################################
}