{"id":20046263,"url":"https://github.com/chataize/plugin-api","last_synced_at":"2026-05-18T05:10:32.490Z","repository":{"id":259613001,"uuid":"879048730","full_name":"chataize/plugin-api","owner":"chataize","description":"Official library for building ChatAIze chatbot add-ons.","archived":false,"fork":false,"pushed_at":"2025-02-08T12:41:38.000Z","size":347,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-08T13:36:04.067Z","etag":null,"topics":["addon","addons","api","asp-net-core","bot","chataize","chatbot","csharp","dotnet","extension","extensions","framework","lib","library","mod","mods","plugin","plugins","sdk","template"],"latest_commit_sha":null,"homepage":"https://www.chataize.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chataize.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-26T20:28:54.000Z","updated_at":"2025-02-08T12:40:27.000Z","dependencies_parsed_at":"2024-10-26T22:31:16.406Z","dependency_job_id":"2326b093-8bf4-4f05-9344-0df1b4ec9ec9","html_url":"https://github.com/chataize/plugin-api","commit_stats":null,"previous_names":["chataize/plugin-api"],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chataize%2Fplugin-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chataize%2Fplugin-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chataize%2Fplugin-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chataize%2Fplugin-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chataize","download_url":"https://codeload.github.com/chataize/plugin-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241476435,"owners_count":19968916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["addon","addons","api","asp-net-core","bot","chataize","chatbot","csharp","dotnet","extension","extensions","framework","lib","library","mod","mods","plugin","plugins","sdk","template"],"created_at":"2024-11-13T11:22:21.418Z","updated_at":"2026-05-18T05:10:32.471Z","avatar_url":"https://github.com/chataize.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ChatAIze.PluginApi\n\n`ChatAIze.PluginApi` is the official SDK for building plugins (add-ons) for `ChatAIze.Chatbot`.\n\nIt builds on top of `ChatAIze.Abstractions` and provides convenient implementations and helpers for:\n\n- **Plugins**: `ChatbotPlugin` (a base implementation of `IChatbotPlugin`).\n- **LLM tools / function calling**: `ChatFunction` (+ `FunctionParameter` for explicit schemas).\n- **Workflow building blocks** (for dashboard “integration functions”): `FunctionAction`, `FunctionCondition`.\n- **Settings UI models**: `StringSetting`, `SelectionSetting`, `IntegerSetting`, `BooleanSetting`, sections/groups, etc.\n\nIf you want a working example, start with `ChatAIze.PluginApi.ExamplePlugin/MyShop.cs`.\n\n## TL;DR\n\n- Create a `net10.0` class library and reference `ChatAIze.PluginApi`.\n- Implement `IPluginLoader` (or `IAsyncPluginLoader`) and return a `ChatbotPlugin` with `Id`, `Title`, and `Version`.\n- Add settings/tools/actions/conditions (either via the collections or callback properties).\n- Build `Release` and deploy to ChatAIze.Chatbot (upload a single `.dll`, or copy the whole output folder for plugins with dependencies).\n\n## What you can build\n\n- **Plugin settings** (dashboard UI): Admin-configured values like API keys and feature toggles.\n- **Tools** (LLM function calling): Model-callable functions that run code and return structured output.\n- **Workflow actions**: Reusable steps that admins compose into “integration functions” in the dashboard.\n- **Workflow conditions**: Gates that allow/deny an integration function for the current chat/user.\n\n## Contents\n\n- [TL;DR](#tldr)\n- [Requirements](#requirements)\n- [Install](#install)\n- [Quick Start](#quick-start-minimal-plugin)\n- [Deploy](#deploy-to-chataizechatbot)\n- [Release Checklist](#plugin-release-checklist)\n- [Core Concepts](#core-concepts)\n  - [Stable IDs](#stable-ids-plugins-settings-tools-actions-conditions)\n  - [Lifetime \u0026 Concurrency](#lifetime-concurrency-and-disposal)\n  - [Context Objects](#context-objects-what-you-can-access-at-runtime)\n  - [Glossary](#glossary-chataizechatbot-terms)\n- [Tools](#tools-llm-callable-functions)\n- [Settings](#settings-plugin-configuration-ui)\n  - [List Settings](#list-settings-listsetting)\n  - [Map Settings](#map-settings-mapsetting)\n- [Workflow Actions](#workflow-actions-integration-function-steps)\n- [Workflow Conditions](#workflow-conditions-gates)\n- [Useful Patterns](#useful-patterns)\n- [Troubleshooting](#troubleshooting)\n- [Examples](#examples)\n- [Links](#links)\n\n## Requirements\n\n- **Target framework**: `net10.0` (matches the current `ChatAIze.Chatbot` host).\n- **.NET SDK**: install the `.NET 10` SDK (or the SDK required by your target ChatAIze.Chatbot version).\n\n## Install\n\n### .NET CLI\n\n```bash\ndotnet add package ChatAIze.PluginApi\n```\n\n### Package Manager Console\n\n```powershell\nInstall-Package ChatAIze.PluginApi\n```\n\n## Quick Start (Minimal Plugin)\n\n1) Create a class library:\n\n```bash\ndotnet new classlib -n MyCompany.MyPlugin -f net10.0\ncd MyCompany.MyPlugin\ndotnet add package ChatAIze.PluginApi\n```\n\n2) Add a plugin loader (the host discovers plugins in this order):\n\n1. a type implementing `IAsyncPluginLoader`,\n2. a type implementing `IPluginLoader`,\n3. any non-abstract type implementing `IChatbotPlugin` (constructed via `Activator.CreateInstance`).\n\n### Synchronous loader (`IPluginLoader`)\n\n```csharp\nusing ChatAIze.Abstractions.Plugins;\nusing ChatAIze.PluginApi;\n\nnamespace MyCompany.MyPlugin;\n\npublic sealed class MyPluginLoader : IPluginLoader\n{\n    public IChatbotPlugin Load()\n    {\n        return new ChatbotPlugin\n        {\n            Id = \"com.mycompany.myplugin\",\n            Title = \"My Plugin\",\n            Description = \"Adds custom tools and workflow actions.\",\n            Version = new Version(1, 0, 0)\n        };\n    }\n}\n```\n\n### Asynchronous loader (`IAsyncPluginLoader`)\n\n```csharp\nusing ChatAIze.Abstractions.Plugins;\nusing ChatAIze.PluginApi;\n\nnamespace MyCompany.MyPlugin;\n\npublic sealed class MyPluginLoader : IAsyncPluginLoader\n{\n    public async ValueTask\u003cIChatbotPlugin\u003e LoadAsync(CancellationToken cancellationToken = default)\n    {\n        // Do any async initialization here (warmup, migrations, etc.)\n        await Task.Delay(10, cancellationToken);\n\n        return new ChatbotPlugin\n        {\n            Id = \"com.mycompany.myplugin\",\n            Title = \"My Plugin\",\n            Version = new Version(1, 0, 0)\n        };\n    }\n}\n```\n\nTip: `ChatbotPlugin` defaults to returning its in-memory `Settings` / `Functions` / `Actions` / `Conditions` collections from the callback properties. You can either populate the collections directly or override the callback properties to return context-dependent definitions.\n\n3) Build:\n\n```bash\ndotnet build -c Release\n```\n\n## Deploy to ChatAIze.Chatbot\n\nChatAIze.Chatbot loads plugins from `.dll` files in the `plugins/` folder, or via dashboard upload.\n\n### Option A: Upload from the dashboard (single `.dll`)\n\nDashboard → Integrations → Plugins → Upload.\n\nThis is the simplest workflow, but it uploads **only one file**. If your plugin depends on additional assemblies, prefer file-copy deployment.\n\n### Option B: Copy files to `plugins/` (recommended for plugins with dependencies)\n\nCopy at least:\n\n- `MyCompany.MyPlugin.dll`\n- `MyCompany.MyPlugin.deps.json`\n- any dependency `.dll` files from your output folder\n\ninto the chatbot server’s `plugins/` directory.\n\nTip: If you have extra dependencies, the safest approach is to copy everything from `bin/Release/net10.0/` (except `.pdb` if you don’t want symbols).\n\n### Plugin release checklist\n\n- Target `net10.0` (match the host).\n- Set `IChatbotPlugin.Id` and keep it stable (loading a plugin with the same id replaces the previous one).\n- Set `IChatbotPlugin.Version` (ChatAIze.Chatbot treats missing versions as invalid).\n- Namespace plugin-level settings (`com.mycompany.myplugin:api_key`) to avoid collisions with other plugins.\n- Prefer named methods for tools (`AddFunction(MyTool)`), and keep tool names unique.\n- Respect `IsPreview` / `IsCommunicationSandboxed` before doing side effects (HTTP calls, emails/SMS, writes to external systems).\n- Don’t log secrets (API keys/tokens).\n- If you deploy dependencies, prefer copying the whole output folder to `plugins/` (don’t rely on dashboard upload for multi-file plugins).\n\n\u003cdetails\u003e\n\u003csummary\u003eHow plugin loading works (advanced)\u003c/summary\u003e\n\nChatAIze.Chatbot loads plugins with an isolated, unloadable `AssemblyLoadContext`:\n\n- The host copies the entire `plugins/` directory to a temporary folder before loading, so the original files are not locked.\n- Each plugin assembly is loaded into its own collectible `AssemblyLoadContext` (so it can be unloaded/replaced).\n- A set of assemblies is treated as *shared contracts* and always resolved from the host (for example: `ChatAIze.Abstractions*`, `ChatAIze.PluginApi*`, `ChatAIze.Utilities*`, and some `Microsoft.Extensions.*` abstractions).\n- Any other dependency must be resolvable via the plugin’s `.deps.json` (and present in the `plugins/` folder).\n\nThis is why dashboard upload is best for “single dll” plugins, and file-copy deployment is best for plugins with external dependencies.\n\u003c/details\u003e\n\n## Core Concepts\n\n### Stable IDs (Plugins, Settings, Tools, Actions, Conditions)\n\nChatAIze.Chatbot persists various identifiers, so treat these as **stable contracts**:\n\n- `IChatbotPlugin.Id`: used to identify and replace already-loaded plugins.\n- `ISetting.Id`: persisted as a key in the host’s plugin settings store.\n- `IChatFunction.Name`: becomes a tool name for the model (and must be unique across all installed plugins + integration functions).\n- `IFunctionAction.Id` / `IFunctionCondition.Id`: stored in integration function definitions.\n\nTool name matching in the ChatAIze stack uses a tolerant “normalized” comparison (case/spacing/punctuation-insensitive), so treat names like `get-order`, `get order`, and `GetOrder` as equivalent when thinking about collisions.\n\nRecommendation:\n\n- Use a reverse-DNS style prefix: `com.mycompany.myplugin:...`, or a GUID string.\n- For **plugin-level settings**: always namespace (shared global keyspace across plugins).\n- For **action/condition setting ids**: keep them simple (they are local to the action/condition settings dictionary and are used for delegate binding).\n\n### Lifetime, concurrency, and disposal\n\n- A plugin instance is typically a **singleton** for the lifetime of the host process.\n- Your callbacks (tools/actions/conditions/settings callbacks) can be invoked **concurrently** for multiple chats/users.\n- Avoid storing per-chat/per-user state on the plugin instance; use the context objects instead.\n- If your plugin needs cleanup, implement `IDisposable` and/or `IAsyncDisposable`. The host will attempt to dispose plugins on unload.\n\n### Context objects (what you can access at runtime)\n\nEvery callback can optionally accept a context parameter:\n\n- `IChatbotContext` (used in the dashboard): `Settings`, `Databases`, `Log(...)`\n- `IChatContext` (per conversation): adds `ChatId`, `User`, `IsPreview`, `IsDebugModeOn`, `IsCommunicationSandboxed`, `GetPlugin\u003cT\u003e(...)`\n- `IFunctionContext` (tools): adds knowledge search, quick replies, forms, prompt override, status/progress\n- `IActionContext` (workflow actions): adds action indices/results + placeholder APIs\n- `IConditionContext` (workflow conditions): currently just `IChatContext`\n\n### Glossary (ChatAIze.Chatbot terms)\n\n- **Plugin**: a loaded `.dll` that returns settings, tools, actions and/or conditions.\n- **Tool / function**: an `IChatFunction` exposed to the model as a callable tool (LLM function calling).\n- **Integration function**: a dashboard-configured function (also an `IChatFunction`) that executes a workflow of actions/conditions.\n- **Workflow action**: an `IFunctionAction` step used inside an integration function (not exposed to the model directly).\n- **Workflow condition**: an `IFunctionCondition` gate evaluated before an integration function runs.\n- **Placeholder**: a named value (`{placeholder}`) produced by actions and injected into later settings as plain-text substitution (supports nested access like `{ticket.id}` when the placeholder is a JSON object).\n\n## Tools (LLM-Callable Functions)\n\nTools are `IChatFunction` instances returned by `IChatbotPlugin.FunctionsCallback`. They are presented to the language model as callable tools.\n\n### Register a tool from a normal C# method\n\n```csharp\nusing System.ComponentModel;\nusing ChatAIze.Abstractions.Chat;\nusing ChatAIze.Abstractions.Plugins;\nusing ChatAIze.PluginApi;\nusing Microsoft.Extensions.Logging;\n\npublic sealed class MyPluginLoader : IPluginLoader\n{\n    public IChatbotPlugin Load()\n    {\n        var plugin = new ChatbotPlugin\n        {\n            Id = \"com.mycompany.myplugin\",\n            Title = \"My Plugin\",\n            Version = new Version(1, 0, 0),\n        };\n\n        plugin.AddFunction(GetOrderStatusAsync);\n        return plugin;\n    }\n\n    [Description(\"Gets the status of an order by id.\")]\n    private static async Task\u003cstring\u003e GetOrderStatusAsync(\n        IFunctionContext context,\n        [Description(\"Order id shown to the customer.\")] string orderId,\n        CancellationToken cancellationToken = default)\n    {\n        var apiKey = await context.Settings.GetAsync(\"com.mycompany.myplugin:api_key\", \"\", cancellationToken);\n        if (string.IsNullOrWhiteSpace(apiKey))\n        {\n            return \"Error: Plugin is not configured (missing api_key).\";\n        }\n\n        // Always respect preview/sandbox flags for side effects.\n        if (context.IsPreview || context.IsCommunicationSandboxed)\n        {\n            context.Log(LogLevel.Information, $\"(preview) Returning fake status for {orderId}.\");\n            return $\"Order {orderId} is shipped. (preview)\";\n        }\n\n        // TODO: call your external system here.\n        return $\"Order {orderId} is shipped.\";\n    }\n}\n```\n\n### Tool parameter binding and naming\n\n- Tool argument JSON is produced by the model using **snake_case** keys.\n- When invoking a delegate, the host binds arguments by `parameterName.ToSnakeLower()`.\n- Tool names are sent to the model as `snake_case` (for example `GetOrderStatus` becomes `get_order_status`).\n- You can optionally accept injected parameters:\n  - `IFunctionContext` is injected by type,\n  - `CancellationToken` is injected by type.\n\nTip: Prefer named methods over lambdas for tools. Some compiler-generated lambda names are not stable/public-friendly and may fail normalization. If you need full control, register a `ChatFunction` with an explicit `Name`.\n\n### Tool schema generation (what the model sees)\n\nIn the ChatAIze stack, schema generation works like this:\n\n- If `IChatFunction.Parameters` is non-null, the host uses it as the JSON schema.\n- Otherwise, the host derives a schema from the delegate signature via reflection.\n\nImportant note:\n\n- The schema serializer excludes `CancellationToken` and ChatAIze context types (`IChatbotContext`, `IChatContext`, `IConditionContext`,\n  `IFunctionContext`, `IActionContext`, `IUserContext`) automatically.\n\nIf you want full control over what the model sees, provide an explicit parameter list:\n\n```csharp\nusing ChatAIze.PluginApi;\n\nvar function = new ChatFunction\n{\n    Name = \"get_order_status\",\n    Description = \"Gets the status of an order by id.\",\n    Callback = GetOrderStatusAsync,\n    Parameters =\n    [\n        new FunctionParameter(typeof(string), \"orderId\", \"Order id shown to the customer.\", isRequired: true),\n    ]\n};\n\nplugin.AddFunction(function);\n```\n\nIf you rely on reflection-based schemas, you can improve the model-visible documentation/constraints using:\n\n- `DescriptionAttribute` on the method and/or parameters\n- string data annotations such as `[Required]`, `[MinLength]`, `[MaxLength]`, `[StringLength]`\n\nExample (reflection schema + runtime validation for strings):\n\n```csharp\nusing System.ComponentModel;\nusing System.ComponentModel.DataAnnotations;\nusing ChatAIze.Abstractions.Chat;\n\n[Description(\"Searches the knowledge base.\")]\nprivate static async Task\u003cstring\u003e SearchAsync(\n    IFunctionContext context,\n    [Description(\"Search query.\")] [Required] [MinLength(2)] string query,\n    CancellationToken cancellationToken = default)\n{\n    var result = await context.SearchKnowledgeAsync(query, cancellationToken: cancellationToken);\n    return result.ToString();\n}\n```\n\n### Error handling conventions\n\n- For **recoverable** failures, prefer returning a string that starts with `\"Error: \"`.\n- In the OpenAI provider, a tool call is considered successful only when the returned string does **not** start with `\"Error:\"` (case-insensitive).\n- Exceptions thrown by your tool delegate are not swallowed by the binder; they can bubble up and disrupt the completion. Catch exceptions and return a user-friendly `\"Error: ...\"` instead.\n- If your delegate returns a non-string value, the host serializes it to JSON for tool output using snake_case property names.\n\n### “Double check” tools\n\nSet `IChatFunction.RequiresDoubleCheck = true` to require an extra round-trip where the model is asked to call the tool again to confirm intent.\nThis behavior is implemented in both the OpenAI and Gemini providers.\n\n## Settings (Plugin Configuration UI)\n\nPlugins can expose settings that admins configure in the dashboard.\n\nIn ChatAIze.Chatbot, your `SettingsCallback` is invoked while rendering the plugin settings page and when settings change. Keep it fast and return deterministic ids so the host can diff and cache settings trees.\n\n### Settings tree: containers vs leaf settings\n\n- Containers: `SettingsSection`, `SettingsGroup`, `SettingsParagraph` (layout-only, do not store a value).\n- Leaf settings: `StringSetting`, `SelectionSetting`, `IntegerSetting`, `BooleanSetting`, `DecimalSetting`, `DateTimeSetting`, `ListSetting`, `MapSetting` (store a value under `ISetting.Id`).\n\n### Settings cheat sheet\n\n| Setting | Stored JSON kind | Typical use |\n| --- | --- | --- |\n| `StringSetting` | string | API keys, URLs, names |\n| `IntegerSetting` / `DecimalSetting` | number | thresholds, limits |\n| `BooleanSetting` | true/false | feature toggles |\n| `SelectionSetting` | string | “pick one” choices |\n| `DateTimeSetting` | string (date/time) | schedules, reminders |\n| `ListSetting` | array of strings | tags, allow/deny lists |\n| `MapSetting` | object (string → string) | headers, simple key/value configs |\n\n### Example settings UI\n\n```csharp\nusing ChatAIze.Abstractions.Settings;\nusing ChatAIze.Abstractions.UI;\nusing ChatAIze.PluginApi.Settings;\n\nplugin.SettingsCallback = _ =\u003e ValueTask.FromResult\u003cIReadOnlyCollection\u003cISetting\u003e\u003e(\n[\n    new SettingsSection(\n        id: \"com.mycompany.myplugin:section.general\",\n        title: \"General\",\n        description: \"Configuration for My Plugin.\",\n        settings:\n        [\n            new StringSetting(\n                id: \"com.mycompany.myplugin:api_key\",\n                title: \"API key\",\n                description: \"Used to call the Example API.\",\n                textFieldType: TextFieldType.Password,\n                maxLength: 200),\n\n            new SelectionSetting(\n                id: \"com.mycompany.myplugin:region\",\n                title: \"Region\",\n                defaultValue: \"us\",\n                style: SelectionSettingStyle.Automatic,\n                choices:\n                [\n                    new SelectionChoice(value: \"us\", title: \"United States\"),\n                    new SelectionChoice(value: \"eu\", title: \"Europe\"),\n                ])\n        ])\n]);\n```\n\nNotes about ChatAIze.Chatbot UI behavior:\n\n- `SelectionSettingStyle.Automatic` chooses a UI based on the number of choices (≤ 3 segmented, ≤ 6 radio buttons, otherwise a dropdown).\n- `SettingsButton` callbacks are executed on the server when clicked; the provided `CancellationToken` is canceled when the settings view is disposed.\n\n### List settings (`ListSetting`)\n\nUse `ListSetting` when you want the admin to enter a **list of strings** (tags, keywords, allowed domains, etc.).\n\n- Stored as a JSON array of strings under `ISetting.Id` (e.g. `[\"a\", \"b\", \"c\"]`).\n- In ChatAIze.Chatbot, the UI is a simple list editor with one text field per item.\n- You still need to validate values at runtime (treat settings as untrusted input).\n\nExample:\n\n```csharp\nusing ChatAIze.PluginApi.Settings;\n\nvar allowedDomains = new ListSetting(\n    id: \"com.mycompany.myplugin:allowed_domains\",\n    title: \"Allowed email domains\",\n    description: \"If set, only users with these email domains are allowed to run certain tools.\",\n    itemPlaceholder: \"@example.com\",\n    maxItems: 20,\n    maxItemLength: 100,\n    allowDuplicates: false,\n    isLowercase: true);\n```\n\nReading a list setting:\n\n```csharp\nvar domains = await context.Settings.GetAsync(\n    \"com.mycompany.myplugin:allowed_domains\",\n    defaultValue: new List\u003cstring\u003e(),\n    cancellationToken);\n```\n\nNotes:\n\n- `AllowDuplicates` and `IsLowercase` are **UI hints**; host enforcement can vary. Even if you set `allowDuplicates: false`, still handle duplicates defensively.\n- If you need a list of non-strings (numbers/objects), you currently have to parse strings yourself or expose dedicated settings (e.g. `IntegerSetting` + `ListSetting`).\n\n### Map settings (`MapSetting`)\n\nUse `MapSetting` when you want the admin to enter **key/value pairs of strings** (headers, labels → URLs, product → price strings, etc.).\n\n- Stored as a JSON object under `ISetting.Id` (e.g. `{\"key\":\"value\"}`).\n- In ChatAIze.Chatbot, the UI is a list of “Key” + “Value” fields and is serialized as `Dictionary\u003cstring, string\u003e`.\n- Keys are treated as plain strings (no normalization). Validate and normalize if your use case requires it (for example use case-insensitive keys for HTTP headers).\n\nExample:\n\n```csharp\nusing ChatAIze.PluginApi.Settings;\n\nvar defaultHeaders = new MapSetting(\n    id: \"com.mycompany.myplugin:default_headers\",\n    title: \"Default HTTP headers\",\n    description: \"Sent with every outbound request made by this plugin.\",\n    keyPlaceholder: \"Header-Name\",\n    valuePlaceholder: \"Value\",\n    maxItems: 30,\n    maxKeyLength: 100,\n    maxValueLength: 500);\n```\n\nReading a map setting:\n\n```csharp\nvar headers = await context.Settings.GetAsync(\n    \"com.mycompany.myplugin:default_headers\",\n    defaultValue: new Dictionary\u003cstring, string\u003e(),\n    cancellationToken);\n\n// Optional: treat keys case-insensitively for HTTP header usage\nvar headersNormalized = new Dictionary\u003cstring, string\u003e(headers, StringComparer.OrdinalIgnoreCase);\n```\n\nNotes:\n\n- `MapSetting` is string → string. If you want structured values, store JSON in the string values and parse it yourself (and validate carefully), or expose dedicated typed settings.\n- Host UIs may handle duplicate keys differently. In ChatAIze.Chatbot, duplicate keys are de-duplicated on save.\n\n### Reading settings at runtime\n\nPlugin settings are stored by the host as JSON keyed by `ISetting.Id` and exposed through `IPluginSettings`:\n\n```csharp\nvar apiKey = await context.Settings.GetAsync(\"com.mycompany.myplugin:api_key\", \"\", cancellationToken);\n```\n\nSecurity note: use plugin settings for secrets (API keys, tokens) and avoid hardcoding credentials in your plugin binary or source code. Do not log secret values.\n\n## Workflow Actions (Integration Function Steps)\n\nChatAIze.Chatbot supports “integration functions” configured in the dashboard. These are workflows composed of action steps and optional conditions.\n\nYour plugin can contribute reusable actions via `IFunctionAction` / `FunctionAction`.\n\n### Define an action with settings and placeholders\n\n```csharp\nusing ChatAIze.Abstractions.Chat;\nusing ChatAIze.PluginApi;\nusing ChatAIze.PluginApi.Settings;\n\nvar action = new FunctionAction(\n    id: \"com.mycompany.myplugin:actions.create_ticket\",\n    title: \"Create Ticket\",\n    callback: CreateTicketAsync)\n{\n    Description = \"Creates a ticket and exposes it as {ticket}.\",\n    Placeholders = [\"ticket\"]\n};\n\naction.AddStringSetting(\"subject\", title: \"Subject\", maxLength: 200);\naction.AddStringSetting(\"message\", title: \"Message\", editorLines: 4, maxLength: 2000);\n\nplugin.AddAction(action);\n\nstatic async Task\u003cstring\u003e CreateTicketAsync(IActionContext context, string subject, string message, CancellationToken cancellationToken)\n{\n    if (context.IsPreview)\n    {\n        context.SetPlaceholder(\"ticket\", new { id = 123, url = \"https://example.test/tickets/123\" });\n        return \"OK: (preview) ticket created.\";\n    }\n\n    // TODO: create the ticket in your external system here.\n    context.SetPlaceholder(\"ticket\", new { id = 123, url = \"https://example.com/tickets/123\" });\n    return \"Ticket created.\";\n}\n```\n\n### List/map settings in actions\n\nActions can use `ListSetting` and `MapSetting` as step configuration. This is useful for things like:\n\n- tags/keywords (list),\n- HTTP headers (map),\n- variable substitution tables (map),\n- allow/deny lists (list).\n\nIf you want the host to bind settings into your delegate parameters, keep the setting ids compatible with C# parameter names (e.g. `headers`, `tags`).\n\n```csharp\nvar action = new FunctionAction(\n    id: \"com.mycompany.myplugin:actions.send_request\",\n    title: \"Send Request\",\n    callback: SendRequestAsync);\n\naction.AddStringSetting(\"url\", title: \"URL\");\naction.AddMapSetting(\"headers\", title: \"Headers\", keyPlaceholder: \"Header\", valuePlaceholder: \"Value\");\naction.AddListSetting(\"tags\", title: \"Tags\", itemPlaceholder: \"tag\");\n\nplugin.AddAction(action);\n\nstatic async Task\u003cstring\u003e SendRequestAsync(\n    IActionContext context,\n    string url,\n    Dictionary\u003cstring, string\u003e headers,\n    List\u003cstring\u003e tags,\n    CancellationToken cancellationToken)\n{\n    // Use headers/tags here...\n    return $\"OK: sending to {url} with {headers.Count} headers and {tags.Count} tags.\";\n}\n```\n\nIn ChatAIze.Chatbot, placeholders are expanded in action settings before invocation. For list/map values (JSON arrays/objects), substitution happens on the raw JSON text and is reparsed, so placeholders must produce valid JSON after replacement.\n\n### Action binding rules\n\nIn ChatAIze.Chatbot:\n\n- Action settings are passed as a JSON dictionary keyed by your setting ids.\n- Delegates can accept `IActionContext` and/or `CancellationToken` injected by type.\n- Other parameters are bound by **exact name** or **snake_case name**.\n\n### Placeholder expansion in action settings\n\nBefore calling your action callback, the host expands placeholders in the action settings:\n\n1. placeholders from integration-function parameters (e.g. `{order_id}`, `{customer_email}`),\n2. placeholders produced by previous actions (e.g. `{ticket.id}`, `{ticket.url}`).\n\nSubstitution is plain text. For JSON objects/arrays, ChatAIze.Chatbot performs substitution on the raw JSON and reparses it, so replacement must produce valid JSON.\n\nChatAIze.Chatbot normalizes placeholder ids to `snake_case` and may suffix them (`_2`, `_3`, …) to avoid collisions when multiple actions in a workflow declare the same placeholder id. Use the placeholder names shown in the dashboard for the specific action placement.\n\n### Action success/failure\n\n- Returning `\"Error: ...\"` does **not** automatically mark an action as failed.\n- To fail an action intentionally, call `context.SetActionResult(false, \"reason\")`.\n- Unhandled exceptions are caught by the workflow runner and recorded as an action failure; in non-preview runs the message may be replaced by a generic one.\n\n### Dynamic action settings (conditional fields)\n\nYou can render different settings based on the current placement values via `SettingsCallback`:\n\n```csharp\nusing System.Text.Json;\nusing ChatAIze.Abstractions.Settings;\nusing ChatAIze.PluginApi;\nusing ChatAIze.PluginApi.Settings;\nusing ChatAIze.Utilities.Extensions;\n\naction.SettingsCallback = (values) =\u003e\n{\n    var settings = new List\u003cISetting\u003e();\n\n    settings.AddSelectionSetting(\n        id: \"mode\",\n        title: \"Mode\",\n        defaultValue: \"simple\",\n        choices:\n        [\n            new SelectionChoice(\"simple\", \"Simple\"),\n            new SelectionChoice(\"advanced\", \"Advanced\"),\n        ]);\n\n    var mode = values.TryGetSettingValue(\"mode\", \"simple\");\n\n    if (mode == \"advanced\")\n    {\n        settings.AddIntegerSetting(id: \"retries\", title: \"Retries\", defaultValue: 3, minValue: 0, maxValue: 10);\n    }\n\n    return settings;\n};\n```\n\n## Workflow Conditions (Gates)\n\nConditions run before an integration function executes and decide whether it’s allowed.\n\n```csharp\nusing ChatAIze.Abstractions.Chat;\nusing ChatAIze.PluginApi;\nusing ChatAIze.PluginApi.Settings;\n\nvar condition = new FunctionCondition(\n    id: \"com.mycompany.myplugin:conditions.company_email\",\n    title: \"Company email required\",\n    callback: (IConditionContext context, string domain) =\u003e\n        context.User.Email?.EndsWith(domain, StringComparison.OrdinalIgnoreCase) == true\n            ? true\n            : $\"Only {domain} users are allowed.\");\n\ncondition.AddStringSetting(\"domain\", title: \"Email domain\", placeholder: \"@mycompany.com\");\nplugin.AddCondition(condition);\n```\n\nConditions can also use `ListSetting` / `MapSetting` for configuration (for example: a list of allowed domains or a map of role → allowed).\n\nIn ChatAIze.Chatbot:\n\n- Condition settings are placeholder-expanded from the integration function parameters before evaluation.\n- Return conventions:\n  - `true` → allow\n  - `false` → deny (no reason)\n  - string/other value → deny with a reason (non-string values are JSON-serialized)\n\nNote: conditions run before actions, so they do not have access to action placeholders (only to function parameters and chat/user context).\n\n## Useful Patterns\n\n### Status and progress\n\nYou can update the current execution status (and optional progress 0–100) via `IFunctionContext.SetStatus`:\n\n```csharp\ncontext.SetStatus(\"Calling external API...\", progress: 30);\n// ...\ncontext.SetStatus(\"Done.\", progress: 100);\n```\n\n### Logging\n\nAll context types expose `Log(...)` (wired to the host logging pipeline):\n\n```csharp\nusing Microsoft.Extensions.Logging;\n\ncontext.Log(LogLevel.Information, \"Starting order lookup...\");\n```\n\nTip: avoid logging secrets. In ChatAIze.Chatbot, logs can be visible to administrators.\n\n### Forms and confirmations\n\nPlugins can prompt the current user with built-in UI dialogs (when supported by the host):\n\n```csharp\nvar ok = await context.ShowConfirmationAsync(\n    title: \"Delete account\",\n    message: \"Are you sure you want to delete your account?\",\n    yesText: \"Delete\",\n    noText: \"Cancel\",\n    cancellationToken: cancellationToken);\n\nif (!ok)\n{\n    return \"Error: Cancelled by user.\";\n}\n```\n\n### Quick replies\n\nQuick replies are suggested “chips” in the chat UI. A tool or action can update them via `IFunctionContext.QuickReplies`:\n\n```csharp\nusing ChatAIze.PluginApi;\n\ncontext.QuickReplies.Clear();\ncontext.QuickReplies.Add(new QuickReply(\"Order\", \"Where is my order?\"));\ncontext.QuickReplies.Add(new QuickReply(\"Refund\", \"I want a refund\"));\n```\n\n### Knowledge search and document retrieval\n\n```csharp\nvar result = await context.SearchKnowledgeAsync(\"refund policy\", folder: \"Support\", cancellationToken: cancellationToken);\nvar content = await context.GetDocumentContentAsync(\"Refund Policy\", cancellationToken);\n```\n\n### Per-user storage\n\n```csharp\nvar lastOrderId = await context.User.GetPropertyAsync(\"com.mycompany.myplugin:last_order_id\", \"\", cancellationToken);\nawait context.User.SetPropertyAsync(\"com.mycompany.myplugin:last_order_id\", \"12345\", cancellationToken);\n```\n\n### Optional plugin-to-plugin integration\n\nIf your plugin can optionally integrate with another plugin, you can ask the host for a plugin instance by type:\n\n```csharp\nvar other = context.GetPlugin\u003cOtherPluginType\u003e(id: \"com.other.plugin\");\nif (other is not null)\n{\n    // Use the optional integration.\n}\n```\n\nAvoid hard dependencies on other plugins being present; always handle `null`.\n\n### Custom databases\n\nPlugins can use `IDatabaseManager` via `context.Databases`:\n\n```csharp\nusing ChatAIze.PluginApi.Databases;\nusing ChatAIze.Abstractions.Databases.Enums;\n\nvar db = await context.Databases.FindDatabaseByTitleAsync(\"Orders\", cancellationToken);\nif (db is null)\n{\n    return \"Error: Database 'Orders' not found.\";\n}\n\nvar item = await context.Databases.GetFirstItemAsync(\n    db,\n    sorting: new DatabaseSorting(property: \"created_at\", order: SortOrder.Descending),\n    cancellationToken: cancellationToken,\n    filters:\n    [\n        new DatabaseFilter(property: \"order_id\", type: FilterType.Equals, value: orderId, options: FilterOptions.None)\n    ]);\n```\n\n## Troubleshooting\n\n### “My plugin loads but my tool never runs”\n\n- In ChatAIze.Chatbot, ensure Integrations are enabled in the dashboard settings (tools and integration functions are added only when integrations are enabled).\n- Ensure your tool name is unique across all plugins and integration functions.\n- Ensure `IChatFunction.Callback` is non-null (tools without callbacks are treated as integration functions and executed by the host’s default callback).\n- Prefer named methods over lambdas for `AddFunction(Delegate)`; compiler-generated names can be unstable.\n\n### “It works locally but fails when uploaded”\n\n- Dashboard upload uploads one `.dll` file. If you use additional dependencies, deploy by copying your full output folder to `plugins/`.\n- Check that the `.deps.json` and dependency `.dll` files are present.\n\n### “My action returns an error string but the workflow still says it succeeded”\n\n- Actions are marked success/failure via `IActionContext.SetActionResult(...)`.\n- Returning `\"Error: ...\"` is just a string result; it does not automatically fail the action.\n\n## Examples\n\n- `ChatAIze.PluginApi.ExamplePlugin/MyShop.cs` demonstrates:\n  - settings (leaf + containers)\n  - a tool (`AddFunction`)\n  - an action (`FunctionAction`)\n  - a condition (`FunctionCondition`)\n\n## Links\n- GitHub: https://github.com/chataize/plugin-api\n- Chataize organization: https://github.com/chataize\n- Website: https://www.chataize.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchataize%2Fplugin-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchataize%2Fplugin-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchataize%2Fplugin-api/lists"}