genai-lite 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ import type { LLMResponse, LLMFailureResponse } from "../types";
2
+ import type { ILLMClientAdapter, InternalLLMChatRequest } from "./types";
3
+ /**
4
+ * Configuration options for OpenRouterClientAdapter
5
+ */
6
+ export interface OpenRouterClientConfig {
7
+ /** Base URL of the OpenRouter API (default: https://openrouter.ai/api/v1) */
8
+ baseURL?: string;
9
+ /** Your app's URL for rankings attribution (optional) */
10
+ httpReferer?: string;
11
+ /** Your app's display name for rankings (optional) */
12
+ siteTitle?: string;
13
+ }
14
+ /**
15
+ * Client adapter for OpenRouter API integration
16
+ *
17
+ * OpenRouter is an API gateway that provides unified access to 100+ LLM models
18
+ * from various providers (OpenAI, Anthropic, Google, Meta, Mistral, etc.)
19
+ * through an OpenAI-compatible API.
20
+ *
21
+ * Key features:
22
+ * - Uses OpenAI-compatible API format
23
+ * - Single API key for all models
24
+ * - Model IDs use provider/model format (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
25
+ * - Optional provider routing for controlling which underlying providers serve requests
26
+ * - App attribution via HTTP-Referer and X-Title headers
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // Create adapter
31
+ * const adapter = new OpenRouterClientAdapter({
32
+ * httpReferer: 'https://myapp.com',
33
+ * siteTitle: 'My App'
34
+ * });
35
+ *
36
+ * // Use via LLMService
37
+ * const response = await service.sendMessage({
38
+ * providerId: 'openrouter',
39
+ * modelId: 'google/gemma-3-27b-it:free',
40
+ * messages: [{ role: 'user', content: 'Hello!' }]
41
+ * });
42
+ * ```
43
+ */
44
+ export declare class OpenRouterClientAdapter implements ILLMClientAdapter {
45
+ private baseURL;
46
+ private httpReferer?;
47
+ private siteTitle?;
48
+ /**
49
+ * Creates a new OpenRouter client adapter
50
+ *
51
+ * @param config Optional configuration for the adapter
52
+ */
53
+ constructor(config?: OpenRouterClientConfig);
54
+ /**
55
+ * Sends a chat message to OpenRouter API
56
+ *
57
+ * @param request - The internal LLM request with applied settings
58
+ * @param apiKey - The OpenRouter API key
59
+ * @returns Promise resolving to success or failure response
60
+ */
61
+ sendMessage(request: InternalLLMChatRequest, apiKey: string): Promise<LLMResponse | LLMFailureResponse>;
62
+ /**
63
+ * Validates OpenRouter API key format
64
+ *
65
+ * OpenRouter API keys typically start with 'sk-or-' and have significant length.
66
+ *
67
+ * @param apiKey - The API key to validate
68
+ * @returns True if the key format appears valid
69
+ */
70
+ validateApiKey(apiKey: string): boolean;
71
+ /**
72
+ * Gets adapter information
73
+ */
74
+ getAdapterInfo(): {
75
+ providerId: "openrouter";
76
+ name: string;
77
+ version: string;
78
+ baseURL: string;
79
+ };
80
+ /**
81
+ * Formats messages for OpenAI-compatible API
82
+ *
83
+ * @param request - The internal LLM request
84
+ * @returns Formatted messages array
85
+ */
86
+ private formatMessages;
87
+ /**
88
+ * Creates a standardized success response from OpenRouter's response
89
+ *
90
+ * @param completion - Raw OpenAI-compatible completion response
91
+ * @param request - Original request for context
92
+ * @returns Standardized LLM response
93
+ */
94
+ private createSuccessResponse;
95
+ /**
96
+ * Creates a standardized error response from an error
97
+ *
98
+ * @param error - The error that occurred
99
+ * @param request - Original request for context
100
+ * @returns Standardized LLM failure response
101
+ */
102
+ private createErrorResponse;
103
+ }
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ // AI Summary: Client adapter for OpenRouter API gateway using OpenAI-compatible API.
3
+ // Provides unified access to 100+ LLM models from various providers through a single API.
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.OpenRouterClientAdapter = void 0;
9
+ const openai_1 = __importDefault(require("openai"));
10
+ const types_1 = require("./types");
11
+ const errorUtils_1 = require("../../shared/adapters/errorUtils");
12
+ const systemMessageUtils_1 = require("../../shared/adapters/systemMessageUtils");
13
+ const defaultLogger_1 = require("../../logging/defaultLogger");
14
+ const logger = (0, defaultLogger_1.createDefaultLogger)();
15
+ /**
16
+ * Client adapter for OpenRouter API integration
17
+ *
18
+ * OpenRouter is an API gateway that provides unified access to 100+ LLM models
19
+ * from various providers (OpenAI, Anthropic, Google, Meta, Mistral, etc.)
20
+ * through an OpenAI-compatible API.
21
+ *
22
+ * Key features:
23
+ * - Uses OpenAI-compatible API format
24
+ * - Single API key for all models
25
+ * - Model IDs use provider/model format (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
26
+ * - Optional provider routing for controlling which underlying providers serve requests
27
+ * - App attribution via HTTP-Referer and X-Title headers
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // Create adapter
32
+ * const adapter = new OpenRouterClientAdapter({
33
+ * httpReferer: 'https://myapp.com',
34
+ * siteTitle: 'My App'
35
+ * });
36
+ *
37
+ * // Use via LLMService
38
+ * const response = await service.sendMessage({
39
+ * providerId: 'openrouter',
40
+ * modelId: 'google/gemma-3-27b-it:free',
41
+ * messages: [{ role: 'user', content: 'Hello!' }]
42
+ * });
43
+ * ```
44
+ */
45
+ class OpenRouterClientAdapter {
46
+ /**
47
+ * Creates a new OpenRouter client adapter
48
+ *
49
+ * @param config Optional configuration for the adapter
50
+ */
51
+ constructor(config) {
52
+ this.baseURL = config?.baseURL || 'https://openrouter.ai/api/v1';
53
+ this.httpReferer = config?.httpReferer || process.env.OPENROUTER_HTTP_REFERER;
54
+ this.siteTitle = config?.siteTitle || process.env.OPENROUTER_SITE_TITLE;
55
+ }
56
+ /**
57
+ * Sends a chat message to OpenRouter API
58
+ *
59
+ * @param request - The internal LLM request with applied settings
60
+ * @param apiKey - The OpenRouter API key
61
+ * @returns Promise resolving to success or failure response
62
+ */
63
+ async sendMessage(request, apiKey) {
64
+ try {
65
+ // Initialize OpenAI client with OpenRouter base URL and custom headers
66
+ const openai = new openai_1.default({
67
+ apiKey,
68
+ baseURL: this.baseURL,
69
+ defaultHeaders: {
70
+ ...(this.httpReferer && { 'HTTP-Referer': this.httpReferer }),
71
+ ...(this.siteTitle && { 'X-Title': this.siteTitle }),
72
+ },
73
+ });
74
+ // Format messages for OpenAI-compatible API
75
+ const messages = this.formatMessages(request);
76
+ // Prepare API call parameters
77
+ const completionParams = {
78
+ model: request.modelId,
79
+ messages: messages,
80
+ temperature: request.settings.temperature,
81
+ max_tokens: request.settings.maxTokens,
82
+ top_p: request.settings.topP,
83
+ ...(request.settings.stopSequences.length > 0 && {
84
+ stop: request.settings.stopSequences,
85
+ }),
86
+ ...(request.settings.frequencyPenalty !== 0 && {
87
+ frequency_penalty: request.settings.frequencyPenalty,
88
+ }),
89
+ ...(request.settings.presencePenalty !== 0 && {
90
+ presence_penalty: request.settings.presencePenalty,
91
+ }),
92
+ };
93
+ // Add OpenRouter-specific provider routing if configured
94
+ const providerSettings = request.settings.openRouterProvider;
95
+ if (providerSettings) {
96
+ const provider = {};
97
+ if (providerSettings.order) {
98
+ provider.order = providerSettings.order;
99
+ }
100
+ if (providerSettings.ignore) {
101
+ provider.ignore = providerSettings.ignore;
102
+ }
103
+ if (providerSettings.allow) {
104
+ provider.allow = providerSettings.allow;
105
+ }
106
+ if (providerSettings.dataCollection) {
107
+ provider.data_collection = providerSettings.dataCollection;
108
+ }
109
+ if (providerSettings.requireParameters !== undefined) {
110
+ provider.require_parameters = providerSettings.requireParameters;
111
+ }
112
+ if (Object.keys(provider).length > 0) {
113
+ completionParams.provider = provider;
114
+ }
115
+ }
116
+ logger.debug(`OpenRouter API parameters:`, {
117
+ baseURL: this.baseURL,
118
+ model: completionParams.model,
119
+ temperature: completionParams.temperature,
120
+ max_tokens: completionParams.max_tokens,
121
+ top_p: completionParams.top_p,
122
+ hasProviderRouting: !!completionParams.provider,
123
+ });
124
+ logger.info(`Making OpenRouter API call for model: ${request.modelId}`);
125
+ // Make the API call
126
+ const completion = await openai.chat.completions.create(completionParams);
127
+ // Type guard to ensure we have a non-streaming response
128
+ if ('id' in completion && 'choices' in completion) {
129
+ logger.info(`OpenRouter API call successful, response ID: ${completion.id}`);
130
+ return this.createSuccessResponse(completion, request);
131
+ }
132
+ else {
133
+ throw new Error('Unexpected streaming response from OpenRouter');
134
+ }
135
+ }
136
+ catch (error) {
137
+ logger.error("OpenRouter API error:", error);
138
+ return this.createErrorResponse(error, request);
139
+ }
140
+ }
141
+ /**
142
+ * Validates OpenRouter API key format
143
+ *
144
+ * OpenRouter API keys typically start with 'sk-or-' and have significant length.
145
+ *
146
+ * @param apiKey - The API key to validate
147
+ * @returns True if the key format appears valid
148
+ */
149
+ validateApiKey(apiKey) {
150
+ // OpenRouter keys start with 'sk-or-' (may include version like 'sk-or-v1-')
151
+ return apiKey.startsWith('sk-or-') && apiKey.length >= 40;
152
+ }
153
+ /**
154
+ * Gets adapter information
155
+ */
156
+ getAdapterInfo() {
157
+ return {
158
+ providerId: "openrouter",
159
+ name: "OpenRouter Client Adapter",
160
+ version: "1.0.0",
161
+ baseURL: this.baseURL,
162
+ };
163
+ }
164
+ /**
165
+ * Formats messages for OpenAI-compatible API
166
+ *
167
+ * @param request - The internal LLM request
168
+ * @returns Formatted messages array
169
+ */
170
+ formatMessages(request) {
171
+ const messages = [];
172
+ const inlineSystemMessages = [];
173
+ // Check if model supports system messages (default true for most OpenRouter models)
174
+ const supportsSystem = request.settings.supportsSystemMessage !== false;
175
+ // Add conversation messages (collecting system messages separately)
176
+ for (const message of request.messages) {
177
+ if (message.role === "system") {
178
+ // Collect inline system messages
179
+ inlineSystemMessages.push(message.content);
180
+ }
181
+ else if (message.role === "user") {
182
+ messages.push({
183
+ role: "user",
184
+ content: message.content,
185
+ });
186
+ }
187
+ else if (message.role === "assistant") {
188
+ messages.push({
189
+ role: "assistant",
190
+ content: message.content,
191
+ });
192
+ }
193
+ }
194
+ // Use shared utility to collect and combine system content
195
+ const { combinedSystemContent, useNativeSystemMessage } = (0, systemMessageUtils_1.collectSystemContent)(request.systemMessage, inlineSystemMessages, supportsSystem);
196
+ if (combinedSystemContent) {
197
+ if (useNativeSystemMessage) {
198
+ // Model supports system messages - add as system role at the start
199
+ messages.unshift({
200
+ role: "system",
201
+ content: combinedSystemContent,
202
+ });
203
+ }
204
+ else {
205
+ // Model doesn't support system messages - prepend to first user message
206
+ const simpleMessages = messages.map((m) => ({
207
+ role: m.role,
208
+ content: m.content,
209
+ }));
210
+ const modifiedIndex = (0, systemMessageUtils_1.prependSystemToFirstUserMessage)(simpleMessages, combinedSystemContent, request.settings.systemMessageFallback);
211
+ if (modifiedIndex !== -1) {
212
+ messages[modifiedIndex].content = simpleMessages[modifiedIndex].content;
213
+ logger.debug(`Model ${request.modelId} doesn't support system messages - prepended to first user message`);
214
+ }
215
+ }
216
+ }
217
+ return messages;
218
+ }
219
+ /**
220
+ * Creates a standardized success response from OpenRouter's response
221
+ *
222
+ * @param completion - Raw OpenAI-compatible completion response
223
+ * @param request - Original request for context
224
+ * @returns Standardized LLM response
225
+ */
226
+ createSuccessResponse(completion, request) {
227
+ const choice = completion.choices[0];
228
+ if (!choice || !choice.message) {
229
+ throw new Error("No valid choices in OpenRouter completion response");
230
+ }
231
+ return {
232
+ id: completion.id,
233
+ provider: request.providerId,
234
+ model: completion.model || request.modelId,
235
+ created: completion.created,
236
+ choices: completion.choices.map((c) => ({
237
+ message: {
238
+ role: "assistant",
239
+ content: c.message.content || "",
240
+ },
241
+ finish_reason: c.finish_reason,
242
+ index: c.index,
243
+ })),
244
+ usage: completion.usage
245
+ ? {
246
+ prompt_tokens: completion.usage.prompt_tokens,
247
+ completion_tokens: completion.usage.completion_tokens,
248
+ total_tokens: completion.usage.total_tokens,
249
+ }
250
+ : undefined,
251
+ object: "chat.completion",
252
+ };
253
+ }
254
+ /**
255
+ * Creates a standardized error response from an error
256
+ *
257
+ * @param error - The error that occurred
258
+ * @param request - Original request for context
259
+ * @returns Standardized LLM failure response
260
+ */
261
+ createErrorResponse(error, request) {
262
+ // Use common error mapping
263
+ const mappedError = (0, errorUtils_1.getCommonMappedErrorDetails)(error);
264
+ // OpenRouter-specific error refinements
265
+ if (mappedError.status === 400) {
266
+ const errorMessage = (error?.message || '').toLowerCase();
267
+ if (errorMessage.includes('model') && (errorMessage.includes('not available') || errorMessage.includes('not found'))) {
268
+ mappedError.errorCode = types_1.ADAPTER_ERROR_CODES.MODEL_NOT_FOUND;
269
+ }
270
+ }
271
+ return {
272
+ provider: request.providerId,
273
+ model: request.modelId,
274
+ error: {
275
+ message: mappedError.errorMessage,
276
+ code: mappedError.errorCode,
277
+ type: mappedError.errorType,
278
+ ...(mappedError.status && { status: mappedError.status }),
279
+ providerError: error,
280
+ },
281
+ object: "error",
282
+ };
283
+ }
284
+ }
285
+ exports.OpenRouterClientAdapter = OpenRouterClientAdapter;
@@ -16,10 +16,10 @@ const OpenAIClientAdapter_1 = require("./clients/OpenAIClientAdapter");
16
16
  const AnthropicClientAdapter_1 = require("./clients/AnthropicClientAdapter");
17
17
  const GeminiClientAdapter_1 = require("./clients/GeminiClientAdapter");
18
18
  const LlamaCppClientAdapter_1 = require("./clients/LlamaCppClientAdapter");
19
+ const OpenRouterClientAdapter_1 = require("./clients/OpenRouterClientAdapter");
20
+ const MistralClientAdapter_1 = require("./clients/MistralClientAdapter");
19
21
  const defaultLogger_1 = require("../logging/defaultLogger");
20
22
  const logger = (0, defaultLogger_1.createDefaultLogger)();
21
- // Placeholder for future imports:
22
- // import { MistralClientAdapter } from './clients/MistralClientAdapter';
23
23
  /**
24
24
  * Mapping from provider IDs to their corresponding adapter constructor classes
25
25
  * This enables dynamic registration of client adapters in LLMServiceMain
@@ -29,7 +29,8 @@ exports.ADAPTER_CONSTRUCTORS = {
29
29
  anthropic: AnthropicClientAdapter_1.AnthropicClientAdapter,
30
30
  gemini: GeminiClientAdapter_1.GeminiClientAdapter,
31
31
  llamacpp: LlamaCppClientAdapter_1.LlamaCppClientAdapter,
32
- // 'mistral': MistralClientAdapter, // Uncomment and add when Mistral adapter is ready
32
+ openrouter: OpenRouterClientAdapter_1.OpenRouterClientAdapter,
33
+ mistral: MistralClientAdapter_1.MistralClientAdapter,
33
34
  };
34
35
  /**
35
36
  * Optional configuration objects for each adapter
@@ -45,8 +46,12 @@ exports.ADAPTER_CONFIGS = {
45
46
  llamacpp: {
46
47
  baseURL: process.env.LLAMACPP_API_BASE_URL || 'http://localhost:8080',
47
48
  },
48
- // 'gemini': { /* ... Gemini specific config ... */ },
49
- // 'mistral': { /* ... Mistral specific config ... */ },
49
+ openrouter: {
50
+ baseURL: process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1',
51
+ },
52
+ mistral: {
53
+ baseURL: process.env.MISTRAL_API_BASE_URL || undefined,
54
+ },
50
55
  };
51
56
  /**
52
57
  * Default settings applied to all LLM requests unless overridden
@@ -59,6 +64,11 @@ exports.DEFAULT_LLM_SETTINGS = {
59
64
  frequencyPenalty: 0.0,
60
65
  presencePenalty: 0.0,
61
66
  supportsSystemMessage: true,
67
+ systemMessageFallback: {
68
+ format: 'xml',
69
+ tagName: 'system',
70
+ separator: '\n\n---\n\n',
71
+ },
62
72
  user: undefined, // Will be filtered out when undefined
63
73
  geminiSafetySettings: [
64
74
  { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
@@ -77,6 +87,7 @@ exports.DEFAULT_LLM_SETTINGS = {
77
87
  tagName: 'thinking',
78
88
  enforce: false
79
89
  },
90
+ openRouterProvider: undefined, // Optional, only used with OpenRouter provider
80
91
  };
81
92
  /**
82
93
  * Per-provider default setting overrides
@@ -130,6 +141,11 @@ exports.SUPPORTED_PROVIDERS = [
130
141
  name: "llama.cpp",
131
142
  allowUnknownModels: true, // Users load arbitrary GGUF models with custom names
132
143
  },
144
+ {
145
+ id: "openrouter",
146
+ name: "OpenRouter",
147
+ allowUnknownModels: true, // OpenRouter provides 100+ models dynamically
148
+ },
133
149
  {
134
150
  id: "mock",
135
151
  name: "Mock Provider",
@@ -591,6 +607,7 @@ exports.SUPPORTED_MODELS = [
591
607
  },
592
608
  },
593
609
  // Google Gemma 3 Models (Open weights, free via Gemini API)
610
+ // Note: Gemma models don't support system instructions - system content is prepended to user message
594
611
  {
595
612
  id: "gemma-3-27b-it",
596
613
  name: "Gemma 3 27B",
@@ -602,6 +619,7 @@ exports.SUPPORTED_MODELS = [
602
619
  maxTokens: 8192,
603
620
  supportsImages: true,
604
621
  supportsPromptCache: false,
622
+ supportsSystemMessage: false,
605
623
  },
606
624
  // OpenAI Models - GPT-5 Series
607
625
  {
@@ -742,6 +760,30 @@ exports.SUPPORTED_MODELS = [
742
760
  cacheReadsPrice: 0.025,
743
761
  },
744
762
  // Mistral AI Models
763
+ {
764
+ id: "mistral-small-latest",
765
+ name: "Mistral Small",
766
+ providerId: "mistral",
767
+ contextWindow: 128000,
768
+ inputPrice: 0.1,
769
+ outputPrice: 0.3,
770
+ description: "Cost-effective model for general tasks",
771
+ maxTokens: 128000,
772
+ supportsImages: false,
773
+ supportsPromptCache: false,
774
+ },
775
+ {
776
+ id: "mistral-large-2512",
777
+ name: "Mistral Large 3",
778
+ providerId: "mistral",
779
+ contextWindow: 256000,
780
+ inputPrice: 0.5,
781
+ outputPrice: 1.5,
782
+ description: "Mistral's frontier model with 256K context",
783
+ maxTokens: 256000,
784
+ supportsImages: false,
785
+ supportsPromptCache: false,
786
+ },
745
787
  {
746
788
  id: "codestral-2501",
747
789
  name: "Codestral",
@@ -779,6 +821,31 @@ exports.SUPPORTED_MODELS = [
779
821
  supportsImages: false,
780
822
  supportsPromptCache: false,
781
823
  },
824
+ // OpenRouter Models (Free Tier)
825
+ {
826
+ id: "google/gemma-3-27b-it:free",
827
+ name: "Gemma 3 27B (Free)",
828
+ providerId: "openrouter",
829
+ contextWindow: 96000,
830
+ inputPrice: 0.0,
831
+ outputPrice: 0.0,
832
+ description: "Google's Gemma 3 27B instruction-tuned model via OpenRouter (free tier)",
833
+ maxTokens: 8192,
834
+ supportsImages: true,
835
+ supportsPromptCache: false,
836
+ },
837
+ {
838
+ id: "mistralai/mistral-small-3.1-24b-instruct:free",
839
+ name: "Mistral Small 3.1 24B (Free)",
840
+ providerId: "openrouter",
841
+ contextWindow: 96000,
842
+ inputPrice: 0.0,
843
+ outputPrice: 0.0,
844
+ description: "Mistral Small 3.1 24B instruction model via OpenRouter (free tier)",
845
+ maxTokens: 8192,
846
+ supportsImages: false,
847
+ supportsPromptCache: false,
848
+ },
782
849
  ];
783
850
  /**
784
851
  * Gets provider information by ID
@@ -32,6 +32,10 @@ class SettingsManager {
32
32
  user: requestSettings?.user ?? modelDefaults.user,
33
33
  supportsSystemMessage: requestSettings?.supportsSystemMessage ??
34
34
  modelDefaults.supportsSystemMessage,
35
+ systemMessageFallback: {
36
+ ...modelDefaults.systemMessageFallback,
37
+ ...requestSettings?.systemMessageFallback,
38
+ },
35
39
  geminiSafetySettings: requestSettings?.geminiSafetySettings ??
36
40
  modelDefaults.geminiSafetySettings,
37
41
  reasoning: {
@@ -42,6 +46,7 @@ class SettingsManager {
42
46
  ...modelDefaults.thinkingTagFallback,
43
47
  ...requestSettings?.thinkingTagFallback,
44
48
  },
49
+ openRouterProvider: requestSettings?.openRouterProvider ?? modelDefaults.openRouterProvider,
45
50
  };
46
51
  // Log the final settings for debugging
47
52
  this.logger.debug(`Merged settings for ${providerId}/${modelId}:`, {
@@ -83,6 +83,74 @@ export interface LLMThinkingTagFallbackSettings {
83
83
  */
84
84
  enforce?: boolean;
85
85
  }
86
+ /**
87
+ * Format options for prepending system content when model doesn't support system messages.
88
+ * - 'xml': Wrap in XML tags (default) - `<system>content</system>\n\n{user message}`
89
+ * - 'separator': Use a simple separator - `{content}\n\n---\n\n{user message}`
90
+ * - 'plain': Just prepend with double newline - `{content}\n\n{user message}`
91
+ */
92
+ export type SystemMessageFallbackFormat = 'xml' | 'separator' | 'plain';
93
+ /**
94
+ * Settings for handling system messages when the model doesn't support them natively.
95
+ * When a model has `supportsSystemMessage: false`, these settings control how
96
+ * system content is formatted when prepended to the first user message.
97
+ */
98
+ export interface SystemMessageFallbackSettings {
99
+ /**
100
+ * Format to use when prepending system content to user message.
101
+ * @default 'xml'
102
+ */
103
+ format?: SystemMessageFallbackFormat;
104
+ /**
105
+ * Tag name to use when format is 'xml'.
106
+ * @default 'system'
107
+ * @example tagName: 'instructions' produces `<instructions>content</instructions>`
108
+ */
109
+ tagName?: string;
110
+ /**
111
+ * Separator string to use when format is 'separator'.
112
+ * @default '---'
113
+ */
114
+ separator?: string;
115
+ }
116
+ /**
117
+ * OpenRouter-specific provider routing settings
118
+ *
119
+ * These settings allow controlling which underlying providers serve requests
120
+ * when using OpenRouter. All fields are optional - by default, OpenRouter
121
+ * automatically selects the best provider based on price, latency, and availability.
122
+ *
123
+ * @see https://openrouter.ai/docs/provider-routing
124
+ */
125
+ export interface OpenRouterProviderSettings {
126
+ /**
127
+ * Provider priority order. OpenRouter will try providers in this order.
128
+ * @example order: ["Together", "Fireworks", "Lepton"]
129
+ */
130
+ order?: string[];
131
+ /**
132
+ * Providers to exclude from serving this request.
133
+ * @example ignore: ["Azure", "OpenAI"]
134
+ */
135
+ ignore?: string[];
136
+ /**
137
+ * Providers to allow exclusively. If set, only these providers can serve the request.
138
+ * @example allow: ["Together", "Fireworks"]
139
+ */
140
+ allow?: string[];
141
+ /**
142
+ * Control whether providers can use your prompts for training.
143
+ * Set to 'deny' to opt out of data collection by providers.
144
+ * @default undefined (provider's default behavior)
145
+ */
146
+ dataCollection?: 'deny' | 'allow';
147
+ /**
148
+ * If true, only route to providers that support all parameters in your request.
149
+ * Useful when using provider-specific features.
150
+ * @default false
151
+ */
152
+ requireParameters?: boolean;
153
+ }
86
154
  /**
87
155
  * Configurable settings for LLM requests
88
156
  */
@@ -103,6 +171,11 @@ export interface LLMSettings {
103
171
  user?: string;
104
172
  /** Whether the LLM supports system message (almost all LLMs do nowadays) */
105
173
  supportsSystemMessage?: boolean;
174
+ /**
175
+ * Settings for handling system messages when the model doesn't support them.
176
+ * Controls how system content is formatted when prepended to user messages.
177
+ */
178
+ systemMessageFallback?: SystemMessageFallbackSettings;
106
179
  /** Gemini-specific safety settings for content filtering */
107
180
  geminiSafetySettings?: GeminiSafetySetting[];
108
181
  /** Universal reasoning/thinking configuration */
@@ -121,6 +194,12 @@ export interface LLMSettings {
121
194
  * The library only extracts them - it doesn't generate them automatically.
122
195
  */
123
196
  thinkingTagFallback?: LLMThinkingTagFallbackSettings;
197
+ /**
198
+ * OpenRouter-specific provider routing settings.
199
+ * Only used when providerId is 'openrouter'.
200
+ * @see OpenRouterProviderSettings
201
+ */
202
+ openRouterProvider?: OpenRouterProviderSettings;
124
203
  }
125
204
  /**
126
205
  * Request structure for chat completion