src/commands/Add-ChatPluginFunction.ps1

#
# Copyright (c), Adam Edwards
#
# 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
Adds a plugin function to an existing collection of plugins sent to the pipeline.

.DESCRIPTION
Add-ChatPluginFunction defines the functions that make up a custom chat plugin registered through the Register-ChatPlugin command. For more information about custom plugins see the Register-ChatPlugin command documentation.

Add-ChatPluginFunction enables you to create a plugin function from a PowerShell Script Block. Register-ChatPlugin can accept any number of plugin functions created by the Add-ChatPluginFunction through the pipeline as a way to specify the collection of functions for the plugin.

.PARAMETER InputFunction
Specifies a function created by Add-ChatPluginFunction. No operation is performed on the function, though the command will validate that none of these functions have duplicate name properties. InputFunction is emitted by this command along with the new function that hs being added to the collection of functions specified to InputFunction via the pipeline. It is not strictly necessary to specify InputFunction -- leaving it unspseicifed means that Add-ChatPluginFunction will generate an output that contains only the added function. However, since chat plugins can contain more than one function, specifyig previously created chat plugin functions through this pipeline parameter provides a usability improvement since multiple Add-ChatPluginFunction invocations can be chained together through the pipeline, with the last inovcation piping the output directly to Register-ChatPlugin to register the plugin.

.PARAMETER FunctionName
The name of the function being defined. The name should be descriptive of the function's purpose because language models rely in the name along with other information about the plugin to include the function in instructions or plans generated by the model to instruct ChatGPS commands to execute the plugin. Appropriate naming also helps distinguish the plugin function from other functions across all plugins enabled for a session so that the generated plan is more likely to choose the correct function. Note that it is a common convention to use snake case for the name of the function because language models have historically been trained to interpret snake case code. The name must be shared by any of the functions specified by the InputFunction parameter through the pipeline.

.PARAMETER ScriptBlock
The ScriptBlock parameter is the function implementation as PowerShell script code. The script blocks can execute any PowerShell code, and operations can be read-only or even include actions that write data or change the state of the system. The supplied script block may be parameterized and these parameters will be treated as the plugin function's parameters. Use of descriptive parameter names will assist the language model in correctly binding the parameters to information in the user's prompt. From a security standpoint, the script block should implement the least amount of functionality possible to satisfy the function's purpose to reduce attack surface area.

.PARAMETER Description
An optional detailed description of the function's purpose. The description is used by the language model to decide when to use the function, and helps differentiate it from other functions that may seem to to have similar capabilities when only the plugin's name is considered.

.PARAMETER OutputType
This optional parameter provides a .NET type name that specifies the output of the function. It is used to assist the language model in correctly interpreting the function's results. By default, the output type is System.String.

.PARAMETER OutputDescription
This optional parameter provides a natural language description of the function's results; it used by the language model to determine how to most appropriately use the function's results. This description can also be part of the Description parameter, but specifying it explicitly through this parameter may improve the model's attention and effective use of it.

.OUTPUTS
A collection of plugin functions that includes all plugin functions specified to the InputFunction parameter as well as the plugin function added by this invocation of the command. The output may be piped to a subsequent invocation of Add-ChatPluginFunction in order to add another plugin function to the collection before finally passing the result of a final invocation of Add-ChatPluginFunction that contains all the plugins needed to register a plugin to the Register-ChatPlugin command.

.EXAMPLE
Add-ChatPluginFunction system_uptime { Get-Uptime } -Description 'Retrieve the uptime of the operating system' |
  Register-ChatPlugin system_basic_information -Description 'Returns basic information about the operating system'
 
Name Desciption Parameters
---- ---------- ----------
system_basic_information Returns basic information about the operating system

In this example, Add-ChatPluginFunction defines a function with a PowerShell script block that simply consists of an invocation of the Get-Uptime command to return the system uptime, and the Description parameter is used to indicate that returning the system uptime is indeed the function's purpose. The outpout of the command is piped to Register-ChatPlugin to create the new plugin which consists solely of this one function.

.EXAMPLE
Add-ChatPluginFunction system_uptime { Get-Uptime } -Description 'Retrieve the uptime of the operating system' |
  Add-ChatPluginFunction get_system_updates {
      param ([int]$Days = 30) Get-HotFix | Where-Object { $_.InstalledOn -gt (Get-Date).AddDays(-$Days) }
  } -Description 'Returns the list of operating system updates applied to the system in the last N days' |
  Add-ChatPluginFunction get_os_drive_free_space { (Get-PSDrive ($env:SystemDrive).Trim(':')).Free
    } -Description 'Returns the amount of free disk space in bytes for the drive that hosts the operating system.' |
  Register-ChatPlugin system_basic_information -Description 'Returns basic information about the operating system'
 
Name Desciption Parameters
---- ---------- ----------
system_basic_information Returns basic information about the operating system

This example shows how the pipeline can be used with Add-ChatPluginFunction to create multiple functions for a plugin and chain them together in sequence by piping the output of each invocation to the next. The final output is then piped to Register-ChatPlugin which creates a plugin from all of those functions.

.LINK
Register-ChatPlugin
#>

function Add-ChatPluginFunction {
    [cmdletbinding(positionalbinding=$false)]
    param(
        [parameter(valuefrompipeline=$true)]
        [Modulus.ChatGPS.Plugins.PowerShellPluginFunction] $InputFunction,

        [parameter(position=0, mandatory=$true)]
        [string] $FunctionName,

        [parameter(position=1, mandatory=$true)]
        [ScriptBlock] $ScriptBlock,

        [string] $Description,

        [parameter(position=2)]
        [string] $OutputType = 'System.String',

        [string] $OutputDescription
    )

    begin {
        $targetDescription = if ( $Description ) {
            $Description
        } else {
            "This method invokes PowerShell code to perform its function."
        }

        $newPluginFunction = NewPowerShellPluginFunction $FunctionName $ScriptBlock $Description $OutputType $OutputDescription
    }

    process {
        if ( $InputFunction ) {
            if ( $InputFunction.Name -eq $FunctionName ) {
                throw [ArgumentException]::new("The name specified for the new chat plugin function '$FunctionName' is already used by an existing function specified to the InputFunction parameter")
            }
            $InputFunction
        }
    }

    end {
        $newPluginFunction
    }
}