@quilltap/plugin-utils 1.0.0 → 1.2.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.
@@ -0,0 +1,166 @@
1
+ import { LLMProvider, PluginLogger, LLMParams, LLMResponse, StreamChunk, ImageGenParams, ImageGenResponse } from '@quilltap/plugin-types';
2
+
3
+ /**
4
+ * OpenAI-Compatible Provider Base Class
5
+ *
6
+ * A reusable base class for building LLM providers that use OpenAI-compatible APIs.
7
+ * This includes services like:
8
+ * - Local LLM servers (LM Studio, vLLM, Text Generation Web UI, Ollama with OpenAI compat)
9
+ * - Cloud services with OpenAI-compatible APIs (Gab AI, Together AI, Fireworks, etc.)
10
+ *
11
+ * External plugins can extend this class to create custom providers with minimal code:
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { OpenAICompatibleProvider } from '@quilltap/plugin-utils';
16
+ *
17
+ * export class MyCustomProvider extends OpenAICompatibleProvider {
18
+ * constructor() {
19
+ * super({
20
+ * baseUrl: 'https://api.my-service.com/v1',
21
+ * providerName: 'MyService',
22
+ * requireApiKey: true,
23
+ * attachmentErrorMessage: 'MyService does not support file attachments',
24
+ * });
25
+ * }
26
+ * }
27
+ * ```
28
+ *
29
+ * @packageDocumentation
30
+ */
31
+
32
+ /**
33
+ * Configuration options for OpenAI-compatible providers.
34
+ *
35
+ * Use this interface when extending OpenAICompatibleProvider to customize
36
+ * the provider's behavior for your specific service.
37
+ */
38
+ interface OpenAICompatibleProviderConfig {
39
+ /**
40
+ * Base URL for the API endpoint.
41
+ * Should include the version path (e.g., 'https://api.example.com/v1')
42
+ */
43
+ baseUrl: string;
44
+ /**
45
+ * Provider name used for logging context.
46
+ * This appears in log messages to identify which provider generated them.
47
+ * @default 'OpenAICompatible'
48
+ */
49
+ providerName?: string;
50
+ /**
51
+ * Whether an API key is required for this provider.
52
+ * If true, requests will fail with an error when no API key is provided.
53
+ * If false, requests will use 'not-needed' as the API key (for local servers).
54
+ * @default false
55
+ */
56
+ requireApiKey?: boolean;
57
+ /**
58
+ * Custom error message shown when file attachments are attempted.
59
+ * @default 'OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)'
60
+ */
61
+ attachmentErrorMessage?: string;
62
+ }
63
+ /**
64
+ * Base provider class for OpenAI-compatible APIs.
65
+ *
66
+ * This class implements the full LLMProvider interface using the OpenAI SDK,
67
+ * allowing subclasses to create custom providers with just configuration.
68
+ *
69
+ * Features:
70
+ * - Streaming and non-streaming chat completions
71
+ * - API key validation
72
+ * - Model listing
73
+ * - Configurable API key requirements
74
+ * - Dynamic logging with provider name context
75
+ *
76
+ * @remarks
77
+ * File attachments and image generation are not supported by default,
78
+ * as support varies across OpenAI-compatible implementations.
79
+ */
80
+ declare class OpenAICompatibleProvider implements LLMProvider {
81
+ /** File attachments are not supported by default */
82
+ readonly supportsFileAttachments = false;
83
+ /** No MIME types are supported for attachments */
84
+ readonly supportedMimeTypes: string[];
85
+ /** Image generation is not supported by default */
86
+ readonly supportsImageGeneration = false;
87
+ /** Web search is not supported */
88
+ readonly supportsWebSearch = false;
89
+ /** Base URL for the API endpoint */
90
+ protected readonly baseUrl: string;
91
+ /** Provider name for logging */
92
+ protected readonly providerName: string;
93
+ /** Whether API key is required */
94
+ protected readonly requireApiKey: boolean;
95
+ /** Error message for attachment failures */
96
+ protected readonly attachmentErrorMessage: string;
97
+ /** Logger instance */
98
+ protected readonly logger: PluginLogger;
99
+ /**
100
+ * Creates a new OpenAI-compatible provider instance.
101
+ *
102
+ * @param config - Configuration object or base URL string (for backward compatibility)
103
+ */
104
+ constructor(config: string | OpenAICompatibleProviderConfig);
105
+ /**
106
+ * Collects attachment failures for messages with attachments.
107
+ * Since attachments are not supported, all attachments are marked as failed.
108
+ *
109
+ * @param params - LLM parameters containing messages
110
+ * @returns Object with empty sent array and failed attachments
111
+ */
112
+ protected collectAttachmentFailures(params: LLMParams): {
113
+ sent: string[];
114
+ failed: {
115
+ id: string;
116
+ error: string;
117
+ }[];
118
+ };
119
+ /**
120
+ * Validates that an API key is provided when required.
121
+ * @throws Error if API key is required but not provided
122
+ */
123
+ protected validateApiKeyRequirement(apiKey: string): void;
124
+ /**
125
+ * Gets the effective API key to use for requests.
126
+ * Returns 'not-needed' for providers that don't require keys.
127
+ */
128
+ protected getEffectiveApiKey(apiKey: string): string;
129
+ /**
130
+ * Sends a message and returns the complete response.
131
+ *
132
+ * @param params - LLM parameters including messages, model, and settings
133
+ * @param apiKey - API key for authentication
134
+ * @returns Complete LLM response with content and usage statistics
135
+ */
136
+ sendMessage(params: LLMParams, apiKey: string): Promise<LLMResponse>;
137
+ /**
138
+ * Sends a message and streams the response.
139
+ *
140
+ * @param params - LLM parameters including messages, model, and settings
141
+ * @param apiKey - API key for authentication
142
+ * @yields Stream chunks with content and final usage statistics
143
+ */
144
+ streamMessage(params: LLMParams, apiKey: string): AsyncGenerator<StreamChunk>;
145
+ /**
146
+ * Validates an API key by attempting to list models.
147
+ *
148
+ * @param apiKey - API key to validate
149
+ * @returns true if the API key is valid, false otherwise
150
+ */
151
+ validateApiKey(apiKey: string): Promise<boolean>;
152
+ /**
153
+ * Fetches available models from the API.
154
+ *
155
+ * @param apiKey - API key for authentication
156
+ * @returns Sorted array of model IDs, or empty array on failure
157
+ */
158
+ getAvailableModels(apiKey: string): Promise<string[]>;
159
+ /**
160
+ * Image generation is not supported by default.
161
+ * @throws Error indicating image generation is not supported
162
+ */
163
+ generateImage(_params: ImageGenParams, _apiKey: string): Promise<ImageGenResponse>;
164
+ }
165
+
166
+ export { OpenAICompatibleProvider, type OpenAICompatibleProviderConfig };
@@ -0,0 +1,385 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/providers/index.ts
31
+ var providers_exports = {};
32
+ __export(providers_exports, {
33
+ OpenAICompatibleProvider: () => OpenAICompatibleProvider
34
+ });
35
+ module.exports = __toCommonJS(providers_exports);
36
+
37
+ // src/providers/openai-compatible.ts
38
+ var import_openai = __toESM(require("openai"));
39
+
40
+ // src/logging/plugin-logger.ts
41
+ function getCoreLoggerFactory() {
42
+ return globalThis.__quilltap_logger_factory ?? null;
43
+ }
44
+ function createConsoleLoggerWithChild(prefix, minLevel = "debug", baseContext = {}) {
45
+ const levels = ["debug", "info", "warn", "error"];
46
+ const shouldLog = (level) => levels.indexOf(level) >= levels.indexOf(minLevel);
47
+ const formatContext = (context) => {
48
+ const merged = { ...baseContext, ...context };
49
+ const entries = Object.entries(merged).filter(([key]) => key !== "context").map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ");
50
+ return entries ? ` ${entries}` : "";
51
+ };
52
+ const logger = {
53
+ debug: (message, context) => {
54
+ if (shouldLog("debug")) {
55
+ console.debug(`[${prefix}] ${message}${formatContext(context)}`);
56
+ }
57
+ },
58
+ info: (message, context) => {
59
+ if (shouldLog("info")) {
60
+ console.info(`[${prefix}] ${message}${formatContext(context)}`);
61
+ }
62
+ },
63
+ warn: (message, context) => {
64
+ if (shouldLog("warn")) {
65
+ console.warn(`[${prefix}] ${message}${formatContext(context)}`);
66
+ }
67
+ },
68
+ error: (message, context, error) => {
69
+ if (shouldLog("error")) {
70
+ console.error(
71
+ `[${prefix}] ${message}${formatContext(context)}`,
72
+ error ? `
73
+ ${error.stack || error.message}` : ""
74
+ );
75
+ }
76
+ },
77
+ child: (additionalContext) => {
78
+ return createConsoleLoggerWithChild(prefix, minLevel, {
79
+ ...baseContext,
80
+ ...additionalContext
81
+ });
82
+ }
83
+ };
84
+ return logger;
85
+ }
86
+ function createPluginLogger(pluginName, minLevel = "debug") {
87
+ const coreFactory = getCoreLoggerFactory();
88
+ if (coreFactory) {
89
+ return coreFactory(pluginName);
90
+ }
91
+ return createConsoleLoggerWithChild(pluginName, minLevel);
92
+ }
93
+
94
+ // src/logging/index.ts
95
+ var import_plugin_types = require("@quilltap/plugin-types");
96
+
97
+ // src/providers/openai-compatible.ts
98
+ var OpenAICompatibleProvider = class {
99
+ /**
100
+ * Creates a new OpenAI-compatible provider instance.
101
+ *
102
+ * @param config - Configuration object or base URL string (for backward compatibility)
103
+ */
104
+ constructor(config) {
105
+ /** File attachments are not supported by default */
106
+ this.supportsFileAttachments = false;
107
+ /** No MIME types are supported for attachments */
108
+ this.supportedMimeTypes = [];
109
+ /** Image generation is not supported by default */
110
+ this.supportsImageGeneration = false;
111
+ /** Web search is not supported */
112
+ this.supportsWebSearch = false;
113
+ if (typeof config === "string") {
114
+ this.baseUrl = config;
115
+ this.providerName = "OpenAICompatible";
116
+ this.requireApiKey = false;
117
+ this.attachmentErrorMessage = "OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)";
118
+ } else {
119
+ this.baseUrl = config.baseUrl;
120
+ this.providerName = config.providerName ?? "OpenAICompatible";
121
+ this.requireApiKey = config.requireApiKey ?? false;
122
+ this.attachmentErrorMessage = config.attachmentErrorMessage ?? "OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)";
123
+ }
124
+ this.logger = createPluginLogger(`${this.providerName}Provider`);
125
+ this.logger.debug(`${this.providerName} provider instantiated`, {
126
+ context: `${this.providerName}Provider.constructor`,
127
+ baseUrl: this.baseUrl
128
+ });
129
+ }
130
+ /**
131
+ * Collects attachment failures for messages with attachments.
132
+ * Since attachments are not supported, all attachments are marked as failed.
133
+ *
134
+ * @param params - LLM parameters containing messages
135
+ * @returns Object with empty sent array and failed attachments
136
+ */
137
+ collectAttachmentFailures(params) {
138
+ const failed = [];
139
+ for (const msg of params.messages) {
140
+ if (msg.attachments) {
141
+ for (const attachment of msg.attachments) {
142
+ failed.push({
143
+ id: attachment.id,
144
+ error: this.attachmentErrorMessage
145
+ });
146
+ }
147
+ }
148
+ }
149
+ return { sent: [], failed };
150
+ }
151
+ /**
152
+ * Validates that an API key is provided when required.
153
+ * @throws Error if API key is required but not provided
154
+ */
155
+ validateApiKeyRequirement(apiKey) {
156
+ if (this.requireApiKey && !apiKey) {
157
+ throw new Error(`${this.providerName} provider requires an API key`);
158
+ }
159
+ }
160
+ /**
161
+ * Gets the effective API key to use for requests.
162
+ * Returns 'not-needed' for providers that don't require keys.
163
+ */
164
+ getEffectiveApiKey(apiKey) {
165
+ return this.requireApiKey ? apiKey : apiKey || "not-needed";
166
+ }
167
+ /**
168
+ * Sends a message and returns the complete response.
169
+ *
170
+ * @param params - LLM parameters including messages, model, and settings
171
+ * @param apiKey - API key for authentication
172
+ * @returns Complete LLM response with content and usage statistics
173
+ */
174
+ async sendMessage(params, apiKey) {
175
+ this.logger.debug(`${this.providerName} sendMessage called`, {
176
+ context: `${this.providerName}Provider.sendMessage`,
177
+ model: params.model,
178
+ baseUrl: this.baseUrl
179
+ });
180
+ this.validateApiKeyRequirement(apiKey);
181
+ const attachmentResults = this.collectAttachmentFailures(params);
182
+ const client = new import_openai.default({
183
+ apiKey: this.getEffectiveApiKey(apiKey),
184
+ baseURL: this.baseUrl
185
+ });
186
+ const messages = params.messages.filter((m) => m.role !== "tool").map((m) => ({
187
+ role: m.role,
188
+ content: m.content
189
+ }));
190
+ try {
191
+ const response = await client.chat.completions.create({
192
+ model: params.model,
193
+ messages,
194
+ temperature: params.temperature ?? 0.7,
195
+ max_tokens: params.maxTokens ?? 4096,
196
+ top_p: params.topP ?? 1,
197
+ stop: params.stop
198
+ });
199
+ const choice = response.choices[0];
200
+ this.logger.debug(`Received ${this.providerName} response`, {
201
+ context: `${this.providerName}Provider.sendMessage`,
202
+ finishReason: choice.finish_reason,
203
+ promptTokens: response.usage?.prompt_tokens,
204
+ completionTokens: response.usage?.completion_tokens
205
+ });
206
+ return {
207
+ content: choice.message.content ?? "",
208
+ finishReason: choice.finish_reason,
209
+ usage: {
210
+ promptTokens: response.usage?.prompt_tokens ?? 0,
211
+ completionTokens: response.usage?.completion_tokens ?? 0,
212
+ totalTokens: response.usage?.total_tokens ?? 0
213
+ },
214
+ raw: response,
215
+ attachmentResults
216
+ };
217
+ } catch (error) {
218
+ this.logger.error(
219
+ `${this.providerName} API error in sendMessage`,
220
+ { context: `${this.providerName}Provider.sendMessage`, baseUrl: this.baseUrl },
221
+ error instanceof Error ? error : void 0
222
+ );
223
+ throw error;
224
+ }
225
+ }
226
+ /**
227
+ * Sends a message and streams the response.
228
+ *
229
+ * @param params - LLM parameters including messages, model, and settings
230
+ * @param apiKey - API key for authentication
231
+ * @yields Stream chunks with content and final usage statistics
232
+ */
233
+ async *streamMessage(params, apiKey) {
234
+ this.logger.debug(`${this.providerName} streamMessage called`, {
235
+ context: `${this.providerName}Provider.streamMessage`,
236
+ model: params.model,
237
+ baseUrl: this.baseUrl
238
+ });
239
+ this.validateApiKeyRequirement(apiKey);
240
+ const attachmentResults = this.collectAttachmentFailures(params);
241
+ const client = new import_openai.default({
242
+ apiKey: this.getEffectiveApiKey(apiKey),
243
+ baseURL: this.baseUrl
244
+ });
245
+ const messages = params.messages.filter((m) => m.role !== "tool").map((m) => ({
246
+ role: m.role,
247
+ content: m.content
248
+ }));
249
+ try {
250
+ const stream = await client.chat.completions.create({
251
+ model: params.model,
252
+ messages,
253
+ temperature: params.temperature ?? 0.7,
254
+ max_tokens: params.maxTokens ?? 4096,
255
+ top_p: params.topP ?? 1,
256
+ stream: true,
257
+ stream_options: { include_usage: true }
258
+ });
259
+ let chunkCount = 0;
260
+ for await (const chunk of stream) {
261
+ chunkCount++;
262
+ const content = chunk.choices[0]?.delta?.content;
263
+ const finishReason = chunk.choices[0]?.finish_reason;
264
+ const hasUsage = chunk.usage;
265
+ if (content && !(finishReason && hasUsage)) {
266
+ yield {
267
+ content,
268
+ done: false
269
+ };
270
+ }
271
+ if (finishReason && hasUsage) {
272
+ this.logger.debug("Stream completed", {
273
+ context: `${this.providerName}Provider.streamMessage`,
274
+ finishReason,
275
+ chunks: chunkCount,
276
+ promptTokens: chunk.usage?.prompt_tokens,
277
+ completionTokens: chunk.usage?.completion_tokens
278
+ });
279
+ yield {
280
+ content: "",
281
+ done: true,
282
+ usage: {
283
+ promptTokens: chunk.usage?.prompt_tokens ?? 0,
284
+ completionTokens: chunk.usage?.completion_tokens ?? 0,
285
+ totalTokens: chunk.usage?.total_tokens ?? 0
286
+ },
287
+ attachmentResults
288
+ };
289
+ }
290
+ }
291
+ } catch (error) {
292
+ this.logger.error(
293
+ `${this.providerName} API error in streamMessage`,
294
+ { context: `${this.providerName}Provider.streamMessage`, baseUrl: this.baseUrl },
295
+ error instanceof Error ? error : void 0
296
+ );
297
+ throw error;
298
+ }
299
+ }
300
+ /**
301
+ * Validates an API key by attempting to list models.
302
+ *
303
+ * @param apiKey - API key to validate
304
+ * @returns true if the API key is valid, false otherwise
305
+ */
306
+ async validateApiKey(apiKey) {
307
+ this.logger.debug(`Validating ${this.providerName} API connection`, {
308
+ context: `${this.providerName}Provider.validateApiKey`,
309
+ baseUrl: this.baseUrl
310
+ });
311
+ if (this.requireApiKey && !apiKey) {
312
+ return false;
313
+ }
314
+ try {
315
+ const client = new import_openai.default({
316
+ apiKey: this.getEffectiveApiKey(apiKey),
317
+ baseURL: this.baseUrl
318
+ });
319
+ await client.models.list();
320
+ this.logger.debug(`${this.providerName} API validation successful`, {
321
+ context: `${this.providerName}Provider.validateApiKey`
322
+ });
323
+ return true;
324
+ } catch (error) {
325
+ this.logger.error(
326
+ `${this.providerName} API validation failed`,
327
+ { context: `${this.providerName}Provider.validateApiKey`, baseUrl: this.baseUrl },
328
+ error instanceof Error ? error : void 0
329
+ );
330
+ return false;
331
+ }
332
+ }
333
+ /**
334
+ * Fetches available models from the API.
335
+ *
336
+ * @param apiKey - API key for authentication
337
+ * @returns Sorted array of model IDs, or empty array on failure
338
+ */
339
+ async getAvailableModels(apiKey) {
340
+ this.logger.debug(`Fetching ${this.providerName} models`, {
341
+ context: `${this.providerName}Provider.getAvailableModels`,
342
+ baseUrl: this.baseUrl
343
+ });
344
+ if (this.requireApiKey && !apiKey) {
345
+ this.logger.error(`${this.providerName} provider requires an API key to fetch models`, {
346
+ context: `${this.providerName}Provider.getAvailableModels`
347
+ });
348
+ return [];
349
+ }
350
+ try {
351
+ const client = new import_openai.default({
352
+ apiKey: this.getEffectiveApiKey(apiKey),
353
+ baseURL: this.baseUrl
354
+ });
355
+ const models = await client.models.list();
356
+ const modelList = models.data.map((m) => m.id).sort();
357
+ this.logger.debug(`Retrieved ${this.providerName} models`, {
358
+ context: `${this.providerName}Provider.getAvailableModels`,
359
+ modelCount: modelList.length
360
+ });
361
+ return modelList;
362
+ } catch (error) {
363
+ this.logger.error(
364
+ `Failed to fetch ${this.providerName} models`,
365
+ { context: `${this.providerName}Provider.getAvailableModels`, baseUrl: this.baseUrl },
366
+ error instanceof Error ? error : void 0
367
+ );
368
+ return [];
369
+ }
370
+ }
371
+ /**
372
+ * Image generation is not supported by default.
373
+ * @throws Error indicating image generation is not supported
374
+ */
375
+ async generateImage(_params, _apiKey) {
376
+ throw new Error(
377
+ `${this.providerName} image generation support varies by implementation (not yet implemented)`
378
+ );
379
+ }
380
+ };
381
+ // Annotate the CommonJS export names for ESM import in node:
382
+ 0 && (module.exports = {
383
+ OpenAICompatibleProvider
384
+ });
385
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/providers/index.ts","../../src/providers/openai-compatible.ts","../../src/logging/plugin-logger.ts","../../src/logging/index.ts"],"sourcesContent":["/**\n * Provider Base Classes\n *\n * Reusable base classes for building LLM provider plugins.\n * External plugins can extend these classes to create custom providers\n * with minimal boilerplate.\n *\n * @packageDocumentation\n */\n\nexport {\n OpenAICompatibleProvider,\n type OpenAICompatibleProviderConfig,\n} from './openai-compatible';\n","/**\n * OpenAI-Compatible Provider Base Class\n *\n * A reusable base class for building LLM providers that use OpenAI-compatible APIs.\n * This includes services like:\n * - Local LLM servers (LM Studio, vLLM, Text Generation Web UI, Ollama with OpenAI compat)\n * - Cloud services with OpenAI-compatible APIs (Gab AI, Together AI, Fireworks, etc.)\n *\n * External plugins can extend this class to create custom providers with minimal code:\n *\n * @example\n * ```typescript\n * import { OpenAICompatibleProvider } from '@quilltap/plugin-utils';\n *\n * export class MyCustomProvider extends OpenAICompatibleProvider {\n * constructor() {\n * super({\n * baseUrl: 'https://api.my-service.com/v1',\n * providerName: 'MyService',\n * requireApiKey: true,\n * attachmentErrorMessage: 'MyService does not support file attachments',\n * });\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport OpenAI from 'openai';\nimport type {\n LLMProvider,\n LLMParams,\n LLMResponse,\n StreamChunk,\n ImageGenParams,\n ImageGenResponse,\n PluginLogger,\n} from '@quilltap/plugin-types';\nimport { createPluginLogger } from '../logging';\n\n/**\n * Configuration options for OpenAI-compatible providers.\n *\n * Use this interface when extending OpenAICompatibleProvider to customize\n * the provider's behavior for your specific service.\n */\nexport interface OpenAICompatibleProviderConfig {\n /**\n * Base URL for the API endpoint.\n * Should include the version path (e.g., 'https://api.example.com/v1')\n */\n baseUrl: string;\n\n /**\n * Provider name used for logging context.\n * This appears in log messages to identify which provider generated them.\n * @default 'OpenAICompatible'\n */\n providerName?: string;\n\n /**\n * Whether an API key is required for this provider.\n * If true, requests will fail with an error when no API key is provided.\n * If false, requests will use 'not-needed' as the API key (for local servers).\n * @default false\n */\n requireApiKey?: boolean;\n\n /**\n * Custom error message shown when file attachments are attempted.\n * @default 'OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)'\n */\n attachmentErrorMessage?: string;\n}\n\n/**\n * Base provider class for OpenAI-compatible APIs.\n *\n * This class implements the full LLMProvider interface using the OpenAI SDK,\n * allowing subclasses to create custom providers with just configuration.\n *\n * Features:\n * - Streaming and non-streaming chat completions\n * - API key validation\n * - Model listing\n * - Configurable API key requirements\n * - Dynamic logging with provider name context\n *\n * @remarks\n * File attachments and image generation are not supported by default,\n * as support varies across OpenAI-compatible implementations.\n */\nexport class OpenAICompatibleProvider implements LLMProvider {\n /** File attachments are not supported by default */\n readonly supportsFileAttachments = false;\n /** No MIME types are supported for attachments */\n readonly supportedMimeTypes: string[] = [];\n /** Image generation is not supported by default */\n readonly supportsImageGeneration = false;\n /** Web search is not supported */\n readonly supportsWebSearch = false;\n\n /** Base URL for the API endpoint */\n protected readonly baseUrl: string;\n /** Provider name for logging */\n protected readonly providerName: string;\n /** Whether API key is required */\n protected readonly requireApiKey: boolean;\n /** Error message for attachment failures */\n protected readonly attachmentErrorMessage: string;\n /** Logger instance */\n protected readonly logger: PluginLogger;\n\n /**\n * Creates a new OpenAI-compatible provider instance.\n *\n * @param config - Configuration object or base URL string (for backward compatibility)\n */\n constructor(config: string | OpenAICompatibleProviderConfig) {\n // Support both legacy string baseUrl and new config object\n if (typeof config === 'string') {\n this.baseUrl = config;\n this.providerName = 'OpenAICompatible';\n this.requireApiKey = false;\n this.attachmentErrorMessage =\n 'OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)';\n } else {\n this.baseUrl = config.baseUrl;\n this.providerName = config.providerName ?? 'OpenAICompatible';\n this.requireApiKey = config.requireApiKey ?? false;\n this.attachmentErrorMessage =\n config.attachmentErrorMessage ??\n 'OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)';\n }\n\n this.logger = createPluginLogger(`${this.providerName}Provider`);\n\n this.logger.debug(`${this.providerName} provider instantiated`, {\n context: `${this.providerName}Provider.constructor`,\n baseUrl: this.baseUrl,\n });\n }\n\n /**\n * Collects attachment failures for messages with attachments.\n * Since attachments are not supported, all attachments are marked as failed.\n *\n * @param params - LLM parameters containing messages\n * @returns Object with empty sent array and failed attachments\n */\n protected collectAttachmentFailures(\n params: LLMParams\n ): { sent: string[]; failed: { id: string; error: string }[] } {\n const failed: { id: string; error: string }[] = [];\n for (const msg of params.messages) {\n if (msg.attachments) {\n for (const attachment of msg.attachments) {\n failed.push({\n id: attachment.id,\n error: this.attachmentErrorMessage,\n });\n }\n }\n }\n return { sent: [], failed };\n }\n\n /**\n * Validates that an API key is provided when required.\n * @throws Error if API key is required but not provided\n */\n protected validateApiKeyRequirement(apiKey: string): void {\n if (this.requireApiKey && !apiKey) {\n throw new Error(`${this.providerName} provider requires an API key`);\n }\n }\n\n /**\n * Gets the effective API key to use for requests.\n * Returns 'not-needed' for providers that don't require keys.\n */\n protected getEffectiveApiKey(apiKey: string): string {\n return this.requireApiKey ? apiKey : apiKey || 'not-needed';\n }\n\n /**\n * Sends a message and returns the complete response.\n *\n * @param params - LLM parameters including messages, model, and settings\n * @param apiKey - API key for authentication\n * @returns Complete LLM response with content and usage statistics\n */\n async sendMessage(params: LLMParams, apiKey: string): Promise<LLMResponse> {\n this.logger.debug(`${this.providerName} sendMessage called`, {\n context: `${this.providerName}Provider.sendMessage`,\n model: params.model,\n baseUrl: this.baseUrl,\n });\n\n this.validateApiKeyRequirement(apiKey);\n const attachmentResults = this.collectAttachmentFailures(params);\n\n const client = new OpenAI({\n apiKey: this.getEffectiveApiKey(apiKey),\n baseURL: this.baseUrl,\n });\n\n // Strip attachments from messages and filter out 'tool' role\n const messages = params.messages\n .filter((m) => m.role !== 'tool')\n .map((m) => ({\n role: m.role as 'system' | 'user' | 'assistant',\n content: m.content,\n }));\n\n try {\n const response = await client.chat.completions.create({\n model: params.model,\n messages,\n temperature: params.temperature ?? 0.7,\n max_tokens: params.maxTokens ?? 4096,\n top_p: params.topP ?? 1,\n stop: params.stop,\n });\n\n const choice = response.choices[0];\n\n this.logger.debug(`Received ${this.providerName} response`, {\n context: `${this.providerName}Provider.sendMessage`,\n finishReason: choice.finish_reason,\n promptTokens: response.usage?.prompt_tokens,\n completionTokens: response.usage?.completion_tokens,\n });\n\n return {\n content: choice.message.content ?? '',\n finishReason: choice.finish_reason,\n usage: {\n promptTokens: response.usage?.prompt_tokens ?? 0,\n completionTokens: response.usage?.completion_tokens ?? 0,\n totalTokens: response.usage?.total_tokens ?? 0,\n },\n raw: response,\n attachmentResults,\n };\n } catch (error) {\n this.logger.error(\n `${this.providerName} API error in sendMessage`,\n { context: `${this.providerName}Provider.sendMessage`, baseUrl: this.baseUrl },\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n }\n\n /**\n * Sends a message and streams the response.\n *\n * @param params - LLM parameters including messages, model, and settings\n * @param apiKey - API key for authentication\n * @yields Stream chunks with content and final usage statistics\n */\n async *streamMessage(params: LLMParams, apiKey: string): AsyncGenerator<StreamChunk> {\n this.logger.debug(`${this.providerName} streamMessage called`, {\n context: `${this.providerName}Provider.streamMessage`,\n model: params.model,\n baseUrl: this.baseUrl,\n });\n\n this.validateApiKeyRequirement(apiKey);\n const attachmentResults = this.collectAttachmentFailures(params);\n\n const client = new OpenAI({\n apiKey: this.getEffectiveApiKey(apiKey),\n baseURL: this.baseUrl,\n });\n\n // Strip attachments from messages and filter out 'tool' role\n const messages = params.messages\n .filter((m) => m.role !== 'tool')\n .map((m) => ({\n role: m.role as 'system' | 'user' | 'assistant',\n content: m.content,\n }));\n\n try {\n const stream = await client.chat.completions.create({\n model: params.model,\n messages,\n temperature: params.temperature ?? 0.7,\n max_tokens: params.maxTokens ?? 4096,\n top_p: params.topP ?? 1,\n stream: true,\n stream_options: { include_usage: true },\n });\n\n let chunkCount = 0;\n\n for await (const chunk of stream) {\n chunkCount++;\n const content = chunk.choices[0]?.delta?.content;\n const finishReason = chunk.choices[0]?.finish_reason;\n const hasUsage = chunk.usage;\n\n // Yield content unless this is the final chunk with usage info\n if (content && !(finishReason && hasUsage)) {\n yield {\n content,\n done: false,\n };\n }\n\n // Final chunk with usage info\n if (finishReason && hasUsage) {\n this.logger.debug('Stream completed', {\n context: `${this.providerName}Provider.streamMessage`,\n finishReason,\n chunks: chunkCount,\n promptTokens: chunk.usage?.prompt_tokens,\n completionTokens: chunk.usage?.completion_tokens,\n });\n\n yield {\n content: '',\n done: true,\n usage: {\n promptTokens: chunk.usage?.prompt_tokens ?? 0,\n completionTokens: chunk.usage?.completion_tokens ?? 0,\n totalTokens: chunk.usage?.total_tokens ?? 0,\n },\n attachmentResults,\n };\n }\n }\n } catch (error) {\n this.logger.error(\n `${this.providerName} API error in streamMessage`,\n { context: `${this.providerName}Provider.streamMessage`, baseUrl: this.baseUrl },\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n }\n\n /**\n * Validates an API key by attempting to list models.\n *\n * @param apiKey - API key to validate\n * @returns true if the API key is valid, false otherwise\n */\n async validateApiKey(apiKey: string): Promise<boolean> {\n this.logger.debug(`Validating ${this.providerName} API connection`, {\n context: `${this.providerName}Provider.validateApiKey`,\n baseUrl: this.baseUrl,\n });\n\n // For providers that require API key, return false if not provided\n if (this.requireApiKey && !apiKey) {\n return false;\n }\n\n try {\n const client = new OpenAI({\n apiKey: this.getEffectiveApiKey(apiKey),\n baseURL: this.baseUrl,\n });\n await client.models.list();\n\n this.logger.debug(`${this.providerName} API validation successful`, {\n context: `${this.providerName}Provider.validateApiKey`,\n });\n return true;\n } catch (error) {\n this.logger.error(\n `${this.providerName} API validation failed`,\n { context: `${this.providerName}Provider.validateApiKey`, baseUrl: this.baseUrl },\n error instanceof Error ? error : undefined\n );\n return false;\n }\n }\n\n /**\n * Fetches available models from the API.\n *\n * @param apiKey - API key for authentication\n * @returns Sorted array of model IDs, or empty array on failure\n */\n async getAvailableModels(apiKey: string): Promise<string[]> {\n this.logger.debug(`Fetching ${this.providerName} models`, {\n context: `${this.providerName}Provider.getAvailableModels`,\n baseUrl: this.baseUrl,\n });\n\n // For providers that require API key, return empty if not provided\n if (this.requireApiKey && !apiKey) {\n this.logger.error(`${this.providerName} provider requires an API key to fetch models`, {\n context: `${this.providerName}Provider.getAvailableModels`,\n });\n return [];\n }\n\n try {\n const client = new OpenAI({\n apiKey: this.getEffectiveApiKey(apiKey),\n baseURL: this.baseUrl,\n });\n const models = await client.models.list();\n const modelList = models.data.map((m) => m.id).sort();\n\n this.logger.debug(`Retrieved ${this.providerName} models`, {\n context: `${this.providerName}Provider.getAvailableModels`,\n modelCount: modelList.length,\n });\n\n return modelList;\n } catch (error) {\n this.logger.error(\n `Failed to fetch ${this.providerName} models`,\n { context: `${this.providerName}Provider.getAvailableModels`, baseUrl: this.baseUrl },\n error instanceof Error ? error : undefined\n );\n return [];\n }\n }\n\n /**\n * Image generation is not supported by default.\n * @throws Error indicating image generation is not supported\n */\n async generateImage(_params: ImageGenParams, _apiKey: string): Promise<ImageGenResponse> {\n throw new Error(\n `${this.providerName} image generation support varies by implementation (not yet implemented)`\n );\n }\n}\n","/**\n * Plugin Logger Bridge\n *\n * Provides a logger factory for plugins that automatically bridges\n * to Quilltap's core logging when running inside the host application,\n * or falls back to console logging when running standalone.\n *\n * @module @quilltap/plugin-utils/logging/plugin-logger\n */\n\nimport type { PluginLogger, LogContext, LogLevel } from '@quilltap/plugin-types';\n\n/**\n * Extended logger interface with child logger support\n */\nexport interface PluginLoggerWithChild extends PluginLogger {\n /**\n * Create a child logger with additional context\n * @param additionalContext Context to merge with parent context\n * @returns A new logger with combined context\n */\n child(additionalContext: LogContext): PluginLoggerWithChild;\n}\n\n/**\n * Type for the global Quilltap logger bridge\n * Stored on globalThis to work across different npm package copies\n */\ndeclare global {\n \n var __quilltap_logger_factory:\n | ((pluginName: string) => PluginLoggerWithChild)\n | undefined;\n}\n\n/**\n * Get the core logger factory from global namespace\n *\n * @returns The injected factory or null if not in Quilltap environment\n */\nfunction getCoreLoggerFactory(): ((pluginName: string) => PluginLoggerWithChild) | null {\n return globalThis.__quilltap_logger_factory ?? null;\n}\n\n/**\n * Inject the core logger factory from Quilltap host\n *\n * This is called by Quilltap core when loading plugins to bridge\n * plugin logging into the host's logging system. Uses globalThis\n * to ensure it works even when plugins have their own copy of\n * plugin-utils in their node_modules.\n *\n * **Internal API - not for plugin use**\n *\n * @param factory A function that creates a child logger for a plugin\n */\nexport function __injectCoreLoggerFactory(\n factory: (pluginName: string) => PluginLoggerWithChild\n): void {\n globalThis.__quilltap_logger_factory = factory;\n}\n\n/**\n * Clear the injected core logger factory\n *\n * Useful for testing or when unloading the plugin system.\n *\n * **Internal API - not for plugin use**\n */\nexport function __clearCoreLoggerFactory(): void {\n globalThis.__quilltap_logger_factory = undefined;\n}\n\n/**\n * Check if a core logger has been injected\n *\n * @returns True if running inside Quilltap with core logging available\n */\nexport function hasCoreLogger(): boolean {\n return getCoreLoggerFactory() !== null;\n}\n\n/**\n * Create a console logger with child support\n *\n * @param prefix Logger prefix\n * @param minLevel Minimum log level\n * @param baseContext Base context to include in all logs\n */\nfunction createConsoleLoggerWithChild(\n prefix: string,\n minLevel: LogLevel = 'debug',\n baseContext: LogContext = {}\n): PluginLoggerWithChild {\n const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];\n const shouldLog = (level: LogLevel): boolean =>\n levels.indexOf(level) >= levels.indexOf(minLevel);\n\n const formatContext = (context?: LogContext): string => {\n const merged = { ...baseContext, ...context };\n const entries = Object.entries(merged)\n .filter(([key]) => key !== 'context')\n .map(([key, value]) => `${key}=${JSON.stringify(value)}`)\n .join(' ');\n return entries ? ` ${entries}` : '';\n };\n\n const logger: PluginLoggerWithChild = {\n debug: (message: string, context?: LogContext): void => {\n if (shouldLog('debug')) {\n console.debug(`[${prefix}] ${message}${formatContext(context)}`);\n }\n },\n\n info: (message: string, context?: LogContext): void => {\n if (shouldLog('info')) {\n console.info(`[${prefix}] ${message}${formatContext(context)}`);\n }\n },\n\n warn: (message: string, context?: LogContext): void => {\n if (shouldLog('warn')) {\n console.warn(`[${prefix}] ${message}${formatContext(context)}`);\n }\n },\n\n error: (message: string, context?: LogContext, error?: Error): void => {\n if (shouldLog('error')) {\n console.error(\n `[${prefix}] ${message}${formatContext(context)}`,\n error ? `\\n${error.stack || error.message}` : ''\n );\n }\n },\n\n child: (additionalContext: LogContext): PluginLoggerWithChild => {\n return createConsoleLoggerWithChild(prefix, minLevel, {\n ...baseContext,\n ...additionalContext,\n });\n },\n };\n\n return logger;\n}\n\n/**\n * Create a plugin logger that bridges to Quilltap core logging\n *\n * When running inside Quilltap:\n * - Routes all logs to the core logger\n * - Tags logs with `{ plugin: pluginName, module: 'plugin' }`\n * - Logs appear in Quilltap's combined.log and console\n *\n * When running standalone:\n * - Falls back to console logging with `[pluginName]` prefix\n * - Respects the specified minimum log level\n *\n * @param pluginName - The plugin identifier (e.g., 'qtap-plugin-openai')\n * @param minLevel - Minimum log level when running standalone (default: 'debug')\n * @returns A logger instance\n *\n * @example\n * ```typescript\n * // In your plugin's provider.ts\n * import { createPluginLogger } from '@quilltap/plugin-utils';\n *\n * const logger = createPluginLogger('qtap-plugin-my-provider');\n *\n * export class MyProvider implements LLMProvider {\n * async sendMessage(params: LLMParams, apiKey: string): Promise<LLMResponse> {\n * logger.debug('Sending message', { model: params.model });\n *\n * try {\n * const response = await this.client.chat({...});\n * logger.info('Received response', { tokens: response.usage?.total_tokens });\n * return response;\n * } catch (error) {\n * logger.error('Failed to send message', { model: params.model }, error as Error);\n * throw error;\n * }\n * }\n * }\n * ```\n */\nexport function createPluginLogger(\n pluginName: string,\n minLevel: LogLevel = 'debug'\n): PluginLoggerWithChild {\n // Check for core logger factory from global namespace\n const coreFactory = getCoreLoggerFactory();\n if (coreFactory) {\n return coreFactory(pluginName);\n }\n\n // Standalone mode: use enhanced console logger\n return createConsoleLoggerWithChild(pluginName, minLevel);\n}\n\n/**\n * Get the minimum log level from environment\n *\n * Checks for LOG_LEVEL or QUILTTAP_LOG_LEVEL environment variables.\n * Useful for configuring standalone plugin logging.\n *\n * @returns The configured log level, or 'info' as default\n */\nexport function getLogLevelFromEnv(): LogLevel {\n if (typeof process !== 'undefined' && process.env) {\n const envLevel = process.env.LOG_LEVEL || process.env.QUILTTAP_LOG_LEVEL;\n if (envLevel && ['debug', 'info', 'warn', 'error'].includes(envLevel)) {\n return envLevel as LogLevel;\n }\n }\n return 'info';\n}\n","/**\n * Logging Utilities\n *\n * Exports the plugin logger bridge and related utilities.\n *\n * @module @quilltap/plugin-utils/logging\n */\n\nexport {\n createPluginLogger,\n hasCoreLogger,\n getLogLevelFromEnv,\n __injectCoreLoggerFactory,\n __clearCoreLoggerFactory,\n} from './plugin-logger';\n\nexport type { PluginLoggerWithChild } from './plugin-logger';\n\n// Re-export logger types from plugin-types\nexport type { PluginLogger, LogContext, LogLevel } from '@quilltap/plugin-types';\n\n// Re-export logger utilities from plugin-types for convenience\nexport { createConsoleLogger, createNoopLogger } from '@quilltap/plugin-types';\n"],"mappings":"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","names":["OpenAI"]}