@quilltap/plugin-utils 1.2.3 → 1.2.4
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 +10 -0
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -4
- package/dist/index.mjs.map +1 -1
- package/dist/tools/index.js +13 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/index.mjs +13 -4
- package/dist/tools/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to @quilltap/plugin-utils will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.2.4] - 2026-01-21
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Graceful Handling of Incomplete Tool Call Arguments During Streaming**
|
|
10
|
+
- `parseOpenAIToolCalls()` now checks if JSON arguments look complete before parsing
|
|
11
|
+
- Skips tool calls with incomplete JSON (common during streaming) instead of throwing errors
|
|
12
|
+
- Eliminates noisy "Unterminated string in JSON" errors during streaming tool calls
|
|
13
|
+
- Tool calls are correctly parsed when the final complete response is received
|
|
14
|
+
|
|
5
15
|
## [1.2.2] - 2026-01-09
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -82,10 +82,19 @@ function parseOpenAIToolCalls(response) {
|
|
|
82
82
|
for (const toolCall of toolCallsArray) {
|
|
83
83
|
const tc = toolCall;
|
|
84
84
|
if (tc.type === "function" && tc.function) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
const argsStr = tc.function.arguments || "{}";
|
|
86
|
+
const trimmed = argsStr.trim();
|
|
87
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
toolCalls.push({
|
|
92
|
+
name: tc.function.name,
|
|
93
|
+
arguments: JSON.parse(argsStr)
|
|
94
|
+
});
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/tools/parsers.ts","../src/tools/converters.ts","../src/logging/plugin-logger.ts","../src/logging/index.ts","../src/providers/openai-compatible.ts","../src/roleplay-templates/builder.ts"],"sourcesContent":["/**\n * @quilltap/plugin-utils\n *\n * Utility functions for Quilltap plugin development.\n *\n * This package provides runtime utilities that complement the type definitions\n * in @quilltap/plugin-types. It includes:\n *\n * - **Tool Parsing**: Parse tool calls from any LLM provider's response format\n * - **Tool Conversion**: Convert between OpenAI, Anthropic, and Google tool formats\n * - **Logger Bridge**: Logging that integrates with Quilltap's core or runs standalone\n *\n * @packageDocumentation\n * @module @quilltap/plugin-utils\n */\n\n// ============================================================================\n// Tool Utilities\n// ============================================================================\n\nexport {\n // Parsers\n parseToolCalls,\n parseOpenAIToolCalls,\n parseAnthropicToolCalls,\n parseGoogleToolCalls,\n detectToolCallFormat,\n hasToolCalls,\n\n // Converters\n convertToAnthropicFormat,\n convertToGoogleFormat,\n convertFromAnthropicFormat,\n convertFromGoogleFormat,\n convertToolTo,\n convertToolsTo,\n applyDescriptionLimit,\n // Backward-compatible aliases\n convertOpenAIToAnthropicFormat,\n convertOpenAIToGoogleFormat,\n} from './tools';\n\nexport type {\n // Tool types (re-exported from plugin-types)\n OpenAIToolDefinition,\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n ToolCall,\n ToolCallRequest,\n ToolResult,\n ToolFormatOptions,\n\n // Utility types\n ToolCallFormat,\n ToolConvertTarget,\n} from './tools';\n\n// ============================================================================\n// Logging Utilities\n// ============================================================================\n\nexport {\n // Plugin logger factory\n createPluginLogger,\n hasCoreLogger,\n getLogLevelFromEnv,\n\n // Re-exported from plugin-types\n createConsoleLogger,\n createNoopLogger,\n\n // Internal APIs for Quilltap core\n __injectCoreLoggerFactory,\n __clearCoreLoggerFactory,\n} from './logging';\n\nexport type {\n // Logger types\n PluginLoggerWithChild,\n PluginLogger,\n LogContext,\n LogLevel,\n} from './logging';\n\n// ============================================================================\n// Provider Base Classes\n// ============================================================================\n\nexport {\n OpenAICompatibleProvider,\n} from './providers';\n\nexport type {\n OpenAICompatibleProviderConfig,\n} from './providers';\n\n// ============================================================================\n// Roleplay Template Utilities\n// ============================================================================\n\nexport {\n // Builder functions\n createRoleplayTemplatePlugin,\n createSingleTemplatePlugin,\n\n // Validation utilities\n validateTemplateConfig,\n validateRoleplayTemplatePlugin,\n} from './roleplay-templates';\n\nexport type {\n // Builder option types\n CreateRoleplayTemplatePluginOptions,\n CreateSingleTemplatePluginOptions,\n} from './roleplay-templates';\n\n// ============================================================================\n// Version\n// ============================================================================\n\n/**\n * Version of the plugin-utils package.\n * Can be used at runtime to check compatibility.\n */\nexport const PLUGIN_UTILS_VERSION = '1.2.0';\n","/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(tc.function.arguments || '{}'),\n });\n }\n }\n }\n } catch (error) {\n // Log error but don't throw - return empty array\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\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","/**\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 // Track usage and finish reason separately - they may come in different chunks\n let accumulatedUsage: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number } | null = null;\n let finalFinishReason: string | null = null;\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 // Track finish reason when we get it\n if (finishReason) {\n finalFinishReason = finishReason;\n }\n\n // Track usage when we get it (may come in a separate final chunk)\n if (hasUsage) {\n accumulatedUsage = {\n prompt_tokens: chunk.usage?.prompt_tokens,\n completion_tokens: chunk.usage?.completion_tokens,\n total_tokens: chunk.usage?.total_tokens,\n };\n this.logger.debug('Received usage data in stream', {\n context: `${this.providerName}Provider.streamMessage`,\n promptTokens: chunk.usage?.prompt_tokens,\n completionTokens: chunk.usage?.completion_tokens,\n });\n }\n\n // Yield content chunks\n if (content) {\n yield {\n content,\n done: false,\n };\n }\n }\n\n // After stream ends, yield final chunk with accumulated usage\n this.logger.debug('Stream completed', {\n context: `${this.providerName}Provider.streamMessage`,\n finishReason: finalFinishReason,\n chunks: chunkCount,\n promptTokens: accumulatedUsage?.prompt_tokens,\n completionTokens: accumulatedUsage?.completion_tokens,\n hasUsage: !!accumulatedUsage,\n });\n\n yield {\n content: '',\n done: true,\n usage: accumulatedUsage ? {\n promptTokens: accumulatedUsage.prompt_tokens ?? 0,\n completionTokens: accumulatedUsage.completion_tokens ?? 0,\n totalTokens: accumulatedUsage.total_tokens ?? 0,\n } : undefined,\n attachmentResults,\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 * Roleplay Template Plugin Builder utilities\n *\n * Provides helper functions for creating and validating roleplay template plugins.\n *\n * @module @quilltap/plugin-utils/roleplay-templates\n */\n\nimport type {\n RoleplayTemplateConfig,\n RoleplayTemplateMetadata,\n RoleplayTemplatePlugin,\n} from '@quilltap/plugin-types';\nimport { createPluginLogger } from '../logging';\n\n// ============================================================================\n// BUILDER OPTIONS\n// ============================================================================\n\n/**\n * Options for creating a roleplay template plugin\n */\nexport interface CreateRoleplayTemplatePluginOptions {\n /** Plugin metadata */\n metadata: RoleplayTemplateMetadata;\n\n /**\n * One or more roleplay templates.\n * Pass a single template object or an array of templates.\n */\n templates: RoleplayTemplateConfig | RoleplayTemplateConfig[];\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n/**\n * Simplified options for plugins that provide a single template\n */\nexport interface CreateSingleTemplatePluginOptions {\n /** Unique template identifier (lowercase, hyphens allowed) */\n templateId: string;\n\n /** Human-readable display name */\n displayName: string;\n\n /** Template description */\n description?: string;\n\n /**\n * The system prompt that defines the formatting rules.\n * This is prepended to character system prompts when the template is active.\n */\n systemPrompt: string;\n\n /** Template author */\n author?: string | {\n name: string;\n email?: string;\n url?: string;\n };\n\n /** Tags for categorization and searchability */\n tags?: string[];\n\n /** Template version */\n version?: string;\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n// ============================================================================\n// BUILDER FUNCTIONS\n// ============================================================================\n\n/**\n * Creates a roleplay template plugin with full control over metadata and templates.\n *\n * Use this when you want to provide multiple templates or have fine-grained\n * control over the plugin structure.\n *\n * @param options - Plugin configuration options\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createRoleplayTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createRoleplayTemplatePlugin({\n * metadata: {\n * templateId: 'my-rp-format',\n * displayName: 'My RP Format',\n * description: 'A custom roleplay formatting style',\n * },\n * templates: [\n * {\n * name: 'My RP Format',\n * description: 'Custom formatting with specific syntax',\n * systemPrompt: '[FORMATTING INSTRUCTIONS]...',\n * tags: ['custom'],\n * },\n * ],\n * });\n * ```\n */\nexport function createRoleplayTemplatePlugin(\n options: CreateRoleplayTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const { metadata, templates, initialize, enableLogging = false } = options;\n\n // Normalize templates to array\n const templateArray = Array.isArray(templates) ? templates : [templates];\n\n // Validate templates\n if (templateArray.length === 0) {\n throw new Error('At least one template is required');\n }\n\n for (const template of templateArray) {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error(`Template \"${template.name}\" requires a systemPrompt`);\n }\n }\n\n // Create the plugin\n const plugin: RoleplayTemplatePlugin = {\n metadata: {\n ...metadata,\n // Ensure tags from templates are included in metadata if not already set\n tags: metadata.tags ?? Array.from(\n new Set(templateArray.flatMap(t => t.tags ?? []))\n ),\n },\n templates: templateArray,\n };\n\n // Add initialize function with optional logging\n if (initialize || enableLogging) {\n plugin.initialize = async () => {\n if (enableLogging) {\n const logger = createPluginLogger(metadata.templateId);\n logger.debug('Roleplay template plugin loaded', {\n context: 'init',\n templateId: metadata.templateId,\n displayName: metadata.displayName,\n templateCount: templateArray.length,\n templateNames: templateArray.map(t => t.name),\n });\n }\n\n if (initialize) {\n await initialize();\n }\n };\n }\n\n return plugin;\n}\n\n/**\n * Creates a simple roleplay template plugin with a single template.\n *\n * This is a convenience function for the common case of a plugin\n * that provides just one roleplay template.\n *\n * @param options - Simplified plugin configuration\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createSingleTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createSingleTemplatePlugin({\n * templateId: 'quilltap-rp',\n * displayName: 'Quilltap RP',\n * description: 'Custom formatting with [actions], {thoughts}, and // OOC',\n * systemPrompt: `[FORMATTING INSTRUCTIONS]\n * 1. DIALOGUE: Write as bare text without quotes\n * 2. ACTIONS: Use [square brackets]\n * 3. THOUGHTS: Use {curly braces}\n * 4. OOC: Use // prefix`,\n * tags: ['quilltap', 'custom'],\n * });\n * ```\n */\nexport function createSingleTemplatePlugin(\n options: CreateSingleTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const {\n templateId,\n displayName,\n description,\n systemPrompt,\n author,\n tags,\n version,\n initialize,\n enableLogging,\n } = options;\n\n return createRoleplayTemplatePlugin({\n metadata: {\n templateId,\n displayName,\n description,\n author,\n tags,\n version,\n },\n templates: {\n name: displayName,\n description,\n systemPrompt,\n tags,\n },\n initialize,\n enableLogging,\n });\n}\n\n// ============================================================================\n// VALIDATION UTILITIES\n// ============================================================================\n\n/**\n * Validates a roleplay template configuration\n *\n * @param template - The template configuration to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateTemplateConfig(template: RoleplayTemplateConfig): boolean {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n\n if (template.name.length > 100) {\n throw new Error('Template name must be 100 characters or less');\n }\n\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error('Template systemPrompt is required');\n }\n\n if (template.description && template.description.length > 500) {\n throw new Error('Template description must be 500 characters or less');\n }\n\n if (template.tags) {\n if (!Array.isArray(template.tags)) {\n throw new Error('Template tags must be an array');\n }\n for (const tag of template.tags) {\n if (typeof tag !== 'string') {\n throw new Error('All tags must be strings');\n }\n }\n }\n\n return true;\n}\n\n/**\n * Validates a complete roleplay template plugin\n *\n * @param plugin - The plugin to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateRoleplayTemplatePlugin(plugin: RoleplayTemplatePlugin): boolean {\n // Validate metadata\n if (!plugin.metadata) {\n throw new Error('Plugin metadata is required');\n }\n\n if (!plugin.metadata.templateId || plugin.metadata.templateId.trim() === '') {\n throw new Error('Plugin metadata.templateId is required');\n }\n\n if (!/^[a-z0-9-]+$/.test(plugin.metadata.templateId)) {\n throw new Error('Plugin templateId must be lowercase alphanumeric with hyphens only');\n }\n\n if (!plugin.metadata.displayName || plugin.metadata.displayName.trim() === '') {\n throw new Error('Plugin metadata.displayName is required');\n }\n\n // Validate templates\n if (!plugin.templates || !Array.isArray(plugin.templates) || plugin.templates.length === 0) {\n throw new Error('Plugin must have at least one template');\n }\n\n for (const template of plugin.templates) {\n validateTemplateConfig(template);\n }\n\n return true;\n}\n"],"mappings":"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","names":["OpenAI"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/tools/parsers.ts","../src/tools/converters.ts","../src/logging/plugin-logger.ts","../src/logging/index.ts","../src/providers/openai-compatible.ts","../src/roleplay-templates/builder.ts"],"sourcesContent":["/**\n * @quilltap/plugin-utils\n *\n * Utility functions for Quilltap plugin development.\n *\n * This package provides runtime utilities that complement the type definitions\n * in @quilltap/plugin-types. It includes:\n *\n * - **Tool Parsing**: Parse tool calls from any LLM provider's response format\n * - **Tool Conversion**: Convert between OpenAI, Anthropic, and Google tool formats\n * - **Logger Bridge**: Logging that integrates with Quilltap's core or runs standalone\n *\n * @packageDocumentation\n * @module @quilltap/plugin-utils\n */\n\n// ============================================================================\n// Tool Utilities\n// ============================================================================\n\nexport {\n // Parsers\n parseToolCalls,\n parseOpenAIToolCalls,\n parseAnthropicToolCalls,\n parseGoogleToolCalls,\n detectToolCallFormat,\n hasToolCalls,\n\n // Converters\n convertToAnthropicFormat,\n convertToGoogleFormat,\n convertFromAnthropicFormat,\n convertFromGoogleFormat,\n convertToolTo,\n convertToolsTo,\n applyDescriptionLimit,\n // Backward-compatible aliases\n convertOpenAIToAnthropicFormat,\n convertOpenAIToGoogleFormat,\n} from './tools';\n\nexport type {\n // Tool types (re-exported from plugin-types)\n OpenAIToolDefinition,\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n ToolCall,\n ToolCallRequest,\n ToolResult,\n ToolFormatOptions,\n\n // Utility types\n ToolCallFormat,\n ToolConvertTarget,\n} from './tools';\n\n// ============================================================================\n// Logging Utilities\n// ============================================================================\n\nexport {\n // Plugin logger factory\n createPluginLogger,\n hasCoreLogger,\n getLogLevelFromEnv,\n\n // Re-exported from plugin-types\n createConsoleLogger,\n createNoopLogger,\n\n // Internal APIs for Quilltap core\n __injectCoreLoggerFactory,\n __clearCoreLoggerFactory,\n} from './logging';\n\nexport type {\n // Logger types\n PluginLoggerWithChild,\n PluginLogger,\n LogContext,\n LogLevel,\n} from './logging';\n\n// ============================================================================\n// Provider Base Classes\n// ============================================================================\n\nexport {\n OpenAICompatibleProvider,\n} from './providers';\n\nexport type {\n OpenAICompatibleProviderConfig,\n} from './providers';\n\n// ============================================================================\n// Roleplay Template Utilities\n// ============================================================================\n\nexport {\n // Builder functions\n createRoleplayTemplatePlugin,\n createSingleTemplatePlugin,\n\n // Validation utilities\n validateTemplateConfig,\n validateRoleplayTemplatePlugin,\n} from './roleplay-templates';\n\nexport type {\n // Builder option types\n CreateRoleplayTemplatePluginOptions,\n CreateSingleTemplatePluginOptions,\n} from './roleplay-templates';\n\n// ============================================================================\n// Version\n// ============================================================================\n\n/**\n * Version of the plugin-utils package.\n * Can be used at runtime to check compatibility.\n */\nexport const PLUGIN_UTILS_VERSION = '1.2.0';\n","/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n const argsStr = tc.function.arguments || '{}';\n\n // During streaming, arguments may be incomplete JSON.\n // Check if the string looks like complete JSON before parsing.\n // Skip incomplete tool calls - they'll be complete in the final response.\n const trimmed = argsStr.trim();\n if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {\n // Incomplete JSON - skip this tool call for now\n continue;\n }\n\n try {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(argsStr),\n });\n } catch {\n // JSON parse failed (e.g., incomplete JSON during streaming)\n // This is expected during streaming - skip silently\n continue;\n }\n }\n }\n }\n } catch (error) {\n // Log error for unexpected parsing failures (not JSON parse errors)\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\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","/**\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 // Track usage and finish reason separately - they may come in different chunks\n let accumulatedUsage: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number } | null = null;\n let finalFinishReason: string | null = null;\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 // Track finish reason when we get it\n if (finishReason) {\n finalFinishReason = finishReason;\n }\n\n // Track usage when we get it (may come in a separate final chunk)\n if (hasUsage) {\n accumulatedUsage = {\n prompt_tokens: chunk.usage?.prompt_tokens,\n completion_tokens: chunk.usage?.completion_tokens,\n total_tokens: chunk.usage?.total_tokens,\n };\n this.logger.debug('Received usage data in stream', {\n context: `${this.providerName}Provider.streamMessage`,\n promptTokens: chunk.usage?.prompt_tokens,\n completionTokens: chunk.usage?.completion_tokens,\n });\n }\n\n // Yield content chunks\n if (content) {\n yield {\n content,\n done: false,\n };\n }\n }\n\n // After stream ends, yield final chunk with accumulated usage\n this.logger.debug('Stream completed', {\n context: `${this.providerName}Provider.streamMessage`,\n finishReason: finalFinishReason,\n chunks: chunkCount,\n promptTokens: accumulatedUsage?.prompt_tokens,\n completionTokens: accumulatedUsage?.completion_tokens,\n hasUsage: !!accumulatedUsage,\n });\n\n yield {\n content: '',\n done: true,\n usage: accumulatedUsage ? {\n promptTokens: accumulatedUsage.prompt_tokens ?? 0,\n completionTokens: accumulatedUsage.completion_tokens ?? 0,\n totalTokens: accumulatedUsage.total_tokens ?? 0,\n } : undefined,\n attachmentResults,\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 * Roleplay Template Plugin Builder utilities\n *\n * Provides helper functions for creating and validating roleplay template plugins.\n *\n * @module @quilltap/plugin-utils/roleplay-templates\n */\n\nimport type {\n RoleplayTemplateConfig,\n RoleplayTemplateMetadata,\n RoleplayTemplatePlugin,\n} from '@quilltap/plugin-types';\nimport { createPluginLogger } from '../logging';\n\n// ============================================================================\n// BUILDER OPTIONS\n// ============================================================================\n\n/**\n * Options for creating a roleplay template plugin\n */\nexport interface CreateRoleplayTemplatePluginOptions {\n /** Plugin metadata */\n metadata: RoleplayTemplateMetadata;\n\n /**\n * One or more roleplay templates.\n * Pass a single template object or an array of templates.\n */\n templates: RoleplayTemplateConfig | RoleplayTemplateConfig[];\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n/**\n * Simplified options for plugins that provide a single template\n */\nexport interface CreateSingleTemplatePluginOptions {\n /** Unique template identifier (lowercase, hyphens allowed) */\n templateId: string;\n\n /** Human-readable display name */\n displayName: string;\n\n /** Template description */\n description?: string;\n\n /**\n * The system prompt that defines the formatting rules.\n * This is prepended to character system prompts when the template is active.\n */\n systemPrompt: string;\n\n /** Template author */\n author?: string | {\n name: string;\n email?: string;\n url?: string;\n };\n\n /** Tags for categorization and searchability */\n tags?: string[];\n\n /** Template version */\n version?: string;\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n// ============================================================================\n// BUILDER FUNCTIONS\n// ============================================================================\n\n/**\n * Creates a roleplay template plugin with full control over metadata and templates.\n *\n * Use this when you want to provide multiple templates or have fine-grained\n * control over the plugin structure.\n *\n * @param options - Plugin configuration options\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createRoleplayTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createRoleplayTemplatePlugin({\n * metadata: {\n * templateId: 'my-rp-format',\n * displayName: 'My RP Format',\n * description: 'A custom roleplay formatting style',\n * },\n * templates: [\n * {\n * name: 'My RP Format',\n * description: 'Custom formatting with specific syntax',\n * systemPrompt: '[FORMATTING INSTRUCTIONS]...',\n * tags: ['custom'],\n * },\n * ],\n * });\n * ```\n */\nexport function createRoleplayTemplatePlugin(\n options: CreateRoleplayTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const { metadata, templates, initialize, enableLogging = false } = options;\n\n // Normalize templates to array\n const templateArray = Array.isArray(templates) ? templates : [templates];\n\n // Validate templates\n if (templateArray.length === 0) {\n throw new Error('At least one template is required');\n }\n\n for (const template of templateArray) {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error(`Template \"${template.name}\" requires a systemPrompt`);\n }\n }\n\n // Create the plugin\n const plugin: RoleplayTemplatePlugin = {\n metadata: {\n ...metadata,\n // Ensure tags from templates are included in metadata if not already set\n tags: metadata.tags ?? Array.from(\n new Set(templateArray.flatMap(t => t.tags ?? []))\n ),\n },\n templates: templateArray,\n };\n\n // Add initialize function with optional logging\n if (initialize || enableLogging) {\n plugin.initialize = async () => {\n if (enableLogging) {\n const logger = createPluginLogger(metadata.templateId);\n logger.debug('Roleplay template plugin loaded', {\n context: 'init',\n templateId: metadata.templateId,\n displayName: metadata.displayName,\n templateCount: templateArray.length,\n templateNames: templateArray.map(t => t.name),\n });\n }\n\n if (initialize) {\n await initialize();\n }\n };\n }\n\n return plugin;\n}\n\n/**\n * Creates a simple roleplay template plugin with a single template.\n *\n * This is a convenience function for the common case of a plugin\n * that provides just one roleplay template.\n *\n * @param options - Simplified plugin configuration\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createSingleTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createSingleTemplatePlugin({\n * templateId: 'quilltap-rp',\n * displayName: 'Quilltap RP',\n * description: 'Custom formatting with [actions], {thoughts}, and // OOC',\n * systemPrompt: `[FORMATTING INSTRUCTIONS]\n * 1. DIALOGUE: Write as bare text without quotes\n * 2. ACTIONS: Use [square brackets]\n * 3. THOUGHTS: Use {curly braces}\n * 4. OOC: Use // prefix`,\n * tags: ['quilltap', 'custom'],\n * });\n * ```\n */\nexport function createSingleTemplatePlugin(\n options: CreateSingleTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const {\n templateId,\n displayName,\n description,\n systemPrompt,\n author,\n tags,\n version,\n initialize,\n enableLogging,\n } = options;\n\n return createRoleplayTemplatePlugin({\n metadata: {\n templateId,\n displayName,\n description,\n author,\n tags,\n version,\n },\n templates: {\n name: displayName,\n description,\n systemPrompt,\n tags,\n },\n initialize,\n enableLogging,\n });\n}\n\n// ============================================================================\n// VALIDATION UTILITIES\n// ============================================================================\n\n/**\n * Validates a roleplay template configuration\n *\n * @param template - The template configuration to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateTemplateConfig(template: RoleplayTemplateConfig): boolean {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n\n if (template.name.length > 100) {\n throw new Error('Template name must be 100 characters or less');\n }\n\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error('Template systemPrompt is required');\n }\n\n if (template.description && template.description.length > 500) {\n throw new Error('Template description must be 500 characters or less');\n }\n\n if (template.tags) {\n if (!Array.isArray(template.tags)) {\n throw new Error('Template tags must be an array');\n }\n for (const tag of template.tags) {\n if (typeof tag !== 'string') {\n throw new Error('All tags must be strings');\n }\n }\n }\n\n return true;\n}\n\n/**\n * Validates a complete roleplay template plugin\n *\n * @param plugin - The plugin to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateRoleplayTemplatePlugin(plugin: RoleplayTemplatePlugin): boolean {\n // Validate metadata\n if (!plugin.metadata) {\n throw new Error('Plugin metadata is required');\n }\n\n if (!plugin.metadata.templateId || plugin.metadata.templateId.trim() === '') {\n throw new Error('Plugin metadata.templateId is required');\n }\n\n if (!/^[a-z0-9-]+$/.test(plugin.metadata.templateId)) {\n throw new Error('Plugin templateId must be lowercase alphanumeric with hyphens only');\n }\n\n if (!plugin.metadata.displayName || plugin.metadata.displayName.trim() === '') {\n throw new Error('Plugin metadata.displayName is required');\n }\n\n // Validate templates\n if (!plugin.templates || !Array.isArray(plugin.templates) || plugin.templates.length === 0) {\n throw new Error('Plugin must have at least one template');\n }\n\n for (const template of plugin.templates) {\n validateTemplateConfig(template);\n }\n\n return true;\n}\n"],"mappings":"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","names":["OpenAI"]}
|
package/dist/index.mjs
CHANGED
|
@@ -19,10 +19,19 @@ function parseOpenAIToolCalls(response) {
|
|
|
19
19
|
for (const toolCall of toolCallsArray) {
|
|
20
20
|
const tc = toolCall;
|
|
21
21
|
if (tc.type === "function" && tc.function) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const argsStr = tc.function.arguments || "{}";
|
|
23
|
+
const trimmed = argsStr.trim();
|
|
24
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
toolCalls.push({
|
|
29
|
+
name: tc.function.name,
|
|
30
|
+
arguments: JSON.parse(argsStr)
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tools/parsers.ts","../src/tools/converters.ts","../src/logging/plugin-logger.ts","../src/logging/index.ts","../src/providers/openai-compatible.ts","../src/roleplay-templates/builder.ts","../src/index.ts"],"sourcesContent":["/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(tc.function.arguments || '{}'),\n });\n }\n }\n }\n } catch (error) {\n // Log error but don't throw - return empty array\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\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","/**\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 // Track usage and finish reason separately - they may come in different chunks\n let accumulatedUsage: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number } | null = null;\n let finalFinishReason: string | null = null;\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 // Track finish reason when we get it\n if (finishReason) {\n finalFinishReason = finishReason;\n }\n\n // Track usage when we get it (may come in a separate final chunk)\n if (hasUsage) {\n accumulatedUsage = {\n prompt_tokens: chunk.usage?.prompt_tokens,\n completion_tokens: chunk.usage?.completion_tokens,\n total_tokens: chunk.usage?.total_tokens,\n };\n this.logger.debug('Received usage data in stream', {\n context: `${this.providerName}Provider.streamMessage`,\n promptTokens: chunk.usage?.prompt_tokens,\n completionTokens: chunk.usage?.completion_tokens,\n });\n }\n\n // Yield content chunks\n if (content) {\n yield {\n content,\n done: false,\n };\n }\n }\n\n // After stream ends, yield final chunk with accumulated usage\n this.logger.debug('Stream completed', {\n context: `${this.providerName}Provider.streamMessage`,\n finishReason: finalFinishReason,\n chunks: chunkCount,\n promptTokens: accumulatedUsage?.prompt_tokens,\n completionTokens: accumulatedUsage?.completion_tokens,\n hasUsage: !!accumulatedUsage,\n });\n\n yield {\n content: '',\n done: true,\n usage: accumulatedUsage ? {\n promptTokens: accumulatedUsage.prompt_tokens ?? 0,\n completionTokens: accumulatedUsage.completion_tokens ?? 0,\n totalTokens: accumulatedUsage.total_tokens ?? 0,\n } : undefined,\n attachmentResults,\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 * Roleplay Template Plugin Builder utilities\n *\n * Provides helper functions for creating and validating roleplay template plugins.\n *\n * @module @quilltap/plugin-utils/roleplay-templates\n */\n\nimport type {\n RoleplayTemplateConfig,\n RoleplayTemplateMetadata,\n RoleplayTemplatePlugin,\n} from '@quilltap/plugin-types';\nimport { createPluginLogger } from '../logging';\n\n// ============================================================================\n// BUILDER OPTIONS\n// ============================================================================\n\n/**\n * Options for creating a roleplay template plugin\n */\nexport interface CreateRoleplayTemplatePluginOptions {\n /** Plugin metadata */\n metadata: RoleplayTemplateMetadata;\n\n /**\n * One or more roleplay templates.\n * Pass a single template object or an array of templates.\n */\n templates: RoleplayTemplateConfig | RoleplayTemplateConfig[];\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n/**\n * Simplified options for plugins that provide a single template\n */\nexport interface CreateSingleTemplatePluginOptions {\n /** Unique template identifier (lowercase, hyphens allowed) */\n templateId: string;\n\n /** Human-readable display name */\n displayName: string;\n\n /** Template description */\n description?: string;\n\n /**\n * The system prompt that defines the formatting rules.\n * This is prepended to character system prompts when the template is active.\n */\n systemPrompt: string;\n\n /** Template author */\n author?: string | {\n name: string;\n email?: string;\n url?: string;\n };\n\n /** Tags for categorization and searchability */\n tags?: string[];\n\n /** Template version */\n version?: string;\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n// ============================================================================\n// BUILDER FUNCTIONS\n// ============================================================================\n\n/**\n * Creates a roleplay template plugin with full control over metadata and templates.\n *\n * Use this when you want to provide multiple templates or have fine-grained\n * control over the plugin structure.\n *\n * @param options - Plugin configuration options\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createRoleplayTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createRoleplayTemplatePlugin({\n * metadata: {\n * templateId: 'my-rp-format',\n * displayName: 'My RP Format',\n * description: 'A custom roleplay formatting style',\n * },\n * templates: [\n * {\n * name: 'My RP Format',\n * description: 'Custom formatting with specific syntax',\n * systemPrompt: '[FORMATTING INSTRUCTIONS]...',\n * tags: ['custom'],\n * },\n * ],\n * });\n * ```\n */\nexport function createRoleplayTemplatePlugin(\n options: CreateRoleplayTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const { metadata, templates, initialize, enableLogging = false } = options;\n\n // Normalize templates to array\n const templateArray = Array.isArray(templates) ? templates : [templates];\n\n // Validate templates\n if (templateArray.length === 0) {\n throw new Error('At least one template is required');\n }\n\n for (const template of templateArray) {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error(`Template \"${template.name}\" requires a systemPrompt`);\n }\n }\n\n // Create the plugin\n const plugin: RoleplayTemplatePlugin = {\n metadata: {\n ...metadata,\n // Ensure tags from templates are included in metadata if not already set\n tags: metadata.tags ?? Array.from(\n new Set(templateArray.flatMap(t => t.tags ?? []))\n ),\n },\n templates: templateArray,\n };\n\n // Add initialize function with optional logging\n if (initialize || enableLogging) {\n plugin.initialize = async () => {\n if (enableLogging) {\n const logger = createPluginLogger(metadata.templateId);\n logger.debug('Roleplay template plugin loaded', {\n context: 'init',\n templateId: metadata.templateId,\n displayName: metadata.displayName,\n templateCount: templateArray.length,\n templateNames: templateArray.map(t => t.name),\n });\n }\n\n if (initialize) {\n await initialize();\n }\n };\n }\n\n return plugin;\n}\n\n/**\n * Creates a simple roleplay template plugin with a single template.\n *\n * This is a convenience function for the common case of a plugin\n * that provides just one roleplay template.\n *\n * @param options - Simplified plugin configuration\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createSingleTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createSingleTemplatePlugin({\n * templateId: 'quilltap-rp',\n * displayName: 'Quilltap RP',\n * description: 'Custom formatting with [actions], {thoughts}, and // OOC',\n * systemPrompt: `[FORMATTING INSTRUCTIONS]\n * 1. DIALOGUE: Write as bare text without quotes\n * 2. ACTIONS: Use [square brackets]\n * 3. THOUGHTS: Use {curly braces}\n * 4. OOC: Use // prefix`,\n * tags: ['quilltap', 'custom'],\n * });\n * ```\n */\nexport function createSingleTemplatePlugin(\n options: CreateSingleTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const {\n templateId,\n displayName,\n description,\n systemPrompt,\n author,\n tags,\n version,\n initialize,\n enableLogging,\n } = options;\n\n return createRoleplayTemplatePlugin({\n metadata: {\n templateId,\n displayName,\n description,\n author,\n tags,\n version,\n },\n templates: {\n name: displayName,\n description,\n systemPrompt,\n tags,\n },\n initialize,\n enableLogging,\n });\n}\n\n// ============================================================================\n// VALIDATION UTILITIES\n// ============================================================================\n\n/**\n * Validates a roleplay template configuration\n *\n * @param template - The template configuration to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateTemplateConfig(template: RoleplayTemplateConfig): boolean {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n\n if (template.name.length > 100) {\n throw new Error('Template name must be 100 characters or less');\n }\n\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error('Template systemPrompt is required');\n }\n\n if (template.description && template.description.length > 500) {\n throw new Error('Template description must be 500 characters or less');\n }\n\n if (template.tags) {\n if (!Array.isArray(template.tags)) {\n throw new Error('Template tags must be an array');\n }\n for (const tag of template.tags) {\n if (typeof tag !== 'string') {\n throw new Error('All tags must be strings');\n }\n }\n }\n\n return true;\n}\n\n/**\n * Validates a complete roleplay template plugin\n *\n * @param plugin - The plugin to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateRoleplayTemplatePlugin(plugin: RoleplayTemplatePlugin): boolean {\n // Validate metadata\n if (!plugin.metadata) {\n throw new Error('Plugin metadata is required');\n }\n\n if (!plugin.metadata.templateId || plugin.metadata.templateId.trim() === '') {\n throw new Error('Plugin metadata.templateId is required');\n }\n\n if (!/^[a-z0-9-]+$/.test(plugin.metadata.templateId)) {\n throw new Error('Plugin templateId must be lowercase alphanumeric with hyphens only');\n }\n\n if (!plugin.metadata.displayName || plugin.metadata.displayName.trim() === '') {\n throw new Error('Plugin metadata.displayName is required');\n }\n\n // Validate templates\n if (!plugin.templates || !Array.isArray(plugin.templates) || plugin.templates.length === 0) {\n throw new Error('Plugin must have at least one template');\n }\n\n for (const template of plugin.templates) {\n validateTemplateConfig(template);\n }\n\n return true;\n}\n","/**\n * @quilltap/plugin-utils\n *\n * Utility functions for Quilltap plugin development.\n *\n * This package provides runtime utilities that complement the type definitions\n * in @quilltap/plugin-types. It includes:\n *\n * - **Tool Parsing**: Parse tool calls from any LLM provider's response format\n * - **Tool Conversion**: Convert between OpenAI, Anthropic, and Google tool formats\n * - **Logger Bridge**: Logging that integrates with Quilltap's core or runs standalone\n *\n * @packageDocumentation\n * @module @quilltap/plugin-utils\n */\n\n// ============================================================================\n// Tool Utilities\n// ============================================================================\n\nexport {\n // Parsers\n parseToolCalls,\n parseOpenAIToolCalls,\n parseAnthropicToolCalls,\n parseGoogleToolCalls,\n detectToolCallFormat,\n hasToolCalls,\n\n // Converters\n convertToAnthropicFormat,\n convertToGoogleFormat,\n convertFromAnthropicFormat,\n convertFromGoogleFormat,\n convertToolTo,\n convertToolsTo,\n applyDescriptionLimit,\n // Backward-compatible aliases\n convertOpenAIToAnthropicFormat,\n convertOpenAIToGoogleFormat,\n} from './tools';\n\nexport type {\n // Tool types (re-exported from plugin-types)\n OpenAIToolDefinition,\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n ToolCall,\n ToolCallRequest,\n ToolResult,\n ToolFormatOptions,\n\n // Utility types\n ToolCallFormat,\n ToolConvertTarget,\n} from './tools';\n\n// ============================================================================\n// Logging Utilities\n// ============================================================================\n\nexport {\n // Plugin logger factory\n createPluginLogger,\n hasCoreLogger,\n getLogLevelFromEnv,\n\n // Re-exported from plugin-types\n createConsoleLogger,\n createNoopLogger,\n\n // Internal APIs for Quilltap core\n __injectCoreLoggerFactory,\n __clearCoreLoggerFactory,\n} from './logging';\n\nexport type {\n // Logger types\n PluginLoggerWithChild,\n PluginLogger,\n LogContext,\n LogLevel,\n} from './logging';\n\n// ============================================================================\n// Provider Base Classes\n// ============================================================================\n\nexport {\n OpenAICompatibleProvider,\n} from './providers';\n\nexport type {\n OpenAICompatibleProviderConfig,\n} from './providers';\n\n// ============================================================================\n// Roleplay Template Utilities\n// ============================================================================\n\nexport {\n // Builder functions\n createRoleplayTemplatePlugin,\n createSingleTemplatePlugin,\n\n // Validation utilities\n validateTemplateConfig,\n validateRoleplayTemplatePlugin,\n} from './roleplay-templates';\n\nexport type {\n // Builder option types\n CreateRoleplayTemplatePluginOptions,\n CreateSingleTemplatePluginOptions,\n} from './roleplay-templates';\n\n// ============================================================================\n// Version\n// ============================================================================\n\n/**\n * Version of the plugin-utils package.\n * Can be used at runtime to check compatibility.\n */\nexport const PLUGIN_UTILS_VERSION = '1.2.0';\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/tools/parsers.ts","../src/tools/converters.ts","../src/logging/plugin-logger.ts","../src/logging/index.ts","../src/providers/openai-compatible.ts","../src/roleplay-templates/builder.ts","../src/index.ts"],"sourcesContent":["/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n const argsStr = tc.function.arguments || '{}';\n\n // During streaming, arguments may be incomplete JSON.\n // Check if the string looks like complete JSON before parsing.\n // Skip incomplete tool calls - they'll be complete in the final response.\n const trimmed = argsStr.trim();\n if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {\n // Incomplete JSON - skip this tool call for now\n continue;\n }\n\n try {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(argsStr),\n });\n } catch {\n // JSON parse failed (e.g., incomplete JSON during streaming)\n // This is expected during streaming - skip silently\n continue;\n }\n }\n }\n }\n } catch (error) {\n // Log error for unexpected parsing failures (not JSON parse errors)\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\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","/**\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 // Track usage and finish reason separately - they may come in different chunks\n let accumulatedUsage: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number } | null = null;\n let finalFinishReason: string | null = null;\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 // Track finish reason when we get it\n if (finishReason) {\n finalFinishReason = finishReason;\n }\n\n // Track usage when we get it (may come in a separate final chunk)\n if (hasUsage) {\n accumulatedUsage = {\n prompt_tokens: chunk.usage?.prompt_tokens,\n completion_tokens: chunk.usage?.completion_tokens,\n total_tokens: chunk.usage?.total_tokens,\n };\n this.logger.debug('Received usage data in stream', {\n context: `${this.providerName}Provider.streamMessage`,\n promptTokens: chunk.usage?.prompt_tokens,\n completionTokens: chunk.usage?.completion_tokens,\n });\n }\n\n // Yield content chunks\n if (content) {\n yield {\n content,\n done: false,\n };\n }\n }\n\n // After stream ends, yield final chunk with accumulated usage\n this.logger.debug('Stream completed', {\n context: `${this.providerName}Provider.streamMessage`,\n finishReason: finalFinishReason,\n chunks: chunkCount,\n promptTokens: accumulatedUsage?.prompt_tokens,\n completionTokens: accumulatedUsage?.completion_tokens,\n hasUsage: !!accumulatedUsage,\n });\n\n yield {\n content: '',\n done: true,\n usage: accumulatedUsage ? {\n promptTokens: accumulatedUsage.prompt_tokens ?? 0,\n completionTokens: accumulatedUsage.completion_tokens ?? 0,\n totalTokens: accumulatedUsage.total_tokens ?? 0,\n } : undefined,\n attachmentResults,\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 * Roleplay Template Plugin Builder utilities\n *\n * Provides helper functions for creating and validating roleplay template plugins.\n *\n * @module @quilltap/plugin-utils/roleplay-templates\n */\n\nimport type {\n RoleplayTemplateConfig,\n RoleplayTemplateMetadata,\n RoleplayTemplatePlugin,\n} from '@quilltap/plugin-types';\nimport { createPluginLogger } from '../logging';\n\n// ============================================================================\n// BUILDER OPTIONS\n// ============================================================================\n\n/**\n * Options for creating a roleplay template plugin\n */\nexport interface CreateRoleplayTemplatePluginOptions {\n /** Plugin metadata */\n metadata: RoleplayTemplateMetadata;\n\n /**\n * One or more roleplay templates.\n * Pass a single template object or an array of templates.\n */\n templates: RoleplayTemplateConfig | RoleplayTemplateConfig[];\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n/**\n * Simplified options for plugins that provide a single template\n */\nexport interface CreateSingleTemplatePluginOptions {\n /** Unique template identifier (lowercase, hyphens allowed) */\n templateId: string;\n\n /** Human-readable display name */\n displayName: string;\n\n /** Template description */\n description?: string;\n\n /**\n * The system prompt that defines the formatting rules.\n * This is prepended to character system prompts when the template is active.\n */\n systemPrompt: string;\n\n /** Template author */\n author?: string | {\n name: string;\n email?: string;\n url?: string;\n };\n\n /** Tags for categorization and searchability */\n tags?: string[];\n\n /** Template version */\n version?: string;\n\n /**\n * Optional initialization function.\n * Called when the plugin is loaded.\n */\n initialize?: () => void | Promise<void>;\n\n /**\n * Whether to enable debug logging.\n * Defaults to false.\n */\n enableLogging?: boolean;\n}\n\n// ============================================================================\n// BUILDER FUNCTIONS\n// ============================================================================\n\n/**\n * Creates a roleplay template plugin with full control over metadata and templates.\n *\n * Use this when you want to provide multiple templates or have fine-grained\n * control over the plugin structure.\n *\n * @param options - Plugin configuration options\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createRoleplayTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createRoleplayTemplatePlugin({\n * metadata: {\n * templateId: 'my-rp-format',\n * displayName: 'My RP Format',\n * description: 'A custom roleplay formatting style',\n * },\n * templates: [\n * {\n * name: 'My RP Format',\n * description: 'Custom formatting with specific syntax',\n * systemPrompt: '[FORMATTING INSTRUCTIONS]...',\n * tags: ['custom'],\n * },\n * ],\n * });\n * ```\n */\nexport function createRoleplayTemplatePlugin(\n options: CreateRoleplayTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const { metadata, templates, initialize, enableLogging = false } = options;\n\n // Normalize templates to array\n const templateArray = Array.isArray(templates) ? templates : [templates];\n\n // Validate templates\n if (templateArray.length === 0) {\n throw new Error('At least one template is required');\n }\n\n for (const template of templateArray) {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error(`Template \"${template.name}\" requires a systemPrompt`);\n }\n }\n\n // Create the plugin\n const plugin: RoleplayTemplatePlugin = {\n metadata: {\n ...metadata,\n // Ensure tags from templates are included in metadata if not already set\n tags: metadata.tags ?? Array.from(\n new Set(templateArray.flatMap(t => t.tags ?? []))\n ),\n },\n templates: templateArray,\n };\n\n // Add initialize function with optional logging\n if (initialize || enableLogging) {\n plugin.initialize = async () => {\n if (enableLogging) {\n const logger = createPluginLogger(metadata.templateId);\n logger.debug('Roleplay template plugin loaded', {\n context: 'init',\n templateId: metadata.templateId,\n displayName: metadata.displayName,\n templateCount: templateArray.length,\n templateNames: templateArray.map(t => t.name),\n });\n }\n\n if (initialize) {\n await initialize();\n }\n };\n }\n\n return plugin;\n}\n\n/**\n * Creates a simple roleplay template plugin with a single template.\n *\n * This is a convenience function for the common case of a plugin\n * that provides just one roleplay template.\n *\n * @param options - Simplified plugin configuration\n * @returns A valid RoleplayTemplatePlugin instance\n *\n * @example\n * ```typescript\n * import { createSingleTemplatePlugin } from '@quilltap/plugin-utils';\n *\n * export const plugin = createSingleTemplatePlugin({\n * templateId: 'quilltap-rp',\n * displayName: 'Quilltap RP',\n * description: 'Custom formatting with [actions], {thoughts}, and // OOC',\n * systemPrompt: `[FORMATTING INSTRUCTIONS]\n * 1. DIALOGUE: Write as bare text without quotes\n * 2. ACTIONS: Use [square brackets]\n * 3. THOUGHTS: Use {curly braces}\n * 4. OOC: Use // prefix`,\n * tags: ['quilltap', 'custom'],\n * });\n * ```\n */\nexport function createSingleTemplatePlugin(\n options: CreateSingleTemplatePluginOptions\n): RoleplayTemplatePlugin {\n const {\n templateId,\n displayName,\n description,\n systemPrompt,\n author,\n tags,\n version,\n initialize,\n enableLogging,\n } = options;\n\n return createRoleplayTemplatePlugin({\n metadata: {\n templateId,\n displayName,\n description,\n author,\n tags,\n version,\n },\n templates: {\n name: displayName,\n description,\n systemPrompt,\n tags,\n },\n initialize,\n enableLogging,\n });\n}\n\n// ============================================================================\n// VALIDATION UTILITIES\n// ============================================================================\n\n/**\n * Validates a roleplay template configuration\n *\n * @param template - The template configuration to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateTemplateConfig(template: RoleplayTemplateConfig): boolean {\n if (!template.name || template.name.trim() === '') {\n throw new Error('Template name is required');\n }\n\n if (template.name.length > 100) {\n throw new Error('Template name must be 100 characters or less');\n }\n\n if (!template.systemPrompt || template.systemPrompt.trim() === '') {\n throw new Error('Template systemPrompt is required');\n }\n\n if (template.description && template.description.length > 500) {\n throw new Error('Template description must be 500 characters or less');\n }\n\n if (template.tags) {\n if (!Array.isArray(template.tags)) {\n throw new Error('Template tags must be an array');\n }\n for (const tag of template.tags) {\n if (typeof tag !== 'string') {\n throw new Error('All tags must be strings');\n }\n }\n }\n\n return true;\n}\n\n/**\n * Validates a complete roleplay template plugin\n *\n * @param plugin - The plugin to validate\n * @returns True if valid, throws Error if invalid\n */\nexport function validateRoleplayTemplatePlugin(plugin: RoleplayTemplatePlugin): boolean {\n // Validate metadata\n if (!plugin.metadata) {\n throw new Error('Plugin metadata is required');\n }\n\n if (!plugin.metadata.templateId || plugin.metadata.templateId.trim() === '') {\n throw new Error('Plugin metadata.templateId is required');\n }\n\n if (!/^[a-z0-9-]+$/.test(plugin.metadata.templateId)) {\n throw new Error('Plugin templateId must be lowercase alphanumeric with hyphens only');\n }\n\n if (!plugin.metadata.displayName || plugin.metadata.displayName.trim() === '') {\n throw new Error('Plugin metadata.displayName is required');\n }\n\n // Validate templates\n if (!plugin.templates || !Array.isArray(plugin.templates) || plugin.templates.length === 0) {\n throw new Error('Plugin must have at least one template');\n }\n\n for (const template of plugin.templates) {\n validateTemplateConfig(template);\n }\n\n return true;\n}\n","/**\n * @quilltap/plugin-utils\n *\n * Utility functions for Quilltap plugin development.\n *\n * This package provides runtime utilities that complement the type definitions\n * in @quilltap/plugin-types. It includes:\n *\n * - **Tool Parsing**: Parse tool calls from any LLM provider's response format\n * - **Tool Conversion**: Convert between OpenAI, Anthropic, and Google tool formats\n * - **Logger Bridge**: Logging that integrates with Quilltap's core or runs standalone\n *\n * @packageDocumentation\n * @module @quilltap/plugin-utils\n */\n\n// ============================================================================\n// Tool Utilities\n// ============================================================================\n\nexport {\n // Parsers\n parseToolCalls,\n parseOpenAIToolCalls,\n parseAnthropicToolCalls,\n parseGoogleToolCalls,\n detectToolCallFormat,\n hasToolCalls,\n\n // Converters\n convertToAnthropicFormat,\n convertToGoogleFormat,\n convertFromAnthropicFormat,\n convertFromGoogleFormat,\n convertToolTo,\n convertToolsTo,\n applyDescriptionLimit,\n // Backward-compatible aliases\n convertOpenAIToAnthropicFormat,\n convertOpenAIToGoogleFormat,\n} from './tools';\n\nexport type {\n // Tool types (re-exported from plugin-types)\n OpenAIToolDefinition,\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n ToolCall,\n ToolCallRequest,\n ToolResult,\n ToolFormatOptions,\n\n // Utility types\n ToolCallFormat,\n ToolConvertTarget,\n} from './tools';\n\n// ============================================================================\n// Logging Utilities\n// ============================================================================\n\nexport {\n // Plugin logger factory\n createPluginLogger,\n hasCoreLogger,\n getLogLevelFromEnv,\n\n // Re-exported from plugin-types\n createConsoleLogger,\n createNoopLogger,\n\n // Internal APIs for Quilltap core\n __injectCoreLoggerFactory,\n __clearCoreLoggerFactory,\n} from './logging';\n\nexport type {\n // Logger types\n PluginLoggerWithChild,\n PluginLogger,\n LogContext,\n LogLevel,\n} from './logging';\n\n// ============================================================================\n// Provider Base Classes\n// ============================================================================\n\nexport {\n OpenAICompatibleProvider,\n} from './providers';\n\nexport type {\n OpenAICompatibleProviderConfig,\n} from './providers';\n\n// ============================================================================\n// Roleplay Template Utilities\n// ============================================================================\n\nexport {\n // Builder functions\n createRoleplayTemplatePlugin,\n createSingleTemplatePlugin,\n\n // Validation utilities\n validateTemplateConfig,\n validateRoleplayTemplatePlugin,\n} from './roleplay-templates';\n\nexport type {\n // Builder option types\n CreateRoleplayTemplatePluginOptions,\n CreateSingleTemplatePluginOptions,\n} from './roleplay-templates';\n\n// ============================================================================\n// Version\n// ============================================================================\n\n/**\n * Version of the plugin-utils package.\n * Can be used at runtime to check compatibility.\n */\nexport const PLUGIN_UTILS_VERSION = '1.2.0';\n"],"mappings":"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","names":[]}
|
package/dist/tools/index.js
CHANGED
|
@@ -59,10 +59,19 @@ function parseOpenAIToolCalls(response) {
|
|
|
59
59
|
for (const toolCall of toolCallsArray) {
|
|
60
60
|
const tc = toolCall;
|
|
61
61
|
if (tc.type === "function" && tc.function) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const argsStr = tc.function.arguments || "{}";
|
|
63
|
+
const trimmed = argsStr.trim();
|
|
64
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
toolCalls.push({
|
|
69
|
+
name: tc.function.name,
|
|
70
|
+
arguments: JSON.parse(argsStr)
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
}
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/index.ts","../../src/tools/parsers.ts","../../src/tools/converters.ts"],"sourcesContent":["/**\n * Tool Utilities\n *\n * Exports all tool-related utilities for parsing and converting\n * tool calls between different LLM provider formats.\n *\n * @module @quilltap/plugin-utils/tools\n */\n\n// Re-export types\nexport type {\n OpenAIToolDefinition,\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n ToolCall,\n ToolCallRequest,\n ToolResult,\n ToolFormatOptions,\n} from './types';\n\n// Export parsers\nexport {\n parseToolCalls,\n parseOpenAIToolCalls,\n parseAnthropicToolCalls,\n parseGoogleToolCalls,\n detectToolCallFormat,\n hasToolCalls,\n} from './parsers';\n\nexport type { ToolCallFormat } from './parsers';\n\n// Export converters\nexport {\n convertToAnthropicFormat,\n convertToGoogleFormat,\n convertFromAnthropicFormat,\n convertFromGoogleFormat,\n convertToolTo,\n convertToolsTo,\n applyDescriptionLimit,\n // Backward-compatible aliases\n convertOpenAIToAnthropicFormat,\n convertOpenAIToGoogleFormat,\n} from './converters';\n\nexport type { ToolConvertTarget } from './converters';\n","/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(tc.function.arguments || '{}'),\n });\n }\n }\n }\n } catch (error) {\n // Log error but don't throw - return empty array\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/tools/index.ts","../../src/tools/parsers.ts","../../src/tools/converters.ts"],"sourcesContent":["/**\n * Tool Utilities\n *\n * Exports all tool-related utilities for parsing and converting\n * tool calls between different LLM provider formats.\n *\n * @module @quilltap/plugin-utils/tools\n */\n\n// Re-export types\nexport type {\n OpenAIToolDefinition,\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n ToolCall,\n ToolCallRequest,\n ToolResult,\n ToolFormatOptions,\n} from './types';\n\n// Export parsers\nexport {\n parseToolCalls,\n parseOpenAIToolCalls,\n parseAnthropicToolCalls,\n parseGoogleToolCalls,\n detectToolCallFormat,\n hasToolCalls,\n} from './parsers';\n\nexport type { ToolCallFormat } from './parsers';\n\n// Export converters\nexport {\n convertToAnthropicFormat,\n convertToGoogleFormat,\n convertFromAnthropicFormat,\n convertFromGoogleFormat,\n convertToolTo,\n convertToolsTo,\n applyDescriptionLimit,\n // Backward-compatible aliases\n convertOpenAIToAnthropicFormat,\n convertOpenAIToGoogleFormat,\n} from './converters';\n\nexport type { ToolConvertTarget } from './converters';\n","/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n const argsStr = tc.function.arguments || '{}';\n\n // During streaming, arguments may be incomplete JSON.\n // Check if the string looks like complete JSON before parsing.\n // Skip incomplete tool calls - they'll be complete in the final response.\n const trimmed = argsStr.trim();\n if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {\n // Incomplete JSON - skip this tool call for now\n continue;\n }\n\n try {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(argsStr),\n });\n } catch {\n // JSON parse failed (e.g., incomplete JSON during streaming)\n // This is expected during streaming - skip silently\n continue;\n }\n }\n }\n }\n } catch (error) {\n // Log error for unexpected parsing failures (not JSON parse errors)\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\n"],"mappings":"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","names":[]}
|
package/dist/tools/index.mjs
CHANGED
|
@@ -19,10 +19,19 @@ function parseOpenAIToolCalls(response) {
|
|
|
19
19
|
for (const toolCall of toolCallsArray) {
|
|
20
20
|
const tc = toolCall;
|
|
21
21
|
if (tc.type === "function" && tc.function) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const argsStr = tc.function.arguments || "{}";
|
|
23
|
+
const trimmed = argsStr.trim();
|
|
24
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
toolCalls.push({
|
|
29
|
+
name: tc.function.name,
|
|
30
|
+
arguments: JSON.parse(argsStr)
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
}
|
package/dist/tools/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/parsers.ts","../../src/tools/converters.ts"],"sourcesContent":["/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(tc.function.arguments || '{}'),\n });\n }\n }\n }\n } catch (error) {\n // Log error but don't throw - return empty array\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/tools/parsers.ts","../../src/tools/converters.ts"],"sourcesContent":["/**\n * Tool Call Parsers\n *\n * Provider-specific parsers for extracting tool calls from LLM responses.\n * Each parser converts from a provider's native format to the standardized\n * ToolCallRequest format.\n *\n * @module @quilltap/plugin-utils/tools/parsers\n */\n\nimport type { ToolCallRequest } from '@quilltap/plugin-types';\n\n/**\n * Supported tool call response formats\n */\nexport type ToolCallFormat = 'openai' | 'anthropic' | 'google' | 'auto';\n\n/**\n * Parse OpenAI format tool calls from LLM response\n *\n * Extracts tool calls from OpenAI/Grok API responses which return\n * tool_calls in the message object.\n *\n * Expected response structures:\n * - `response.tool_calls` (direct)\n * - `response.choices[0].message.tool_calls` (nested)\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await openai.chat.completions.create({...});\n * const toolCalls = parseOpenAIToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseOpenAIToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n // Handle direct tool_calls array (snake_case)\n let toolCallsArray = resp?.tool_calls as unknown[] | undefined;\n\n // Handle direct toolCalls array (camelCase - some SDKs use this)\n if (!toolCallsArray) {\n toolCallsArray = (resp as Record<string, unknown>)?.toolCalls as unknown[] | undefined;\n }\n\n // Check nested structure from non-streaming responses: choices[0].message.tool_calls\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ message?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls;\n }\n\n // Check nested structure from streaming responses: choices[0].delta.toolCalls\n // OpenRouter SDK uses camelCase and puts tool calls in delta for streaming\n if (!toolCallsArray) {\n const choices = resp?.choices as\n | Array<{ delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] } }>\n | undefined;\n toolCallsArray = choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls;\n }\n\n if (toolCallsArray && Array.isArray(toolCallsArray) && toolCallsArray.length > 0) {\n for (const toolCall of toolCallsArray) {\n const tc = toolCall as {\n type?: string;\n function?: { name: string; arguments: string };\n };\n\n if (tc.type === 'function' && tc.function) {\n const argsStr = tc.function.arguments || '{}';\n\n // During streaming, arguments may be incomplete JSON.\n // Check if the string looks like complete JSON before parsing.\n // Skip incomplete tool calls - they'll be complete in the final response.\n const trimmed = argsStr.trim();\n if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {\n // Incomplete JSON - skip this tool call for now\n continue;\n }\n\n try {\n toolCalls.push({\n name: tc.function.name,\n arguments: JSON.parse(argsStr),\n });\n } catch {\n // JSON parse failed (e.g., incomplete JSON during streaming)\n // This is expected during streaming - skip silently\n continue;\n }\n }\n }\n }\n } catch (error) {\n // Log error for unexpected parsing failures (not JSON parse errors)\n console.error('[plugin-utils] Error parsing OpenAI tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Anthropic format tool calls from LLM response\n *\n * Extracts tool calls from Anthropic API responses which return\n * tool_use blocks in the content array.\n *\n * Expected response structure:\n * - `response.content` array with `{ type: 'tool_use', name, input }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await anthropic.messages.create({...});\n * const toolCalls = parseAnthropicToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseAnthropicToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n\n if (!resp?.content || !Array.isArray(resp.content)) {\n return toolCalls;\n }\n\n for (const block of resp.content) {\n const b = block as { type?: string; name?: string; input?: Record<string, unknown> };\n\n if (b.type === 'tool_use' && b.name) {\n toolCalls.push({\n name: b.name,\n arguments: b.input || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Anthropic tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Parse Google Gemini format tool calls from LLM response\n *\n * Extracts tool calls from Google Gemini API responses which return\n * functionCall objects in the parts array.\n *\n * Expected response structure:\n * - `response.candidates[0].content.parts` array with `{ functionCall: { name, args } }`\n *\n * @param response - The raw response from provider API\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * const response = await gemini.generateContent({...});\n * const toolCalls = parseGoogleToolCalls(response);\n * // Returns: [{ name: 'search_web', arguments: { query: 'hello' } }]\n * ```\n */\nexport function parseGoogleToolCalls(response: unknown): ToolCallRequest[] {\n const toolCalls: ToolCallRequest[] = [];\n\n try {\n const resp = response as Record<string, unknown>;\n const candidates = resp?.candidates as\n | Array<{ content?: { parts?: unknown[] } }>\n | undefined;\n const parts = candidates?.[0]?.content?.parts;\n\n if (!parts || !Array.isArray(parts)) {\n return toolCalls;\n }\n\n for (const part of parts) {\n const p = part as {\n functionCall?: { name: string; args?: Record<string, unknown> };\n };\n\n if (p.functionCall) {\n toolCalls.push({\n name: p.functionCall.name,\n arguments: p.functionCall.args || {},\n });\n }\n }\n } catch (error) {\n console.error('[plugin-utils] Error parsing Google tool calls:', error);\n }\n\n return toolCalls;\n}\n\n/**\n * Detect the format of a tool call response\n *\n * Analyzes the response structure to determine which provider format it uses.\n *\n * @param response - The raw response from a provider API\n * @returns The detected format, or null if unrecognized\n */\nexport function detectToolCallFormat(response: unknown): ToolCallFormat | null {\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const resp = response as Record<string, unknown>;\n\n // OpenAI format: has tool_calls/toolCalls directly or in choices[0].message or choices[0].delta\n if (resp.tool_calls && Array.isArray(resp.tool_calls)) {\n return 'openai';\n }\n if (resp.toolCalls && Array.isArray(resp.toolCalls)) {\n return 'openai';\n }\n\n const choices = resp.choices as Array<{\n message?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n delta?: { tool_calls?: unknown[]; toolCalls?: unknown[] };\n }> | undefined;\n if (choices?.[0]?.message?.tool_calls || choices?.[0]?.message?.toolCalls) {\n return 'openai';\n }\n // Check delta for streaming responses (OpenRouter SDK uses this)\n if (choices?.[0]?.delta?.tool_calls || choices?.[0]?.delta?.toolCalls) {\n return 'openai';\n }\n\n // Anthropic format: has content array with tool_use type\n if (resp.content && Array.isArray(resp.content)) {\n const hasToolUse = (resp.content as Array<{ type?: string }>).some(\n (block) => block.type === 'tool_use'\n );\n if (hasToolUse) {\n return 'anthropic';\n }\n }\n\n // Google format: has candidates[0].content.parts with functionCall\n const candidates = resp.candidates as\n | Array<{ content?: { parts?: Array<{ functionCall?: unknown }> } }>\n | undefined;\n if (candidates?.[0]?.content?.parts) {\n const hasFunctionCall = candidates[0].content.parts.some((part) => part.functionCall);\n if (hasFunctionCall) {\n return 'google';\n }\n }\n\n return null;\n}\n\n/**\n * Parse tool calls with auto-detection or explicit format\n *\n * A unified parser that can either auto-detect the response format\n * or use a specified format. This is useful when you're not sure\n * which provider's response you're handling.\n *\n * @param response - The raw response from a provider API\n * @param format - The format to use: 'openai', 'anthropic', 'google', or 'auto'\n * @returns Array of parsed tool call requests\n *\n * @example\n * ```typescript\n * // Auto-detect format\n * const toolCalls = parseToolCalls(response, 'auto');\n *\n * // Or specify format explicitly\n * const toolCalls = parseToolCalls(response, 'openai');\n * ```\n */\nexport function parseToolCalls(\n response: unknown,\n format: ToolCallFormat = 'auto'\n): ToolCallRequest[] {\n let actualFormat: ToolCallFormat | null = format;\n\n if (format === 'auto') {\n actualFormat = detectToolCallFormat(response);\n if (!actualFormat) {\n return [];\n }\n }\n\n switch (actualFormat) {\n case 'openai':\n return parseOpenAIToolCalls(response);\n case 'anthropic':\n return parseAnthropicToolCalls(response);\n case 'google':\n return parseGoogleToolCalls(response);\n default:\n return [];\n }\n}\n\n/**\n * Check if a response contains tool calls\n *\n * Quick check to determine if a response has any tool calls\n * without fully parsing them.\n *\n * @param response - The raw response from a provider API\n * @returns True if the response contains tool calls\n */\nexport function hasToolCalls(response: unknown): boolean {\n const format = detectToolCallFormat(response);\n return format !== null;\n}\n","/**\n * Tool Format Converters\n *\n * Utilities for converting between different provider tool formats.\n * The universal format (OpenAI-style) serves as the baseline for all conversions.\n *\n * @module @quilltap/plugin-utils/tools/converters\n */\n\nimport type {\n UniversalTool,\n AnthropicToolDefinition,\n GoogleToolDefinition,\n OpenAIToolDefinition,\n} from '@quilltap/plugin-types';\n\n/**\n * Convert OpenAI/Universal format tool to Anthropic format\n *\n * Anthropic uses a tool_use format with:\n * - name: string\n * - description: string\n * - input_schema: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Anthropic's tool_use\n *\n * @example\n * ```typescript\n * const anthropicTool = convertToAnthropicFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // input_schema: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToAnthropicFormat(tool: UniversalTool | OpenAIToolDefinition): AnthropicToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n input_schema: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert OpenAI/Universal format tool to Google Gemini format\n *\n * Google uses a function calling format with:\n * - name: string\n * - description: string\n * - parameters: JSON schema object\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @returns Tool formatted for Google's functionCall\n *\n * @example\n * ```typescript\n * const googleTool = convertToGoogleFormat(universalTool);\n * // Returns: {\n * // name: 'search_web',\n * // description: 'Search the web',\n * // parameters: {\n * // type: 'object',\n * // properties: { query: { type: 'string' } },\n * // required: ['query']\n * // }\n * // }\n * ```\n */\nexport function convertToGoogleFormat(tool: UniversalTool | OpenAIToolDefinition): GoogleToolDefinition {\n return {\n name: tool.function.name,\n description: tool.function.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.function.parameters?.properties ?? {},\n required: tool.function.parameters?.required ?? [],\n },\n };\n}\n\n/**\n * Convert Anthropic format tool to Universal/OpenAI format\n *\n * @param tool - Anthropic format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromAnthropicFormat(tool: AnthropicToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description ?? '',\n parameters: {\n type: 'object',\n properties: tool.input_schema.properties,\n required: tool.input_schema.required ?? [],\n },\n },\n };\n}\n\n/**\n * Convert Google format tool to Universal/OpenAI format\n *\n * @param tool - Google format tool\n * @returns Tool in universal OpenAI format\n */\nexport function convertFromGoogleFormat(tool: GoogleToolDefinition): UniversalTool {\n return {\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: {\n type: 'object',\n properties: tool.parameters.properties,\n required: tool.parameters.required,\n },\n },\n };\n}\n\n/**\n * Apply prompt/description length limit to a tool\n *\n * Modifies a tool's description if it exceeds maxBytes, appending a warning\n * that the description was truncated. This is useful for providers with\n * strict token limits.\n *\n * @param tool - Tool object (any format) with a description property\n * @param maxBytes - Maximum bytes allowed for description (including warning)\n * @returns Modified tool with truncated description if needed\n *\n * @example\n * ```typescript\n * const limitedTool = applyDescriptionLimit(tool, 500);\n * // If description > 500 bytes, truncates and adds warning\n * ```\n */\nexport function applyDescriptionLimit<T extends { description: string }>(\n tool: T,\n maxBytes: number\n): T {\n if (!tool || !tool.description) {\n return tool;\n }\n\n const warningText = ' [Note: description truncated due to length limit]';\n const maxDescBytes = maxBytes - Buffer.byteLength(warningText);\n\n if (maxDescBytes <= 0) {\n console.warn('[plugin-utils] Length limit too small for warning text:', maxBytes);\n return tool;\n }\n\n const descBytes = Buffer.byteLength(tool.description);\n\n if (descBytes > maxBytes) {\n // Truncate description to fit within the byte limit\n let truncated = tool.description;\n while (Buffer.byteLength(truncated) > maxDescBytes && truncated.length > 0) {\n truncated = truncated.slice(0, -1);\n }\n\n return {\n ...tool,\n description: truncated + warningText,\n };\n }\n\n return tool;\n}\n\n/**\n * Target format for tool conversion\n */\nexport type ToolConvertTarget = 'openai' | 'anthropic' | 'google';\n\n/**\n * Convert a universal tool to a specific provider format\n *\n * @param tool - Universal tool or OpenAI tool definition\n * @param target - Target provider format\n * @returns Tool in the target format\n */\nexport function convertToolTo(\n tool: UniversalTool | OpenAIToolDefinition,\n target: ToolConvertTarget\n): UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition {\n switch (target) {\n case 'anthropic':\n return convertToAnthropicFormat(tool);\n case 'google':\n return convertToGoogleFormat(tool);\n case 'openai':\n default:\n return tool;\n }\n}\n\n/**\n * Convert multiple tools to a specific provider format\n *\n * @param tools - Array of universal tools or OpenAI tool definitions\n * @param target - Target provider format\n * @returns Array of tools in the target format\n */\nexport function convertToolsTo(\n tools: Array<UniversalTool | OpenAIToolDefinition>,\n target: ToolConvertTarget\n): Array<UniversalTool | OpenAIToolDefinition | AnthropicToolDefinition | GoogleToolDefinition> {\n return tools.map((tool) => convertToolTo(tool, target));\n}\n\n// ============================================================================\n// Backward-compatible aliases\n// ============================================================================\n\n/**\n * Alias for convertToAnthropicFormat\n * @deprecated Use convertToAnthropicFormat instead\n */\nexport const convertOpenAIToAnthropicFormat = convertToAnthropicFormat;\n\n/**\n * Alias for convertToGoogleFormat\n * @deprecated Use convertToGoogleFormat instead\n */\nexport const convertOpenAIToGoogleFormat = convertToGoogleFormat;\n"],"mappings":"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","names":[]}
|