@quilltap/qtap-plugin-gab-ai 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md ADDED
@@ -0,0 +1,46 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Package
6
+
7
+ Published as `@quilltap/qtap-plugin-gab-ai` (public npm package).
8
+
9
+ ## Build Commands
10
+
11
+ ```bash
12
+ npm run build # Bundle TypeScript to index.js using esbuild
13
+ npm run clean # Remove built index.js
14
+ ```
15
+
16
+ ## Architecture
17
+
18
+ This is a Quilltap LLM provider plugin that integrates Gab AI's API. The plugin uses an OpenAI-compatible API pattern.
19
+
20
+ ### Key Files
21
+
22
+ - **index.ts** - Plugin entry point exporting the `LLMProviderPlugin` interface with metadata, config, capabilities, and factory methods
23
+ - **provider.ts** - `GabAIProvider` class extending `OpenAICompatibleProvider` from `@quilltap/plugin-utils` with Gab-specific config (base URL: `https://gab.ai/v1`)
24
+ - **types.ts** - Re-exports types from `@quilltap/plugin-types`
25
+ - **manifest.json** - Plugin metadata including compatibility requirements (Quilltap >=1.7.0, Node >=18.0.0)
26
+ - **icon.tsx** - React component for the provider icon
27
+
28
+ ### Build System
29
+
30
+ esbuild bundles the plugin to CommonJS format. External packages (`@quilltap/plugin-types`, `@quilltap/plugin-utils`, React, Next.js, zod) are provided at runtime by the main Quilltap app and should not be bundled.
31
+
32
+ ### Plugin Interface
33
+
34
+ The plugin exports:
35
+ - `metadata` - Provider name, display name, colors, abbreviation
36
+ - `config` - API key requirements
37
+ - `capabilities` - Supports chat only (no image generation, embeddings, or web search)
38
+ - `attachmentSupport` - Text-only, no file attachments
39
+ - `createProvider()` - Factory for GabAIProvider instances
40
+ - `getAvailableModels()` - Fetches models from Gab AI API
41
+ - `validateApiKey()` - Validates API credentials
42
+ - `formatTools()` / `parseToolCalls()` - OpenAI-format tool handling
43
+
44
+ ### Dependencies
45
+
46
+ Runtime dependencies (`@quilltap/plugin-utils`, React, Next.js, zod) are provided by the Quilltap host application. The OpenAI SDK is provided transitively via `@quilltap/plugin-utils`.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Gab AI Provider Plugin for Quilltap
2
+
3
+ This plugin provides integration with Gab AI's API, enabling Quilltap to use Gab AI language models for chat completions.
4
+
5
+ ## Features
6
+
7
+ - **Chat Completions**: Access to Gab AI language models for conversational AI
8
+ - **Streaming**: Support for streaming responses for real-time chat
9
+ - **Text-Only**: Text input and output (no file attachments)
10
+ - **OpenAI-Compatible**: Uses OpenAI SDK with Gab AI's base URL
11
+
12
+ ## Installation
13
+
14
+ The plugin is included with Quilltap. To ensure you have the latest version of the OpenAI SDK (used for API compatibility):
15
+
16
+ ```bash
17
+ npm install openai@latest
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ ### API Key Setup
23
+
24
+ 1. Create a Gab AI account at https://api.gab.com
25
+ 2. Generate an API key from your account dashboard
26
+ 3. In Quilltap settings, add your API key under the Gab AI provider configuration
27
+
28
+ ### Required Permissions
29
+
30
+ This plugin requires the following:
31
+ - Network access to `api.gab.com`
32
+ - A valid Gab AI API account with billing enabled
33
+
34
+ ## Supported Models
35
+
36
+ The available models depend on your Gab AI account access. Use the "Fetch Available Models" feature in Quilltap to see your accessible models.
37
+
38
+ ### Chat Completion Models
39
+
40
+ Gab AI provides various language models optimized for different use cases:
41
+ - Standard models for general-purpose chat
42
+ - Specialized models for specific domains
43
+
44
+ **Note**: The exact available models and their capabilities depend on your Gab AI account access and may change over time. Use the "Fetch Available Models" feature in Quilltap to see your current accessible models.
45
+
46
+ ## File Attachment Support
47
+
48
+ The plugin does not support file attachments. All interactions are text-only.
49
+
50
+ ### Supported MIME Types
51
+
52
+ None - text only
53
+
54
+ ## Parameters
55
+
56
+ ### Chat Completion Parameters
57
+
58
+ - **model**: The model to use (varies by account access)
59
+ - **temperature**: Randomness of responses (0-2, default: 0.7)
60
+ - **maxTokens**: Maximum response length (default: 1000)
61
+ - **topP**: Diversity parameter (0-1, default: 1)
62
+ - **stop**: Stop sequences to end generation
63
+
64
+ ## Logging
65
+
66
+ The plugin includes comprehensive debug logging for all operations:
67
+
68
+ - API calls and responses
69
+ - Stream processing
70
+ - Model fetching
71
+ - API key validation
72
+ - Error handling
73
+
74
+ Set `LOG_LEVEL=debug` to see detailed operation logs.
75
+
76
+ ## Error Handling
77
+
78
+ The plugin provides detailed error messages for:
79
+ - Invalid API keys
80
+ - Model availability errors
81
+ - API errors and rate limiting
82
+ - Network connectivity issues
83
+
84
+ ## Examples
85
+
86
+ ### Basic Chat
87
+
88
+ ```typescript
89
+ const response = await provider.sendMessage({
90
+ messages: [
91
+ { role: 'user', content: 'Hello, how are you?' }
92
+ ],
93
+ model: 'gab-01',
94
+ }, apiKey);
95
+ ```
96
+
97
+ ### Streaming
98
+
99
+ ```typescript
100
+ for await (const chunk of provider.streamMessage({
101
+ messages: [{ role: 'user', content: 'Tell me a story' }],
102
+ model: 'gab-01',
103
+ }, apiKey)) {
104
+ console.log(chunk.content);
105
+ }
106
+ ```
107
+
108
+ ## Troubleshooting
109
+
110
+ ### Invalid API Key
111
+
112
+ - Verify the key is correct from your Gab AI account dashboard
113
+ - Ensure the key has not expired
114
+ - Check that billing is enabled on your Gab AI account
115
+
116
+ ### No Models Available
117
+
118
+ - Ensure your API key has access to the models
119
+ - Check that your account is not in a restricted region
120
+ - Verify your billing information is valid
121
+
122
+ ### Slow Responses
123
+
124
+ - Check Gab AI status page
125
+ - Verify network connection
126
+ - Check rate limiting (429 errors)
127
+
128
+ ## Support
129
+
130
+ For issues with the plugin, refer to:
131
+ - Quilltap GitHub: https://github.com/foundry-9/F9-Quilltap
132
+ - Gab AI Documentation: https://api.gab.com/docs
133
+
134
+ ## License
135
+
136
+ MIT License - See LICENSE file for details
@@ -0,0 +1,109 @@
1
+ /**
2
+ * esbuild configuration for qtap-plugin-anthropic
3
+ *
4
+ * Bundles the plugin with its SDK dependency into a single CommonJS file.
5
+ * External packages (react, zod, etc.) are provided by the main app at runtime.
6
+ */
7
+
8
+ import * as esbuild from 'esbuild';
9
+ import { resolve, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+
14
+ // Find the project root (3 levels up from plugin directory)
15
+ const projectRoot = resolve(__dirname, '..', '..', '..');
16
+
17
+ // Packages that should NOT be bundled - they're provided by the main app at runtime
18
+ const EXTERNAL_PACKAGES = [
19
+ // Quilltap packages (provided by main app)
20
+ '@quilltap/plugin-types',
21
+ '@quilltap/plugin-utils',
22
+ // React (provided by main app)
23
+ 'react',
24
+ 'react-dom',
25
+ 'react/jsx-runtime',
26
+ 'react/jsx-dev-runtime',
27
+ // Next.js (provided by main app)
28
+ 'next',
29
+ 'next-auth',
30
+ // Other main app dependencies
31
+ 'zod',
32
+ // Node.js built-ins
33
+ 'fs',
34
+ 'path',
35
+ 'crypto',
36
+ 'http',
37
+ 'https',
38
+ 'url',
39
+ 'util',
40
+ 'stream',
41
+ 'events',
42
+ 'buffer',
43
+ 'querystring',
44
+ 'os',
45
+ 'child_process',
46
+ 'node:fs',
47
+ 'node:path',
48
+ 'node:crypto',
49
+ 'node:http',
50
+ 'node:https',
51
+ 'node:url',
52
+ 'node:util',
53
+ 'node:stream',
54
+ 'node:events',
55
+ 'node:buffer',
56
+ 'node:querystring',
57
+ 'node:os',
58
+ 'node:child_process',
59
+ 'node:module',
60
+ ];
61
+
62
+ async function build() {
63
+ try {
64
+ const result = await esbuild.build({
65
+ entryPoints: [resolve(__dirname, 'index.ts')],
66
+ bundle: true,
67
+ platform: 'node',
68
+ target: 'node18',
69
+ format: 'cjs',
70
+ outfile: resolve(__dirname, 'index.js'),
71
+
72
+ // Resolve @/ imports to project root
73
+ alias: {
74
+ '@': projectRoot,
75
+ },
76
+
77
+ // Don't bundle these - they're available at runtime from the main app
78
+ external: EXTERNAL_PACKAGES,
79
+
80
+ // Source maps for debugging (optional, can remove for smaller builds)
81
+ sourcemap: false,
82
+
83
+ // Minification (optional, disable for debugging)
84
+ minify: false,
85
+
86
+ // Tree shaking
87
+ treeShaking: true,
88
+
89
+ // Log level
90
+ logLevel: 'info',
91
+ });
92
+
93
+ if (result.errors.length > 0) {
94
+ console.error('Build failed with errors:', result.errors);
95
+ process.exit(1);
96
+ }
97
+
98
+ console.log('Build completed successfully!');
99
+
100
+ if (result.warnings.length > 0) {
101
+ console.warn('Warnings:', result.warnings);
102
+ }
103
+ } catch (error) {
104
+ console.error('Build failed:', error);
105
+ process.exit(1);
106
+ }
107
+ }
108
+
109
+ build();
package/icon.tsx ADDED
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ interface IconProps {
6
+ className?: string;
7
+ }
8
+
9
+ /**
10
+ * Gab AI Icon Component
11
+ * Displays the Gab AI branding icon with customizable styling
12
+ *
13
+ * @param props Icon component properties
14
+ * @param props.className Optional CSS class name for styling
15
+ * @returns JSX Element representing the Gab AI icon
16
+ */
17
+ export function GabAIIcon({ className = 'h-5 w-5' }: IconProps) {
18
+ return (
19
+ <svg
20
+ className={`text-green-600 ${className}`}
21
+ fill="currentColor"
22
+ viewBox="0 0 24 24"
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ data-testid="gab-ai-icon"
25
+ >
26
+ {/* Gab AI logo - stylized circular design */}
27
+ <circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" strokeWidth="2" />
28
+ <path
29
+ d="M12 2A10 10 0 1 1 2 12A10 10 0 0 1 12 2Z"
30
+ fill="currentColor"
31
+ opacity="0.1"
32
+ />
33
+ {/* Text label GAB */}
34
+ <text
35
+ x="50%"
36
+ y="50%"
37
+ textAnchor="middle"
38
+ dominantBaseline="middle"
39
+ fill="currentColor"
40
+ fontSize="9"
41
+ fontWeight="bold"
42
+ fontFamily="system-ui, -apple-system, sans-serif"
43
+ >
44
+ GAB
45
+ </text>
46
+ </svg>
47
+ );
48
+ }
49
+
50
+ export default GabAIIcon;
package/index.js ADDED
@@ -0,0 +1,255 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // index.ts
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ default: () => index_default,
33
+ plugin: () => plugin
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // provider.ts
38
+ var import_plugin_utils = require("@quilltap/plugin-utils");
39
+ var GabAIProvider = class extends import_plugin_utils.OpenAICompatibleProvider {
40
+ constructor() {
41
+ super({
42
+ baseUrl: "https://gab.ai/v1",
43
+ providerName: "GabAI",
44
+ requireApiKey: true,
45
+ attachmentErrorMessage: "Gab AI does not support file attachments"
46
+ });
47
+ }
48
+ };
49
+
50
+ // icon.tsx
51
+ var import_react = __toESM(require("react"));
52
+ function GabAIIcon({ className = "h-5 w-5" }) {
53
+ return /* @__PURE__ */ import_react.default.createElement(
54
+ "svg",
55
+ {
56
+ className: `text-green-600 ${className}`,
57
+ fill: "currentColor",
58
+ viewBox: "0 0 24 24",
59
+ xmlns: "http://www.w3.org/2000/svg",
60
+ "data-testid": "gab-ai-icon"
61
+ },
62
+ /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "12", r: "11", fill: "none", stroke: "currentColor", strokeWidth: "2" }),
63
+ /* @__PURE__ */ import_react.default.createElement(
64
+ "path",
65
+ {
66
+ d: "M12 2A10 10 0 1 1 2 12A10 10 0 0 1 12 2Z",
67
+ fill: "currentColor",
68
+ opacity: "0.1"
69
+ }
70
+ ),
71
+ /* @__PURE__ */ import_react.default.createElement(
72
+ "text",
73
+ {
74
+ x: "50%",
75
+ y: "50%",
76
+ textAnchor: "middle",
77
+ dominantBaseline: "middle",
78
+ fill: "currentColor",
79
+ fontSize: "9",
80
+ fontWeight: "bold",
81
+ fontFamily: "system-ui, -apple-system, sans-serif"
82
+ },
83
+ "GAB"
84
+ )
85
+ );
86
+ }
87
+
88
+ // index.ts
89
+ var import_plugin_utils2 = require("@quilltap/plugin-utils");
90
+ var logger = (0, import_plugin_utils2.createPluginLogger)("GabAI");
91
+ var metadata = {
92
+ providerName: "GAB_AI",
93
+ displayName: "Gab AI",
94
+ description: "Gab AI language models for chat completions",
95
+ colors: {
96
+ bg: "bg-green-100",
97
+ text: "text-green-800",
98
+ icon: "text-green-600"
99
+ },
100
+ abbreviation: "GAB"
101
+ };
102
+ var config = {
103
+ requiresApiKey: true,
104
+ requiresBaseUrl: false,
105
+ apiKeyLabel: "Gab AI API Key"
106
+ };
107
+ var capabilities = {
108
+ chat: true,
109
+ imageGeneration: false,
110
+ embeddings: false,
111
+ webSearch: false
112
+ };
113
+ var attachmentSupport = {
114
+ supportsAttachments: false,
115
+ supportedMimeTypes: [],
116
+ description: "No attachment support (text only)",
117
+ notes: "Gab AI does not currently support file attachments"
118
+ };
119
+ var plugin = {
120
+ metadata,
121
+ config,
122
+ capabilities,
123
+ attachmentSupport,
124
+ /**
125
+ * Factory method to create a Gab AI LLM provider instance
126
+ */
127
+ createProvider: (baseUrl) => {
128
+ logger.debug("Creating Gab AI provider instance", { context: "plugin.createProvider", baseUrl });
129
+ return new GabAIProvider();
130
+ },
131
+ /**
132
+ * Get list of available models from Gab AI API
133
+ * Requires a valid API key
134
+ */
135
+ getAvailableModels: async (apiKey, baseUrl) => {
136
+ logger.debug("Fetching available Gab AI models", { context: "plugin.getAvailableModels" });
137
+ try {
138
+ const provider = new GabAIProvider();
139
+ const models = await provider.getAvailableModels(apiKey);
140
+ logger.debug("Successfully fetched Gab AI models", { context: "plugin.getAvailableModels", count: models.length });
141
+ return models;
142
+ } catch (error) {
143
+ logger.error("Failed to fetch Gab AI models", { context: "plugin.getAvailableModels" }, error instanceof Error ? error : void 0);
144
+ return [];
145
+ }
146
+ },
147
+ /**
148
+ * Validate a Gab AI API key
149
+ */
150
+ validateApiKey: async (apiKey, baseUrl) => {
151
+ logger.debug("Validating Gab AI API key", { context: "plugin.validateApiKey" });
152
+ try {
153
+ const provider = new GabAIProvider();
154
+ const isValid = await provider.validateApiKey(apiKey);
155
+ logger.debug("Gab AI API key validation result", { context: "plugin.validateApiKey", isValid });
156
+ return isValid;
157
+ } catch (error) {
158
+ logger.error("Error validating Gab AI API key", { context: "plugin.validateApiKey" }, error instanceof Error ? error : void 0);
159
+ return false;
160
+ }
161
+ },
162
+ /**
163
+ * Get static model information
164
+ * Returns cached information about Gab AI models without needing API calls
165
+ */
166
+ getModelInfo: () => {
167
+ logger.debug("Getting Gab AI model information", { context: "plugin.getModelInfo" });
168
+ return [
169
+ {
170
+ id: "gab-01",
171
+ name: "Gab AI 01",
172
+ contextWindow: 128e3,
173
+ maxOutputTokens: 4096,
174
+ supportsImages: false,
175
+ supportsTools: false
176
+ }
177
+ ];
178
+ },
179
+ /**
180
+ * Render the Gab AI icon
181
+ */
182
+ renderIcon: (props) => {
183
+ logger.debug("Rendering Gab AI icon", { context: "plugin.renderIcon", className: props.className });
184
+ return GabAIIcon(props);
185
+ },
186
+ /**
187
+ * Format tools from OpenAI format to OpenAI format
188
+ * Gab AI uses OpenAI format (inherited from OpenAI-compatible)
189
+ *
190
+ * @param tools Array of tools in OpenAI format
191
+ * @returns Array of tools in OpenAI format
192
+ */
193
+ formatTools: (tools) => {
194
+ logger.debug("Formatting tools for Gab AI provider", {
195
+ context: "plugin.formatTools",
196
+ toolCount: tools.length
197
+ });
198
+ try {
199
+ const formattedTools = [];
200
+ for (const tool of tools) {
201
+ if (!("function" in tool)) {
202
+ logger.warn("Skipping tool with invalid format", {
203
+ context: "plugin.formatTools"
204
+ });
205
+ continue;
206
+ }
207
+ formattedTools.push(tool);
208
+ }
209
+ logger.debug("Successfully formatted tools", {
210
+ context: "plugin.formatTools",
211
+ count: formattedTools.length
212
+ });
213
+ return formattedTools;
214
+ } catch (error) {
215
+ logger.error(
216
+ "Error formatting tools for Gab AI",
217
+ { context: "plugin.formatTools" },
218
+ error instanceof Error ? error : void 0
219
+ );
220
+ return [];
221
+ }
222
+ },
223
+ /**
224
+ * Parse tool calls from Gab AI response format
225
+ * Extracts tool calls from Gab AI API responses (OpenAI format)
226
+ *
227
+ * @param response Gab AI API response object
228
+ * @returns Array of tool call requests
229
+ */
230
+ parseToolCalls: (response) => {
231
+ logger.debug("Parsing tool calls from Gab AI response", {
232
+ context: "plugin.parseToolCalls"
233
+ });
234
+ try {
235
+ const toolCalls = (0, import_plugin_utils2.parseOpenAIToolCalls)(response);
236
+ logger.debug("Successfully parsed tool calls", {
237
+ context: "plugin.parseToolCalls",
238
+ count: toolCalls.length
239
+ });
240
+ return toolCalls;
241
+ } catch (error) {
242
+ logger.error(
243
+ "Error parsing tool calls from Gab AI response",
244
+ { context: "plugin.parseToolCalls" },
245
+ error instanceof Error ? error : void 0
246
+ );
247
+ return [];
248
+ }
249
+ }
250
+ };
251
+ var index_default = plugin;
252
+ // Annotate the CommonJS export names for ESM import in node:
253
+ 0 && (module.exports = {
254
+ plugin
255
+ });
package/index.ts ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Gab AI Provider Plugin for Quilltap
3
+ * Main entry point that exports the plugin configuration
4
+ *
5
+ * This plugin provides:
6
+ * - Chat completion using Gab AI language models
7
+ * - Support for streaming responses
8
+ * - Text-only interactions (no file attachments)
9
+ *
10
+ * Implementation note: GabAIProvider extends OpenAICompatibleProvider,
11
+ * inheriting all the OpenAI-compatible logic with Gab-specific configuration.
12
+ */
13
+
14
+ import type { LLMProviderPlugin } from './types';
15
+ import { GabAIProvider } from './provider';
16
+ import { GabAIIcon } from './icon';
17
+ import {
18
+ createPluginLogger,
19
+ parseOpenAIToolCalls,
20
+ type OpenAIToolDefinition,
21
+ type ToolCallRequest,
22
+ } from '@quilltap/plugin-utils';
23
+
24
+ const logger = createPluginLogger('GabAI');
25
+
26
+ /**
27
+ * Plugin metadata configuration
28
+ */
29
+ const metadata = {
30
+ providerName: 'GAB_AI',
31
+ displayName: 'Gab AI',
32
+ description: 'Gab AI language models for chat completions',
33
+ colors: {
34
+ bg: 'bg-green-100',
35
+ text: 'text-green-800',
36
+ icon: 'text-green-600',
37
+ },
38
+ abbreviation: 'GAB',
39
+ } as const;
40
+
41
+ /**
42
+ * Configuration requirements
43
+ */
44
+ const config = {
45
+ requiresApiKey: true,
46
+ requiresBaseUrl: false,
47
+ apiKeyLabel: 'Gab AI API Key',
48
+ } as const;
49
+
50
+ /**
51
+ * Supported capabilities
52
+ */
53
+ const capabilities = {
54
+ chat: true,
55
+ imageGeneration: false,
56
+ embeddings: false,
57
+ webSearch: false,
58
+ } as const;
59
+
60
+ /**
61
+ * File attachment support
62
+ */
63
+ const attachmentSupport = {
64
+ supportsAttachments: false as const,
65
+ supportedMimeTypes: [] as string[],
66
+ description: 'No attachment support (text only)',
67
+ notes: 'Gab AI does not currently support file attachments',
68
+ };
69
+
70
+ /**
71
+ * The Gab AI Provider Plugin
72
+ * Implements the LLMProviderPlugin interface for Quilltap
73
+ *
74
+ * Note: The underlying GabAIProvider extends OpenAICompatibleProvider,
75
+ * so all the core functionality is inherited from the openai-compatible plugin.
76
+ */
77
+ export const plugin: LLMProviderPlugin = {
78
+ metadata,
79
+
80
+ config,
81
+
82
+ capabilities,
83
+
84
+ attachmentSupport,
85
+
86
+ /**
87
+ * Factory method to create a Gab AI LLM provider instance
88
+ */
89
+ createProvider: (baseUrl?: string) => {
90
+ logger.debug('Creating Gab AI provider instance', { context: 'plugin.createProvider', baseUrl });
91
+ return new GabAIProvider();
92
+ },
93
+
94
+ /**
95
+ * Get list of available models from Gab AI API
96
+ * Requires a valid API key
97
+ */
98
+ getAvailableModels: async (apiKey: string, baseUrl?: string) => {
99
+ logger.debug('Fetching available Gab AI models', { context: 'plugin.getAvailableModels' });
100
+ try {
101
+ const provider = new GabAIProvider();
102
+ const models = await provider.getAvailableModels(apiKey);
103
+ logger.debug('Successfully fetched Gab AI models', { context: 'plugin.getAvailableModels', count: models.length });
104
+ return models;
105
+ } catch (error) {
106
+ logger.error('Failed to fetch Gab AI models', { context: 'plugin.getAvailableModels' }, error instanceof Error ? error : undefined);
107
+ return [];
108
+ }
109
+ },
110
+
111
+ /**
112
+ * Validate a Gab AI API key
113
+ */
114
+ validateApiKey: async (apiKey: string, baseUrl?: string) => {
115
+ logger.debug('Validating Gab AI API key', { context: 'plugin.validateApiKey' });
116
+ try {
117
+ const provider = new GabAIProvider();
118
+ const isValid = await provider.validateApiKey(apiKey);
119
+ logger.debug('Gab AI API key validation result', { context: 'plugin.validateApiKey', isValid });
120
+ return isValid;
121
+ } catch (error) {
122
+ logger.error('Error validating Gab AI API key', { context: 'plugin.validateApiKey' }, error instanceof Error ? error : undefined);
123
+ return false;
124
+ }
125
+ },
126
+
127
+ /**
128
+ * Get static model information
129
+ * Returns cached information about Gab AI models without needing API calls
130
+ */
131
+ getModelInfo: () => {
132
+ logger.debug('Getting Gab AI model information', { context: 'plugin.getModelInfo' });
133
+ return [
134
+ {
135
+ id: 'gab-01',
136
+ name: 'Gab AI 01',
137
+ contextWindow: 128000,
138
+ maxOutputTokens: 4096,
139
+ supportsImages: false,
140
+ supportsTools: false,
141
+ },
142
+ ];
143
+ },
144
+
145
+ /**
146
+ * Render the Gab AI icon
147
+ */
148
+ renderIcon: (props) => {
149
+ logger.debug('Rendering Gab AI icon', { context: 'plugin.renderIcon', className: props.className });
150
+ return GabAIIcon(props);
151
+ },
152
+
153
+ /**
154
+ * Format tools from OpenAI format to OpenAI format
155
+ * Gab AI uses OpenAI format (inherited from OpenAI-compatible)
156
+ *
157
+ * @param tools Array of tools in OpenAI format
158
+ * @returns Array of tools in OpenAI format
159
+ */
160
+ formatTools: (
161
+ tools: (OpenAIToolDefinition | Record<string, unknown>)[],
162
+ ): OpenAIToolDefinition[] => {
163
+ logger.debug('Formatting tools for Gab AI provider', {
164
+ context: 'plugin.formatTools',
165
+ toolCount: tools.length,
166
+ });
167
+
168
+ try {
169
+ const formattedTools: OpenAIToolDefinition[] = [];
170
+
171
+ for (const tool of tools) {
172
+ // Validate tool has function property (OpenAI format)
173
+ if (!('function' in tool)) {
174
+ logger.warn('Skipping tool with invalid format', {
175
+ context: 'plugin.formatTools',
176
+ });
177
+ continue;
178
+ }
179
+
180
+ // Tools already in OpenAI format, pass through
181
+ formattedTools.push(tool as OpenAIToolDefinition);
182
+ }
183
+
184
+ logger.debug('Successfully formatted tools', {
185
+ context: 'plugin.formatTools',
186
+ count: formattedTools.length,
187
+ });
188
+
189
+ return formattedTools;
190
+ } catch (error) {
191
+ logger.error(
192
+ 'Error formatting tools for Gab AI',
193
+ { context: 'plugin.formatTools' },
194
+ error instanceof Error ? error : undefined
195
+ );
196
+ return [];
197
+ }
198
+ },
199
+
200
+ /**
201
+ * Parse tool calls from Gab AI response format
202
+ * Extracts tool calls from Gab AI API responses (OpenAI format)
203
+ *
204
+ * @param response Gab AI API response object
205
+ * @returns Array of tool call requests
206
+ */
207
+ parseToolCalls: (response: any): ToolCallRequest[] => {
208
+ logger.debug('Parsing tool calls from Gab AI response', {
209
+ context: 'plugin.parseToolCalls',
210
+ });
211
+
212
+ try {
213
+ const toolCalls = parseOpenAIToolCalls(response);
214
+
215
+ logger.debug('Successfully parsed tool calls', {
216
+ context: 'plugin.parseToolCalls',
217
+ count: toolCalls.length,
218
+ });
219
+
220
+ return toolCalls;
221
+ } catch (error) {
222
+ logger.error(
223
+ 'Error parsing tool calls from Gab AI response',
224
+ { context: 'plugin.parseToolCalls' },
225
+ error instanceof Error ? error : undefined
226
+ );
227
+ return [];
228
+ }
229
+ },
230
+ };
231
+
232
+ export default plugin;
package/manifest.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "$schema": "../qtap-plugin-template/schemas/plugin-manifest.schema.json",
3
+ "name": "qtap-plugin-gab-ai",
4
+ "title": "Gab AI Provider",
5
+ "description": "Provides Gab AI language models for Quilltap",
6
+ "version": "1.0.0",
7
+ "author": {
8
+ "name": "Foundry-9 LLC",
9
+ "email": "charles.sebold@foundry-9.com",
10
+ "url": "https://foundry-9.com"
11
+ },
12
+ "license": "MIT",
13
+ "compatibility": {
14
+ "quilltapVersion": ">=1.7.0",
15
+ "nodeVersion": ">=18.0.0"
16
+ },
17
+ "capabilities": ["LLM_PROVIDER"],
18
+ "category": "PROVIDER",
19
+ "main": "index.js",
20
+ "typescript": true,
21
+ "frontend": "REACT",
22
+ "styling": "TAILWIND",
23
+ "enabledByDefault": true,
24
+ "status": "STABLE",
25
+ "keywords": ["gab", "gab-ai", "llm", "ai", "chat"],
26
+ "providerConfig": {
27
+ "providerName": "GAB_AI",
28
+ "displayName": "Gab AI",
29
+ "description": "Gab AI language models for chat completions",
30
+ "abbreviation": "GAB",
31
+ "colors": {
32
+ "bg": "bg-green-100",
33
+ "text": "text-green-800",
34
+ "icon": "text-green-600"
35
+ },
36
+ "requiresApiKey": true,
37
+ "requiresBaseUrl": false,
38
+ "apiKeyLabel": "Gab AI API Key",
39
+ "capabilities": {
40
+ "chat": true,
41
+ "imageGeneration": false,
42
+ "embeddings": false,
43
+ "webSearch": false
44
+ },
45
+ "attachmentSupport": {
46
+ "supported": false,
47
+ "mimeTypes": [],
48
+ "description": "No attachment support (text only)"
49
+ }
50
+ },
51
+ "permissions": {
52
+ "network": ["api.gab.com"],
53
+ "userData": false,
54
+ "database": false
55
+ }
56
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@quilltap/qtap-plugin-gab-ai",
3
+ "version": "1.1.0",
4
+ "description": "Gab AI provider plugin for Quilltap",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "author": "Foundry-9 LLC",
8
+ "license": "MIT",
9
+ "scripts": {
10
+ "build": "node esbuild.config.mjs",
11
+ "clean": "rm -f index.js"
12
+ },
13
+ "dependencies": {
14
+ "@quilltap/plugin-utils": "^1.1.0"
15
+ },
16
+ "devDependencies": {
17
+ "esbuild": "^0.24.0"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ }
22
+ }
package/provider.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Gab AI Provider Implementation for Quilltap Plugin
3
+ *
4
+ * Gab AI is OpenAI-compatible and provides language models via api.gab.com/v1
5
+ * This provider extends OpenAICompatibleProvider from @quilltap/plugin-utils
6
+ * with static configuration specific to the Gab AI service.
7
+ *
8
+ * Note: Gab AI does not currently support file attachments or image generation.
9
+ */
10
+
11
+ import { OpenAICompatibleProvider } from '@quilltap/plugin-utils';
12
+
13
+ /**
14
+ * Gab AI Provider - extends OpenAICompatibleProvider with Gab-specific configuration
15
+ *
16
+ * Configuration:
17
+ * - baseUrl: https://gab.ai/v1 (fixed)
18
+ * - API key: required
19
+ * - Attachments: not supported
20
+ * - Image generation: not supported
21
+ */
22
+ export class GabAIProvider extends OpenAICompatibleProvider {
23
+ constructor() {
24
+ super({
25
+ baseUrl: 'https://gab.ai/v1',
26
+ providerName: 'GabAI',
27
+ requireApiKey: true,
28
+ attachmentErrorMessage: 'Gab AI does not support file attachments',
29
+ });
30
+ }
31
+ }
package/types.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Type exports for Gab AI Plugin
3
+ * Re-exports types from @quilltap/plugin-types for use within the plugin
4
+ */
5
+
6
+ export type {
7
+ FileAttachment,
8
+ ImageGenParams,
9
+ GeneratedImage,
10
+ ImageGenResponse,
11
+ LLMMessage,
12
+ LLMParams,
13
+ LLMResponse,
14
+ StreamChunk,
15
+ LLMProvider,
16
+ LLMProviderPlugin,
17
+ ProviderMetadata,
18
+ ProviderConfigRequirements,
19
+ ProviderCapabilities,
20
+ AttachmentSupport,
21
+ ModelInfo,
22
+ } from '@quilltap/plugin-types';