@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.
- package/CHANGELOG.md +33 -0
- package/README.md +95 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +431 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +415 -2
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +166 -0
- package/dist/providers/index.d.ts +166 -0
- package/dist/providers/index.js +385 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/index.mjs +348 -0
- package/dist/providers/index.mjs.map +1 -0
- package/dist/roleplay-templates/index.d.mts +143 -0
- package/dist/roleplay-templates/index.d.ts +143 -0
- package/dist/roleplay-templates/index.js +215 -0
- package/dist/roleplay-templates/index.js.map +1 -0
- package/dist/roleplay-templates/index.mjs +185 -0
- package/dist/roleplay-templates/index.mjs.map +1 -0
- package/package.json +17 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to @quilltap/plugin-utils will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.2.0] - 2025-12-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Roleplay Template Plugin Utilities**
|
|
10
|
+
- `createRoleplayTemplatePlugin()` - Create roleplay template plugins with full control
|
|
11
|
+
- `createSingleTemplatePlugin()` - Simplified helper for single-template plugins
|
|
12
|
+
- `validateTemplateConfig()` - Validate individual template configurations
|
|
13
|
+
- `validateRoleplayTemplatePlugin()` - Validate complete roleplay template plugins
|
|
14
|
+
- `CreateRoleplayTemplatePluginOptions` - Options interface for full control
|
|
15
|
+
- `CreateSingleTemplatePluginOptions` - Simplified options for single templates
|
|
16
|
+
|
|
17
|
+
- **New Export Path**
|
|
18
|
+
- `@quilltap/plugin-utils/roleplay-templates` - Direct import path for roleplay template utilities
|
|
19
|
+
|
|
20
|
+
## [1.1.0] - 2025-12-30
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **OpenAI-Compatible Provider Base Class**
|
|
25
|
+
- `OpenAICompatibleProvider` - Reusable base class for OpenAI-compatible LLM providers
|
|
26
|
+
- `OpenAICompatibleProviderConfig` - Configuration interface for customizing providers
|
|
27
|
+
- Supports streaming and non-streaming chat completions
|
|
28
|
+
- Configurable API key requirements (`requireApiKey` option)
|
|
29
|
+
- Customizable provider name for logging (`providerName` option)
|
|
30
|
+
- Customizable attachment error messages (`attachmentErrorMessage` option)
|
|
31
|
+
- Includes API key validation and model listing
|
|
32
|
+
- Uses the plugin logger bridge for consistent logging
|
|
33
|
+
- Peer dependency on `openai` package (optional, only needed if using this provider)
|
|
34
|
+
|
|
35
|
+
- **New Export Path**
|
|
36
|
+
- `@quilltap/plugin-utils/providers` - Direct import path for provider base classes
|
|
37
|
+
|
|
5
38
|
## [1.0.0] - 2025-12-30
|
|
6
39
|
|
|
7
40
|
### Added
|
package/README.md
CHANGED
|
@@ -110,6 +110,101 @@ When running standalone:
|
|
|
110
110
|
| `createConsoleLogger(prefix, minLevel?)` | Create a standalone console logger |
|
|
111
111
|
| `createNoopLogger()` | Create a no-op logger |
|
|
112
112
|
|
|
113
|
+
### OpenAI-Compatible Provider Base Class
|
|
114
|
+
|
|
115
|
+
Create custom LLM providers for OpenAI-compatible APIs with minimal code:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { OpenAICompatibleProvider } from '@quilltap/plugin-utils';
|
|
119
|
+
|
|
120
|
+
// Create a provider for any OpenAI-compatible API
|
|
121
|
+
export class MyLLMProvider extends OpenAICompatibleProvider {
|
|
122
|
+
constructor() {
|
|
123
|
+
super({
|
|
124
|
+
baseUrl: 'https://api.my-llm-service.com/v1',
|
|
125
|
+
providerName: 'MyLLM',
|
|
126
|
+
requireApiKey: true,
|
|
127
|
+
attachmentErrorMessage: 'MyLLM does not support file attachments',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This gives you a complete `LLMProvider` implementation with:
|
|
134
|
+
- Streaming and non-streaming chat completions
|
|
135
|
+
- API key validation
|
|
136
|
+
- Model listing
|
|
137
|
+
- Proper error handling and logging
|
|
138
|
+
|
|
139
|
+
**Configuration Options:**
|
|
140
|
+
|
|
141
|
+
| Option | Type | Default | Description |
|
|
142
|
+
|--------|------|---------|-------------|
|
|
143
|
+
| `baseUrl` | `string` | (required) | API endpoint URL with version path |
|
|
144
|
+
| `providerName` | `string` | `'OpenAICompatible'` | Name used in log messages |
|
|
145
|
+
| `requireApiKey` | `boolean` | `false` | Whether API key is mandatory |
|
|
146
|
+
| `attachmentErrorMessage` | `string` | (default message) | Error shown for attachment failures |
|
|
147
|
+
|
|
148
|
+
**Note:** Requires `openai` as a peer dependency:
|
|
149
|
+
```bash
|
|
150
|
+
npm install openai
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Roleplay Template Plugin Utilities
|
|
154
|
+
|
|
155
|
+
Create roleplay template plugins with built-in validation and logging:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { createSingleTemplatePlugin } from '@quilltap/plugin-utils';
|
|
159
|
+
|
|
160
|
+
// Simple single-template plugin
|
|
161
|
+
export const plugin = createSingleTemplatePlugin({
|
|
162
|
+
templateId: 'my-rp-format',
|
|
163
|
+
displayName: 'My RP Format',
|
|
164
|
+
description: 'A custom roleplay formatting style',
|
|
165
|
+
systemPrompt: `[FORMATTING INSTRUCTIONS]
|
|
166
|
+
1. DIALOGUE: Use quotation marks
|
|
167
|
+
2. ACTIONS: Use asterisks *like this*
|
|
168
|
+
3. THOUGHTS: Use angle brackets <like this>`,
|
|
169
|
+
tags: ['custom', 'roleplay'],
|
|
170
|
+
enableLogging: true,
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For plugins providing multiple templates:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { createRoleplayTemplatePlugin } from '@quilltap/plugin-utils';
|
|
178
|
+
|
|
179
|
+
export const plugin = createRoleplayTemplatePlugin({
|
|
180
|
+
metadata: {
|
|
181
|
+
templateId: 'rp-format-pack',
|
|
182
|
+
displayName: 'RP Format Pack',
|
|
183
|
+
description: 'A collection of roleplay formats',
|
|
184
|
+
},
|
|
185
|
+
templates: [
|
|
186
|
+
{
|
|
187
|
+
name: 'Screenplay',
|
|
188
|
+
description: 'Screenplay-style formatting',
|
|
189
|
+
systemPrompt: '...',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'Novel',
|
|
193
|
+
description: 'Novel-style prose',
|
|
194
|
+
systemPrompt: '...',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
enableLogging: true,
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
| Function | Description |
|
|
202
|
+
|----------|-------------|
|
|
203
|
+
| `createRoleplayTemplatePlugin(options)` | Create a plugin with full control over metadata and templates |
|
|
204
|
+
| `createSingleTemplatePlugin(options)` | Simplified helper for plugins with a single template |
|
|
205
|
+
| `validateTemplateConfig(template)` | Validate an individual template configuration |
|
|
206
|
+
| `validateRoleplayTemplatePlugin(plugin)` | Validate a complete roleplay template plugin |
|
|
207
|
+
|
|
113
208
|
## Example: Complete Plugin Provider
|
|
114
209
|
|
|
115
210
|
```typescript
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { AnthropicToolDefinition, GoogleToolDefinition, LogContext, LogLevel, OpenAIToolDefinition, PluginLogger, ToolCall, ToolCallRequest, ToolFormatOptions, ToolResult, UniversalTool, createConsoleLogger, createNoopLogger } from '@quilltap/plugin-types';
|
|
2
2
|
export { ToolCallFormat, ToolConvertTarget, applyDescriptionLimit, convertFromAnthropicFormat, convertFromGoogleFormat, convertToAnthropicFormat, convertToGoogleFormat, convertToolTo, convertToolsTo, detectToolCallFormat, hasToolCalls, parseAnthropicToolCalls, parseGoogleToolCalls, parseOpenAIToolCalls, parseToolCalls } from './tools/index.mjs';
|
|
3
3
|
export { PluginLoggerWithChild, __clearCoreLoggerFactory, __injectCoreLoggerFactory, createPluginLogger, getLogLevelFromEnv, hasCoreLogger } from './logging/index.mjs';
|
|
4
|
+
export { OpenAICompatibleProvider, OpenAICompatibleProviderConfig } from './providers/index.mjs';
|
|
5
|
+
export { CreateRoleplayTemplatePluginOptions, CreateSingleTemplatePluginOptions, createRoleplayTemplatePlugin, createSingleTemplatePlugin, validateRoleplayTemplatePlugin, validateTemplateConfig } from './roleplay-templates/index.mjs';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @quilltap/plugin-utils
|
|
@@ -22,6 +24,6 @@ export { PluginLoggerWithChild, __clearCoreLoggerFactory, __injectCoreLoggerFact
|
|
|
22
24
|
* Version of the plugin-utils package.
|
|
23
25
|
* Can be used at runtime to check compatibility.
|
|
24
26
|
*/
|
|
25
|
-
declare const PLUGIN_UTILS_VERSION = "1.
|
|
27
|
+
declare const PLUGIN_UTILS_VERSION = "1.2.0";
|
|
26
28
|
|
|
27
29
|
export { PLUGIN_UTILS_VERSION };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { AnthropicToolDefinition, GoogleToolDefinition, LogContext, LogLevel, OpenAIToolDefinition, PluginLogger, ToolCall, ToolCallRequest, ToolFormatOptions, ToolResult, UniversalTool, createConsoleLogger, createNoopLogger } from '@quilltap/plugin-types';
|
|
2
2
|
export { ToolCallFormat, ToolConvertTarget, applyDescriptionLimit, convertFromAnthropicFormat, convertFromGoogleFormat, convertToAnthropicFormat, convertToGoogleFormat, convertToolTo, convertToolsTo, detectToolCallFormat, hasToolCalls, parseAnthropicToolCalls, parseGoogleToolCalls, parseOpenAIToolCalls, parseToolCalls } from './tools/index.js';
|
|
3
3
|
export { PluginLoggerWithChild, __clearCoreLoggerFactory, __injectCoreLoggerFactory, createPluginLogger, getLogLevelFromEnv, hasCoreLogger } from './logging/index.js';
|
|
4
|
+
export { OpenAICompatibleProvider, OpenAICompatibleProviderConfig } from './providers/index.js';
|
|
5
|
+
export { CreateRoleplayTemplatePluginOptions, CreateSingleTemplatePluginOptions, createRoleplayTemplatePlugin, createSingleTemplatePlugin, validateRoleplayTemplatePlugin, validateTemplateConfig } from './roleplay-templates/index.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @quilltap/plugin-utils
|
|
@@ -22,6 +24,6 @@ export { PluginLoggerWithChild, __clearCoreLoggerFactory, __injectCoreLoggerFact
|
|
|
22
24
|
* Version of the plugin-utils package.
|
|
23
25
|
* Can be used at runtime to check compatibility.
|
|
24
26
|
*/
|
|
25
|
-
declare const PLUGIN_UTILS_VERSION = "1.
|
|
27
|
+
declare const PLUGIN_UTILS_VERSION = "1.2.0";
|
|
26
28
|
|
|
27
29
|
export { PLUGIN_UTILS_VERSION };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,11 +17,20 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
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
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var src_exports = {};
|
|
22
32
|
__export(src_exports, {
|
|
33
|
+
OpenAICompatibleProvider: () => OpenAICompatibleProvider,
|
|
23
34
|
PLUGIN_UTILS_VERSION: () => PLUGIN_UTILS_VERSION,
|
|
24
35
|
__clearCoreLoggerFactory: () => __clearCoreLoggerFactory,
|
|
25
36
|
__injectCoreLoggerFactory: () => __injectCoreLoggerFactory,
|
|
@@ -33,6 +44,8 @@ __export(src_exports, {
|
|
|
33
44
|
createConsoleLogger: () => import_plugin_types.createConsoleLogger,
|
|
34
45
|
createNoopLogger: () => import_plugin_types.createNoopLogger,
|
|
35
46
|
createPluginLogger: () => createPluginLogger,
|
|
47
|
+
createRoleplayTemplatePlugin: () => createRoleplayTemplatePlugin,
|
|
48
|
+
createSingleTemplatePlugin: () => createSingleTemplatePlugin,
|
|
36
49
|
detectToolCallFormat: () => detectToolCallFormat,
|
|
37
50
|
getLogLevelFromEnv: () => getLogLevelFromEnv,
|
|
38
51
|
hasCoreLogger: () => hasCoreLogger,
|
|
@@ -40,7 +53,9 @@ __export(src_exports, {
|
|
|
40
53
|
parseAnthropicToolCalls: () => parseAnthropicToolCalls,
|
|
41
54
|
parseGoogleToolCalls: () => parseGoogleToolCalls,
|
|
42
55
|
parseOpenAIToolCalls: () => parseOpenAIToolCalls,
|
|
43
|
-
parseToolCalls: () => parseToolCalls
|
|
56
|
+
parseToolCalls: () => parseToolCalls,
|
|
57
|
+
validateRoleplayTemplatePlugin: () => validateRoleplayTemplatePlugin,
|
|
58
|
+
validateTemplateConfig: () => validateTemplateConfig
|
|
44
59
|
});
|
|
45
60
|
module.exports = __toCommonJS(src_exports);
|
|
46
61
|
|
|
@@ -331,10 +346,419 @@ function getLogLevelFromEnv() {
|
|
|
331
346
|
// src/logging/index.ts
|
|
332
347
|
var import_plugin_types = require("@quilltap/plugin-types");
|
|
333
348
|
|
|
349
|
+
// src/providers/openai-compatible.ts
|
|
350
|
+
var import_openai = __toESM(require("openai"));
|
|
351
|
+
var OpenAICompatibleProvider = class {
|
|
352
|
+
/**
|
|
353
|
+
* Creates a new OpenAI-compatible provider instance.
|
|
354
|
+
*
|
|
355
|
+
* @param config - Configuration object or base URL string (for backward compatibility)
|
|
356
|
+
*/
|
|
357
|
+
constructor(config) {
|
|
358
|
+
/** File attachments are not supported by default */
|
|
359
|
+
this.supportsFileAttachments = false;
|
|
360
|
+
/** No MIME types are supported for attachments */
|
|
361
|
+
this.supportedMimeTypes = [];
|
|
362
|
+
/** Image generation is not supported by default */
|
|
363
|
+
this.supportsImageGeneration = false;
|
|
364
|
+
/** Web search is not supported */
|
|
365
|
+
this.supportsWebSearch = false;
|
|
366
|
+
if (typeof config === "string") {
|
|
367
|
+
this.baseUrl = config;
|
|
368
|
+
this.providerName = "OpenAICompatible";
|
|
369
|
+
this.requireApiKey = false;
|
|
370
|
+
this.attachmentErrorMessage = "OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)";
|
|
371
|
+
} else {
|
|
372
|
+
this.baseUrl = config.baseUrl;
|
|
373
|
+
this.providerName = config.providerName ?? "OpenAICompatible";
|
|
374
|
+
this.requireApiKey = config.requireApiKey ?? false;
|
|
375
|
+
this.attachmentErrorMessage = config.attachmentErrorMessage ?? "OpenAI-compatible provider file attachment support varies by implementation (not yet implemented)";
|
|
376
|
+
}
|
|
377
|
+
this.logger = createPluginLogger(`${this.providerName}Provider`);
|
|
378
|
+
this.logger.debug(`${this.providerName} provider instantiated`, {
|
|
379
|
+
context: `${this.providerName}Provider.constructor`,
|
|
380
|
+
baseUrl: this.baseUrl
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Collects attachment failures for messages with attachments.
|
|
385
|
+
* Since attachments are not supported, all attachments are marked as failed.
|
|
386
|
+
*
|
|
387
|
+
* @param params - LLM parameters containing messages
|
|
388
|
+
* @returns Object with empty sent array and failed attachments
|
|
389
|
+
*/
|
|
390
|
+
collectAttachmentFailures(params) {
|
|
391
|
+
const failed = [];
|
|
392
|
+
for (const msg of params.messages) {
|
|
393
|
+
if (msg.attachments) {
|
|
394
|
+
for (const attachment of msg.attachments) {
|
|
395
|
+
failed.push({
|
|
396
|
+
id: attachment.id,
|
|
397
|
+
error: this.attachmentErrorMessage
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return { sent: [], failed };
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Validates that an API key is provided when required.
|
|
406
|
+
* @throws Error if API key is required but not provided
|
|
407
|
+
*/
|
|
408
|
+
validateApiKeyRequirement(apiKey) {
|
|
409
|
+
if (this.requireApiKey && !apiKey) {
|
|
410
|
+
throw new Error(`${this.providerName} provider requires an API key`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Gets the effective API key to use for requests.
|
|
415
|
+
* Returns 'not-needed' for providers that don't require keys.
|
|
416
|
+
*/
|
|
417
|
+
getEffectiveApiKey(apiKey) {
|
|
418
|
+
return this.requireApiKey ? apiKey : apiKey || "not-needed";
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Sends a message and returns the complete response.
|
|
422
|
+
*
|
|
423
|
+
* @param params - LLM parameters including messages, model, and settings
|
|
424
|
+
* @param apiKey - API key for authentication
|
|
425
|
+
* @returns Complete LLM response with content and usage statistics
|
|
426
|
+
*/
|
|
427
|
+
async sendMessage(params, apiKey) {
|
|
428
|
+
this.logger.debug(`${this.providerName} sendMessage called`, {
|
|
429
|
+
context: `${this.providerName}Provider.sendMessage`,
|
|
430
|
+
model: params.model,
|
|
431
|
+
baseUrl: this.baseUrl
|
|
432
|
+
});
|
|
433
|
+
this.validateApiKeyRequirement(apiKey);
|
|
434
|
+
const attachmentResults = this.collectAttachmentFailures(params);
|
|
435
|
+
const client = new import_openai.default({
|
|
436
|
+
apiKey: this.getEffectiveApiKey(apiKey),
|
|
437
|
+
baseURL: this.baseUrl
|
|
438
|
+
});
|
|
439
|
+
const messages = params.messages.filter((m) => m.role !== "tool").map((m) => ({
|
|
440
|
+
role: m.role,
|
|
441
|
+
content: m.content
|
|
442
|
+
}));
|
|
443
|
+
try {
|
|
444
|
+
const response = await client.chat.completions.create({
|
|
445
|
+
model: params.model,
|
|
446
|
+
messages,
|
|
447
|
+
temperature: params.temperature ?? 0.7,
|
|
448
|
+
max_tokens: params.maxTokens ?? 4096,
|
|
449
|
+
top_p: params.topP ?? 1,
|
|
450
|
+
stop: params.stop
|
|
451
|
+
});
|
|
452
|
+
const choice = response.choices[0];
|
|
453
|
+
this.logger.debug(`Received ${this.providerName} response`, {
|
|
454
|
+
context: `${this.providerName}Provider.sendMessage`,
|
|
455
|
+
finishReason: choice.finish_reason,
|
|
456
|
+
promptTokens: response.usage?.prompt_tokens,
|
|
457
|
+
completionTokens: response.usage?.completion_tokens
|
|
458
|
+
});
|
|
459
|
+
return {
|
|
460
|
+
content: choice.message.content ?? "",
|
|
461
|
+
finishReason: choice.finish_reason,
|
|
462
|
+
usage: {
|
|
463
|
+
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
464
|
+
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
465
|
+
totalTokens: response.usage?.total_tokens ?? 0
|
|
466
|
+
},
|
|
467
|
+
raw: response,
|
|
468
|
+
attachmentResults
|
|
469
|
+
};
|
|
470
|
+
} catch (error) {
|
|
471
|
+
this.logger.error(
|
|
472
|
+
`${this.providerName} API error in sendMessage`,
|
|
473
|
+
{ context: `${this.providerName}Provider.sendMessage`, baseUrl: this.baseUrl },
|
|
474
|
+
error instanceof Error ? error : void 0
|
|
475
|
+
);
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Sends a message and streams the response.
|
|
481
|
+
*
|
|
482
|
+
* @param params - LLM parameters including messages, model, and settings
|
|
483
|
+
* @param apiKey - API key for authentication
|
|
484
|
+
* @yields Stream chunks with content and final usage statistics
|
|
485
|
+
*/
|
|
486
|
+
async *streamMessage(params, apiKey) {
|
|
487
|
+
this.logger.debug(`${this.providerName} streamMessage called`, {
|
|
488
|
+
context: `${this.providerName}Provider.streamMessage`,
|
|
489
|
+
model: params.model,
|
|
490
|
+
baseUrl: this.baseUrl
|
|
491
|
+
});
|
|
492
|
+
this.validateApiKeyRequirement(apiKey);
|
|
493
|
+
const attachmentResults = this.collectAttachmentFailures(params);
|
|
494
|
+
const client = new import_openai.default({
|
|
495
|
+
apiKey: this.getEffectiveApiKey(apiKey),
|
|
496
|
+
baseURL: this.baseUrl
|
|
497
|
+
});
|
|
498
|
+
const messages = params.messages.filter((m) => m.role !== "tool").map((m) => ({
|
|
499
|
+
role: m.role,
|
|
500
|
+
content: m.content
|
|
501
|
+
}));
|
|
502
|
+
try {
|
|
503
|
+
const stream = await client.chat.completions.create({
|
|
504
|
+
model: params.model,
|
|
505
|
+
messages,
|
|
506
|
+
temperature: params.temperature ?? 0.7,
|
|
507
|
+
max_tokens: params.maxTokens ?? 4096,
|
|
508
|
+
top_p: params.topP ?? 1,
|
|
509
|
+
stream: true,
|
|
510
|
+
stream_options: { include_usage: true }
|
|
511
|
+
});
|
|
512
|
+
let chunkCount = 0;
|
|
513
|
+
for await (const chunk of stream) {
|
|
514
|
+
chunkCount++;
|
|
515
|
+
const content = chunk.choices[0]?.delta?.content;
|
|
516
|
+
const finishReason = chunk.choices[0]?.finish_reason;
|
|
517
|
+
const hasUsage = chunk.usage;
|
|
518
|
+
if (content && !(finishReason && hasUsage)) {
|
|
519
|
+
yield {
|
|
520
|
+
content,
|
|
521
|
+
done: false
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (finishReason && hasUsage) {
|
|
525
|
+
this.logger.debug("Stream completed", {
|
|
526
|
+
context: `${this.providerName}Provider.streamMessage`,
|
|
527
|
+
finishReason,
|
|
528
|
+
chunks: chunkCount,
|
|
529
|
+
promptTokens: chunk.usage?.prompt_tokens,
|
|
530
|
+
completionTokens: chunk.usage?.completion_tokens
|
|
531
|
+
});
|
|
532
|
+
yield {
|
|
533
|
+
content: "",
|
|
534
|
+
done: true,
|
|
535
|
+
usage: {
|
|
536
|
+
promptTokens: chunk.usage?.prompt_tokens ?? 0,
|
|
537
|
+
completionTokens: chunk.usage?.completion_tokens ?? 0,
|
|
538
|
+
totalTokens: chunk.usage?.total_tokens ?? 0
|
|
539
|
+
},
|
|
540
|
+
attachmentResults
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
this.logger.error(
|
|
546
|
+
`${this.providerName} API error in streamMessage`,
|
|
547
|
+
{ context: `${this.providerName}Provider.streamMessage`, baseUrl: this.baseUrl },
|
|
548
|
+
error instanceof Error ? error : void 0
|
|
549
|
+
);
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Validates an API key by attempting to list models.
|
|
555
|
+
*
|
|
556
|
+
* @param apiKey - API key to validate
|
|
557
|
+
* @returns true if the API key is valid, false otherwise
|
|
558
|
+
*/
|
|
559
|
+
async validateApiKey(apiKey) {
|
|
560
|
+
this.logger.debug(`Validating ${this.providerName} API connection`, {
|
|
561
|
+
context: `${this.providerName}Provider.validateApiKey`,
|
|
562
|
+
baseUrl: this.baseUrl
|
|
563
|
+
});
|
|
564
|
+
if (this.requireApiKey && !apiKey) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
const client = new import_openai.default({
|
|
569
|
+
apiKey: this.getEffectiveApiKey(apiKey),
|
|
570
|
+
baseURL: this.baseUrl
|
|
571
|
+
});
|
|
572
|
+
await client.models.list();
|
|
573
|
+
this.logger.debug(`${this.providerName} API validation successful`, {
|
|
574
|
+
context: `${this.providerName}Provider.validateApiKey`
|
|
575
|
+
});
|
|
576
|
+
return true;
|
|
577
|
+
} catch (error) {
|
|
578
|
+
this.logger.error(
|
|
579
|
+
`${this.providerName} API validation failed`,
|
|
580
|
+
{ context: `${this.providerName}Provider.validateApiKey`, baseUrl: this.baseUrl },
|
|
581
|
+
error instanceof Error ? error : void 0
|
|
582
|
+
);
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Fetches available models from the API.
|
|
588
|
+
*
|
|
589
|
+
* @param apiKey - API key for authentication
|
|
590
|
+
* @returns Sorted array of model IDs, or empty array on failure
|
|
591
|
+
*/
|
|
592
|
+
async getAvailableModels(apiKey) {
|
|
593
|
+
this.logger.debug(`Fetching ${this.providerName} models`, {
|
|
594
|
+
context: `${this.providerName}Provider.getAvailableModels`,
|
|
595
|
+
baseUrl: this.baseUrl
|
|
596
|
+
});
|
|
597
|
+
if (this.requireApiKey && !apiKey) {
|
|
598
|
+
this.logger.error(`${this.providerName} provider requires an API key to fetch models`, {
|
|
599
|
+
context: `${this.providerName}Provider.getAvailableModels`
|
|
600
|
+
});
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const client = new import_openai.default({
|
|
605
|
+
apiKey: this.getEffectiveApiKey(apiKey),
|
|
606
|
+
baseURL: this.baseUrl
|
|
607
|
+
});
|
|
608
|
+
const models = await client.models.list();
|
|
609
|
+
const modelList = models.data.map((m) => m.id).sort();
|
|
610
|
+
this.logger.debug(`Retrieved ${this.providerName} models`, {
|
|
611
|
+
context: `${this.providerName}Provider.getAvailableModels`,
|
|
612
|
+
modelCount: modelList.length
|
|
613
|
+
});
|
|
614
|
+
return modelList;
|
|
615
|
+
} catch (error) {
|
|
616
|
+
this.logger.error(
|
|
617
|
+
`Failed to fetch ${this.providerName} models`,
|
|
618
|
+
{ context: `${this.providerName}Provider.getAvailableModels`, baseUrl: this.baseUrl },
|
|
619
|
+
error instanceof Error ? error : void 0
|
|
620
|
+
);
|
|
621
|
+
return [];
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Image generation is not supported by default.
|
|
626
|
+
* @throws Error indicating image generation is not supported
|
|
627
|
+
*/
|
|
628
|
+
async generateImage(_params, _apiKey) {
|
|
629
|
+
throw new Error(
|
|
630
|
+
`${this.providerName} image generation support varies by implementation (not yet implemented)`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// src/roleplay-templates/builder.ts
|
|
636
|
+
function createRoleplayTemplatePlugin(options) {
|
|
637
|
+
const { metadata, templates, initialize, enableLogging = false } = options;
|
|
638
|
+
const templateArray = Array.isArray(templates) ? templates : [templates];
|
|
639
|
+
if (templateArray.length === 0) {
|
|
640
|
+
throw new Error("At least one template is required");
|
|
641
|
+
}
|
|
642
|
+
for (const template of templateArray) {
|
|
643
|
+
if (!template.name || template.name.trim() === "") {
|
|
644
|
+
throw new Error("Template name is required");
|
|
645
|
+
}
|
|
646
|
+
if (!template.systemPrompt || template.systemPrompt.trim() === "") {
|
|
647
|
+
throw new Error(`Template "${template.name}" requires a systemPrompt`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const plugin = {
|
|
651
|
+
metadata: {
|
|
652
|
+
...metadata,
|
|
653
|
+
// Ensure tags from templates are included in metadata if not already set
|
|
654
|
+
tags: metadata.tags ?? Array.from(
|
|
655
|
+
new Set(templateArray.flatMap((t) => t.tags ?? []))
|
|
656
|
+
)
|
|
657
|
+
},
|
|
658
|
+
templates: templateArray
|
|
659
|
+
};
|
|
660
|
+
if (initialize || enableLogging) {
|
|
661
|
+
plugin.initialize = async () => {
|
|
662
|
+
if (enableLogging) {
|
|
663
|
+
const logger = createPluginLogger(metadata.templateId);
|
|
664
|
+
logger.debug("Roleplay template plugin loaded", {
|
|
665
|
+
context: "init",
|
|
666
|
+
templateId: metadata.templateId,
|
|
667
|
+
displayName: metadata.displayName,
|
|
668
|
+
templateCount: templateArray.length,
|
|
669
|
+
templateNames: templateArray.map((t) => t.name)
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
if (initialize) {
|
|
673
|
+
await initialize();
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return plugin;
|
|
678
|
+
}
|
|
679
|
+
function createSingleTemplatePlugin(options) {
|
|
680
|
+
const {
|
|
681
|
+
templateId,
|
|
682
|
+
displayName,
|
|
683
|
+
description,
|
|
684
|
+
systemPrompt,
|
|
685
|
+
author,
|
|
686
|
+
tags,
|
|
687
|
+
version,
|
|
688
|
+
initialize,
|
|
689
|
+
enableLogging
|
|
690
|
+
} = options;
|
|
691
|
+
return createRoleplayTemplatePlugin({
|
|
692
|
+
metadata: {
|
|
693
|
+
templateId,
|
|
694
|
+
displayName,
|
|
695
|
+
description,
|
|
696
|
+
author,
|
|
697
|
+
tags,
|
|
698
|
+
version
|
|
699
|
+
},
|
|
700
|
+
templates: {
|
|
701
|
+
name: displayName,
|
|
702
|
+
description,
|
|
703
|
+
systemPrompt,
|
|
704
|
+
tags
|
|
705
|
+
},
|
|
706
|
+
initialize,
|
|
707
|
+
enableLogging
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
function validateTemplateConfig(template) {
|
|
711
|
+
if (!template.name || template.name.trim() === "") {
|
|
712
|
+
throw new Error("Template name is required");
|
|
713
|
+
}
|
|
714
|
+
if (template.name.length > 100) {
|
|
715
|
+
throw new Error("Template name must be 100 characters or less");
|
|
716
|
+
}
|
|
717
|
+
if (!template.systemPrompt || template.systemPrompt.trim() === "") {
|
|
718
|
+
throw new Error("Template systemPrompt is required");
|
|
719
|
+
}
|
|
720
|
+
if (template.description && template.description.length > 500) {
|
|
721
|
+
throw new Error("Template description must be 500 characters or less");
|
|
722
|
+
}
|
|
723
|
+
if (template.tags) {
|
|
724
|
+
if (!Array.isArray(template.tags)) {
|
|
725
|
+
throw new Error("Template tags must be an array");
|
|
726
|
+
}
|
|
727
|
+
for (const tag of template.tags) {
|
|
728
|
+
if (typeof tag !== "string") {
|
|
729
|
+
throw new Error("All tags must be strings");
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
function validateRoleplayTemplatePlugin(plugin) {
|
|
736
|
+
if (!plugin.metadata) {
|
|
737
|
+
throw new Error("Plugin metadata is required");
|
|
738
|
+
}
|
|
739
|
+
if (!plugin.metadata.templateId || plugin.metadata.templateId.trim() === "") {
|
|
740
|
+
throw new Error("Plugin metadata.templateId is required");
|
|
741
|
+
}
|
|
742
|
+
if (!/^[a-z0-9-]+$/.test(plugin.metadata.templateId)) {
|
|
743
|
+
throw new Error("Plugin templateId must be lowercase alphanumeric with hyphens only");
|
|
744
|
+
}
|
|
745
|
+
if (!plugin.metadata.displayName || plugin.metadata.displayName.trim() === "") {
|
|
746
|
+
throw new Error("Plugin metadata.displayName is required");
|
|
747
|
+
}
|
|
748
|
+
if (!plugin.templates || !Array.isArray(plugin.templates) || plugin.templates.length === 0) {
|
|
749
|
+
throw new Error("Plugin must have at least one template");
|
|
750
|
+
}
|
|
751
|
+
for (const template of plugin.templates) {
|
|
752
|
+
validateTemplateConfig(template);
|
|
753
|
+
}
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
|
|
334
757
|
// src/index.ts
|
|
335
|
-
var PLUGIN_UTILS_VERSION = "1.
|
|
758
|
+
var PLUGIN_UTILS_VERSION = "1.2.0";
|
|
336
759
|
// Annotate the CommonJS export names for ESM import in node:
|
|
337
760
|
0 && (module.exports = {
|
|
761
|
+
OpenAICompatibleProvider,
|
|
338
762
|
PLUGIN_UTILS_VERSION,
|
|
339
763
|
__clearCoreLoggerFactory,
|
|
340
764
|
__injectCoreLoggerFactory,
|
|
@@ -348,6 +772,8 @@ var PLUGIN_UTILS_VERSION = "1.0.0";
|
|
|
348
772
|
createConsoleLogger,
|
|
349
773
|
createNoopLogger,
|
|
350
774
|
createPluginLogger,
|
|
775
|
+
createRoleplayTemplatePlugin,
|
|
776
|
+
createSingleTemplatePlugin,
|
|
351
777
|
detectToolCallFormat,
|
|
352
778
|
getLogLevelFromEnv,
|
|
353
779
|
hasCoreLogger,
|
|
@@ -355,6 +781,8 @@ var PLUGIN_UTILS_VERSION = "1.0.0";
|
|
|
355
781
|
parseAnthropicToolCalls,
|
|
356
782
|
parseGoogleToolCalls,
|
|
357
783
|
parseOpenAIToolCalls,
|
|
358
|
-
parseToolCalls
|
|
784
|
+
parseToolCalls,
|
|
785
|
+
validateRoleplayTemplatePlugin,
|
|
786
|
+
validateTemplateConfig
|
|
359
787
|
});
|
|
360
788
|
//# sourceMappingURL=index.js.map
|