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.
- package/dist/config/llm-presets.json +40 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -1
- package/dist/llm/clients/AnthropicClientAdapter.js +28 -9
- package/dist/llm/clients/GeminiClientAdapter.js +30 -8
- package/dist/llm/clients/LlamaCppClientAdapter.js +30 -12
- package/dist/llm/clients/MistralClientAdapter.d.ts +94 -0
- package/dist/llm/clients/MistralClientAdapter.js +239 -0
- package/dist/llm/clients/OpenAIClientAdapter.js +30 -13
- package/dist/llm/clients/OpenRouterClientAdapter.d.ts +103 -0
- package/dist/llm/clients/OpenRouterClientAdapter.js +285 -0
- package/dist/llm/config.js +72 -5
- package/dist/llm/services/SettingsManager.js +5 -0
- package/dist/llm/types.d.ts +79 -0
- package/dist/shared/adapters/systemMessageUtils.d.ts +162 -0
- package/dist/shared/adapters/systemMessageUtils.js +172 -0
- package/package.json +2 -1
- package/src/config/llm-presets.json +40 -0
|
@@ -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;
|
package/dist/llm/config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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}:`, {
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -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
|