@quilltap/plugin-utils 1.1.0 → 1.2.1

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 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
@@ -2,6 +2,7 @@ export { AnthropicToolDefinition, GoogleToolDefinition, LogContext, LogLevel, Op
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
4
  export { OpenAICompatibleProvider, OpenAICompatibleProviderConfig } from './providers/index.mjs';
5
+ export { CreateRoleplayTemplatePluginOptions, CreateSingleTemplatePluginOptions, createRoleplayTemplatePlugin, createSingleTemplatePlugin, validateRoleplayTemplatePlugin, validateTemplateConfig } from './roleplay-templates/index.mjs';
5
6
 
6
7
  /**
7
8
  * @quilltap/plugin-utils
@@ -23,6 +24,6 @@ export { OpenAICompatibleProvider, OpenAICompatibleProviderConfig } from './prov
23
24
  * Version of the plugin-utils package.
24
25
  * Can be used at runtime to check compatibility.
25
26
  */
26
- declare const PLUGIN_UTILS_VERSION = "1.1.0";
27
+ declare const PLUGIN_UTILS_VERSION = "1.2.0";
27
28
 
28
29
  export { PLUGIN_UTILS_VERSION };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { AnthropicToolDefinition, GoogleToolDefinition, LogContext, LogLevel, Op
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
4
  export { OpenAICompatibleProvider, OpenAICompatibleProviderConfig } from './providers/index.js';
5
+ export { CreateRoleplayTemplatePluginOptions, CreateSingleTemplatePluginOptions, createRoleplayTemplatePlugin, createSingleTemplatePlugin, validateRoleplayTemplatePlugin, validateTemplateConfig } from './roleplay-templates/index.js';
5
6
 
6
7
  /**
7
8
  * @quilltap/plugin-utils
@@ -23,6 +24,6 @@ export { OpenAICompatibleProvider, OpenAICompatibleProviderConfig } from './prov
23
24
  * Version of the plugin-utils package.
24
25
  * Can be used at runtime to check compatibility.
25
26
  */
26
- declare const PLUGIN_UTILS_VERSION = "1.1.0";
27
+ declare const PLUGIN_UTILS_VERSION = "1.2.0";
27
28
 
28
29
  export { PLUGIN_UTILS_VERSION };
package/dist/index.js CHANGED
@@ -44,6 +44,8 @@ __export(src_exports, {
44
44
  createConsoleLogger: () => import_plugin_types.createConsoleLogger,
45
45
  createNoopLogger: () => import_plugin_types.createNoopLogger,
46
46
  createPluginLogger: () => createPluginLogger,
47
+ createRoleplayTemplatePlugin: () => createRoleplayTemplatePlugin,
48
+ createSingleTemplatePlugin: () => createSingleTemplatePlugin,
47
49
  detectToolCallFormat: () => detectToolCallFormat,
48
50
  getLogLevelFromEnv: () => getLogLevelFromEnv,
49
51
  hasCoreLogger: () => hasCoreLogger,
@@ -51,7 +53,9 @@ __export(src_exports, {
51
53
  parseAnthropicToolCalls: () => parseAnthropicToolCalls,
52
54
  parseGoogleToolCalls: () => parseGoogleToolCalls,
53
55
  parseOpenAIToolCalls: () => parseOpenAIToolCalls,
54
- parseToolCalls: () => parseToolCalls
56
+ parseToolCalls: () => parseToolCalls,
57
+ validateRoleplayTemplatePlugin: () => validateRoleplayTemplatePlugin,
58
+ validateTemplateConfig: () => validateTemplateConfig
55
59
  });
56
60
  module.exports = __toCommonJS(src_exports);
57
61
 
@@ -506,37 +510,53 @@ var OpenAICompatibleProvider = class {
506
510
  stream_options: { include_usage: true }
507
511
  });
508
512
  let chunkCount = 0;
513
+ let accumulatedUsage = null;
514
+ let finalFinishReason = null;
509
515
  for await (const chunk of stream) {
510
516
  chunkCount++;
511
517
  const content = chunk.choices[0]?.delta?.content;
512
518
  const finishReason = chunk.choices[0]?.finish_reason;
513
519
  const hasUsage = chunk.usage;
514
- if (content && !(finishReason && hasUsage)) {
515
- yield {
516
- content,
517
- done: false
518
- };
520
+ if (finishReason) {
521
+ finalFinishReason = finishReason;
519
522
  }
520
- if (finishReason && hasUsage) {
521
- this.logger.debug("Stream completed", {
523
+ if (hasUsage) {
524
+ accumulatedUsage = {
525
+ prompt_tokens: chunk.usage?.prompt_tokens,
526
+ completion_tokens: chunk.usage?.completion_tokens,
527
+ total_tokens: chunk.usage?.total_tokens
528
+ };
529
+ this.logger.debug("Received usage data in stream", {
522
530
  context: `${this.providerName}Provider.streamMessage`,
523
- finishReason,
524
- chunks: chunkCount,
525
531
  promptTokens: chunk.usage?.prompt_tokens,
526
532
  completionTokens: chunk.usage?.completion_tokens
527
533
  });
534
+ }
535
+ if (content) {
528
536
  yield {
529
- content: "",
530
- done: true,
531
- usage: {
532
- promptTokens: chunk.usage?.prompt_tokens ?? 0,
533
- completionTokens: chunk.usage?.completion_tokens ?? 0,
534
- totalTokens: chunk.usage?.total_tokens ?? 0
535
- },
536
- attachmentResults
537
+ content,
538
+ done: false
537
539
  };
538
540
  }
539
541
  }
542
+ this.logger.debug("Stream completed", {
543
+ context: `${this.providerName}Provider.streamMessage`,
544
+ finishReason: finalFinishReason,
545
+ chunks: chunkCount,
546
+ promptTokens: accumulatedUsage?.prompt_tokens,
547
+ completionTokens: accumulatedUsage?.completion_tokens,
548
+ hasUsage: !!accumulatedUsage
549
+ });
550
+ yield {
551
+ content: "",
552
+ done: true,
553
+ usage: accumulatedUsage ? {
554
+ promptTokens: accumulatedUsage.prompt_tokens ?? 0,
555
+ completionTokens: accumulatedUsage.completion_tokens ?? 0,
556
+ totalTokens: accumulatedUsage.total_tokens ?? 0
557
+ } : void 0,
558
+ attachmentResults
559
+ };
540
560
  } catch (error) {
541
561
  this.logger.error(
542
562
  `${this.providerName} API error in streamMessage`,
@@ -628,8 +648,130 @@ var OpenAICompatibleProvider = class {
628
648
  }
629
649
  };
630
650
 
651
+ // src/roleplay-templates/builder.ts
652
+ function createRoleplayTemplatePlugin(options) {
653
+ const { metadata, templates, initialize, enableLogging = false } = options;
654
+ const templateArray = Array.isArray(templates) ? templates : [templates];
655
+ if (templateArray.length === 0) {
656
+ throw new Error("At least one template is required");
657
+ }
658
+ for (const template of templateArray) {
659
+ if (!template.name || template.name.trim() === "") {
660
+ throw new Error("Template name is required");
661
+ }
662
+ if (!template.systemPrompt || template.systemPrompt.trim() === "") {
663
+ throw new Error(`Template "${template.name}" requires a systemPrompt`);
664
+ }
665
+ }
666
+ const plugin = {
667
+ metadata: {
668
+ ...metadata,
669
+ // Ensure tags from templates are included in metadata if not already set
670
+ tags: metadata.tags ?? Array.from(
671
+ new Set(templateArray.flatMap((t) => t.tags ?? []))
672
+ )
673
+ },
674
+ templates: templateArray
675
+ };
676
+ if (initialize || enableLogging) {
677
+ plugin.initialize = async () => {
678
+ if (enableLogging) {
679
+ const logger = createPluginLogger(metadata.templateId);
680
+ logger.debug("Roleplay template plugin loaded", {
681
+ context: "init",
682
+ templateId: metadata.templateId,
683
+ displayName: metadata.displayName,
684
+ templateCount: templateArray.length,
685
+ templateNames: templateArray.map((t) => t.name)
686
+ });
687
+ }
688
+ if (initialize) {
689
+ await initialize();
690
+ }
691
+ };
692
+ }
693
+ return plugin;
694
+ }
695
+ function createSingleTemplatePlugin(options) {
696
+ const {
697
+ templateId,
698
+ displayName,
699
+ description,
700
+ systemPrompt,
701
+ author,
702
+ tags,
703
+ version,
704
+ initialize,
705
+ enableLogging
706
+ } = options;
707
+ return createRoleplayTemplatePlugin({
708
+ metadata: {
709
+ templateId,
710
+ displayName,
711
+ description,
712
+ author,
713
+ tags,
714
+ version
715
+ },
716
+ templates: {
717
+ name: displayName,
718
+ description,
719
+ systemPrompt,
720
+ tags
721
+ },
722
+ initialize,
723
+ enableLogging
724
+ });
725
+ }
726
+ function validateTemplateConfig(template) {
727
+ if (!template.name || template.name.trim() === "") {
728
+ throw new Error("Template name is required");
729
+ }
730
+ if (template.name.length > 100) {
731
+ throw new Error("Template name must be 100 characters or less");
732
+ }
733
+ if (!template.systemPrompt || template.systemPrompt.trim() === "") {
734
+ throw new Error("Template systemPrompt is required");
735
+ }
736
+ if (template.description && template.description.length > 500) {
737
+ throw new Error("Template description must be 500 characters or less");
738
+ }
739
+ if (template.tags) {
740
+ if (!Array.isArray(template.tags)) {
741
+ throw new Error("Template tags must be an array");
742
+ }
743
+ for (const tag of template.tags) {
744
+ if (typeof tag !== "string") {
745
+ throw new Error("All tags must be strings");
746
+ }
747
+ }
748
+ }
749
+ return true;
750
+ }
751
+ function validateRoleplayTemplatePlugin(plugin) {
752
+ if (!plugin.metadata) {
753
+ throw new Error("Plugin metadata is required");
754
+ }
755
+ if (!plugin.metadata.templateId || plugin.metadata.templateId.trim() === "") {
756
+ throw new Error("Plugin metadata.templateId is required");
757
+ }
758
+ if (!/^[a-z0-9-]+$/.test(plugin.metadata.templateId)) {
759
+ throw new Error("Plugin templateId must be lowercase alphanumeric with hyphens only");
760
+ }
761
+ if (!plugin.metadata.displayName || plugin.metadata.displayName.trim() === "") {
762
+ throw new Error("Plugin metadata.displayName is required");
763
+ }
764
+ if (!plugin.templates || !Array.isArray(plugin.templates) || plugin.templates.length === 0) {
765
+ throw new Error("Plugin must have at least one template");
766
+ }
767
+ for (const template of plugin.templates) {
768
+ validateTemplateConfig(template);
769
+ }
770
+ return true;
771
+ }
772
+
631
773
  // src/index.ts
632
- var PLUGIN_UTILS_VERSION = "1.1.0";
774
+ var PLUGIN_UTILS_VERSION = "1.2.0";
633
775
  // Annotate the CommonJS export names for ESM import in node:
634
776
  0 && (module.exports = {
635
777
  OpenAICompatibleProvider,
@@ -646,6 +788,8 @@ var PLUGIN_UTILS_VERSION = "1.1.0";
646
788
  createConsoleLogger,
647
789
  createNoopLogger,
648
790
  createPluginLogger,
791
+ createRoleplayTemplatePlugin,
792
+ createSingleTemplatePlugin,
649
793
  detectToolCallFormat,
650
794
  getLogLevelFromEnv,
651
795
  hasCoreLogger,
@@ -653,6 +797,8 @@ var PLUGIN_UTILS_VERSION = "1.1.0";
653
797
  parseAnthropicToolCalls,
654
798
  parseGoogleToolCalls,
655
799
  parseOpenAIToolCalls,
656
- parseToolCalls
800
+ parseToolCalls,
801
+ validateRoleplayTemplatePlugin,
802
+ validateTemplateConfig
657
803
  });
658
804
  //# sourceMappingURL=index.js.map