@realtimex/email-automator 2.11.0 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/src/routes/sdk.ts +3 -4
- package/api/src/routes/settings.ts +5 -1
- package/api/src/services/SDKService.ts +59 -40
- package/api/src/services/intelligence.ts +42 -16
- package/dist/api/src/routes/sdk.js +3 -4
- package/dist/api/src/routes/settings.js +5 -1
- package/dist/api/src/services/SDKService.js +42 -34
- package/dist/api/src/services/intelligence.js +38 -13
- package/dist/assets/index-DhiZIx49.js +106 -0
- package/dist/assets/index-nEg88GTA.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/assets/index-Bb7BPcc9.js +0 -106
- package/dist/assets/index-D6Bd2h8f.css +0 -1
package/api/src/routes/sdk.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Router, Request, Response } from 'express';
|
|
2
2
|
import { SDKService } from '../services/SDKService.js';
|
|
3
3
|
import { ProvidersResponse } from '@realtimex/sdk';
|
|
4
|
+
import { apiRateLimit } from '../middleware/rateLimit.js';
|
|
4
5
|
|
|
5
6
|
const router = Router();
|
|
6
7
|
|
|
@@ -8,7 +9,7 @@ const router = Router();
|
|
|
8
9
|
* GET /api/sdk/providers/chat
|
|
9
10
|
* Returns available chat providers and their models
|
|
10
11
|
*/
|
|
11
|
-
router.get('/providers/chat', async (req: Request, res: Response) => {
|
|
12
|
+
router.get('/providers/chat', apiRateLimit, async (req: Request, res: Response) => {
|
|
12
13
|
try {
|
|
13
14
|
const sdk = SDKService.getSDK();
|
|
14
15
|
if (!sdk) {
|
|
@@ -23,7 +24,6 @@ router.get('/providers/chat', async (req: Request, res: Response) => {
|
|
|
23
24
|
|
|
24
25
|
res.json({ success: true, providers: providers || [] });
|
|
25
26
|
} catch (error: any) {
|
|
26
|
-
console.warn('[SDK API] Failed to fetch chat providers:', error.message);
|
|
27
27
|
res.json({ success: false, providers: [], message: error.message });
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -32,7 +32,7 @@ router.get('/providers/chat', async (req: Request, res: Response) => {
|
|
|
32
32
|
* GET /api/sdk/providers/embed
|
|
33
33
|
* Returns available embedding providers and their models
|
|
34
34
|
*/
|
|
35
|
-
router.get('/providers/embed', async (req: Request, res: Response) => {
|
|
35
|
+
router.get('/providers/embed', apiRateLimit, async (req: Request, res: Response) => {
|
|
36
36
|
try {
|
|
37
37
|
const sdk = SDKService.getSDK();
|
|
38
38
|
if (!sdk) {
|
|
@@ -47,7 +47,6 @@ router.get('/providers/embed', async (req: Request, res: Response) => {
|
|
|
47
47
|
|
|
48
48
|
res.json({ success: true, providers: providers || [] });
|
|
49
49
|
} catch (error: any) {
|
|
50
|
-
console.warn('[SDK API] Failed to fetch embed providers:', error.message);
|
|
51
50
|
res.json({ success: false, providers: [], message: error.message });
|
|
52
51
|
}
|
|
53
52
|
});
|
|
@@ -165,10 +165,14 @@ router.post('/test-llm',
|
|
|
165
165
|
apiRateLimit,
|
|
166
166
|
authMiddleware,
|
|
167
167
|
asyncHandler(async (req, res) => {
|
|
168
|
+
const { llm_provider, llm_model } = req.body;
|
|
168
169
|
const { getIntelligenceService } = await import('../services/intelligence.js');
|
|
169
170
|
const intelligence = getIntelligenceService();
|
|
170
171
|
|
|
171
|
-
const result = await intelligence.testConnection(
|
|
172
|
+
const result = await intelligence.testConnection({
|
|
173
|
+
provider: llm_provider,
|
|
174
|
+
model: llm_model
|
|
175
|
+
});
|
|
172
176
|
res.json(result);
|
|
173
177
|
})
|
|
174
178
|
);
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { RealtimeXSDK, ProvidersResponse } from '@realtimex/sdk';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { createLogger } from '../utils/logger.js';
|
|
5
|
+
|
|
6
|
+
const logger = createLogger('SDKService');
|
|
7
|
+
|
|
8
|
+
export interface ProviderResult {
|
|
9
|
+
provider: string;
|
|
10
|
+
model: string;
|
|
11
|
+
isDefaultFallback?: boolean;
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
/**
|
|
6
15
|
* Centralized SDK Service
|
|
@@ -10,6 +19,13 @@ export class SDKService {
|
|
|
10
19
|
private static instance: RealtimeXSDK | null = null;
|
|
11
20
|
private static initAttempted = false;
|
|
12
21
|
|
|
22
|
+
// Default provider/model configuration
|
|
23
|
+
// realtimexai routes through RealTimeX Desktop to user's configured providers
|
|
24
|
+
static readonly DEFAULT_LLM_PROVIDER = 'realtimexai';
|
|
25
|
+
static readonly DEFAULT_LLM_MODEL = 'gpt-4o-mini';
|
|
26
|
+
static readonly DEFAULT_EMBED_PROVIDER = 'realtimexai';
|
|
27
|
+
static readonly DEFAULT_EMBED_MODEL = 'text-embedding-3-small';
|
|
28
|
+
|
|
13
29
|
/**
|
|
14
30
|
* Initialize SDK with required permissions
|
|
15
31
|
* Safe to call multiple times - returns existing instance
|
|
@@ -39,16 +55,16 @@ export class SDKService {
|
|
|
39
55
|
],
|
|
40
56
|
});
|
|
41
57
|
|
|
42
|
-
|
|
58
|
+
logger.info('RealTimeX SDK initialized successfully');
|
|
43
59
|
|
|
44
60
|
// Verify connection with Desktop App
|
|
45
61
|
// @ts-ignore - Dev Mode feature
|
|
46
62
|
this.instance.ping()
|
|
47
|
-
.then((status: any) =>
|
|
48
|
-
.catch((err: any) =>
|
|
63
|
+
.then((status: any) => logger.debug('Desktop App Connection:', { status }))
|
|
64
|
+
.catch((err: any) => logger.warn('Desktop App Connection failed', { error: err.message }));
|
|
49
65
|
|
|
50
66
|
} catch (error: any) {
|
|
51
|
-
|
|
67
|
+
logger.error('Failed to initialize SDK', error);
|
|
52
68
|
this.instance = null;
|
|
53
69
|
}
|
|
54
70
|
}
|
|
@@ -84,8 +100,8 @@ export class SDKService {
|
|
|
84
100
|
await sdk.llm.chatProviders();
|
|
85
101
|
return true;
|
|
86
102
|
}
|
|
87
|
-
} catch (error) {
|
|
88
|
-
|
|
103
|
+
} catch (error: any) {
|
|
104
|
+
logger.warn('SDK not available', { error: error.message });
|
|
89
105
|
return false;
|
|
90
106
|
}
|
|
91
107
|
}
|
|
@@ -108,14 +124,14 @@ export class SDKService {
|
|
|
108
124
|
}
|
|
109
125
|
|
|
110
126
|
// Cache for default providers (avoid repeated SDK calls)
|
|
111
|
-
private static defaultChatProvider:
|
|
112
|
-
private static defaultEmbedProvider:
|
|
127
|
+
private static defaultChatProvider: ProviderResult | null = null;
|
|
128
|
+
private static defaultEmbedProvider: ProviderResult | null = null;
|
|
113
129
|
|
|
114
130
|
/**
|
|
115
131
|
* Get default chat provider/model from SDK dynamically
|
|
116
132
|
* Caches result to avoid repeated SDK calls
|
|
117
133
|
*/
|
|
118
|
-
static async getDefaultChatProvider(): Promise<
|
|
134
|
+
static async getDefaultChatProvider(): Promise<ProviderResult> {
|
|
119
135
|
// Return cached if available
|
|
120
136
|
if (this.defaultChatProvider) {
|
|
121
137
|
return this.defaultChatProvider;
|
|
@@ -137,22 +153,40 @@ export class SDKService {
|
|
|
137
153
|
throw new Error('No LLM providers available. Please configure a provider in RealTimeX Desktop.');
|
|
138
154
|
}
|
|
139
155
|
|
|
140
|
-
//
|
|
156
|
+
// 1. Try to find the preferred default provider (realtimexai)
|
|
157
|
+
const preferredProvider = providers.find(p => p.provider === this.DEFAULT_LLM_PROVIDER);
|
|
158
|
+
if (preferredProvider && preferredProvider.models && preferredProvider.models.length > 0) {
|
|
159
|
+
// Try to find the preferred default model (gpt-4o-mini) within it
|
|
160
|
+
const preferredModel = preferredProvider.models.find(m => m.id === this.DEFAULT_LLM_MODEL) || preferredProvider.models[0];
|
|
161
|
+
|
|
162
|
+
this.defaultChatProvider = {
|
|
163
|
+
provider: preferredProvider.provider,
|
|
164
|
+
model: preferredModel.id
|
|
165
|
+
};
|
|
166
|
+
logger.info(`Using preferred default chat provider: ${this.defaultChatProvider.provider}/${this.defaultChatProvider.model}`);
|
|
167
|
+
return this.defaultChatProvider;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 2. Fallback to the first provider with available models
|
|
141
171
|
for (const p of providers) {
|
|
142
172
|
if (p.models && p.models.length > 0) {
|
|
143
173
|
this.defaultChatProvider = {
|
|
144
174
|
provider: p.provider,
|
|
145
175
|
model: p.models[0].id
|
|
146
176
|
};
|
|
147
|
-
|
|
177
|
+
logger.info(`Defaulting to first available chat provider: ${this.defaultChatProvider.provider}/${this.defaultChatProvider.model}`);
|
|
148
178
|
return this.defaultChatProvider;
|
|
149
179
|
}
|
|
150
180
|
}
|
|
151
181
|
|
|
152
182
|
throw new Error('No LLM models available. Please configure a model in RealTimeX Desktop.');
|
|
153
183
|
} catch (error: any) {
|
|
154
|
-
|
|
155
|
-
|
|
184
|
+
logger.warn('Failed to get default chat provider from SDK. Using hardcoded fallback.', error);
|
|
185
|
+
return {
|
|
186
|
+
provider: this.DEFAULT_LLM_PROVIDER,
|
|
187
|
+
model: this.DEFAULT_LLM_MODEL,
|
|
188
|
+
isDefaultFallback: true
|
|
189
|
+
};
|
|
156
190
|
}
|
|
157
191
|
}
|
|
158
192
|
|
|
@@ -160,7 +194,7 @@ export class SDKService {
|
|
|
160
194
|
* Get default embedding provider/model from SDK dynamically
|
|
161
195
|
* Caches result to avoid repeated SDK calls
|
|
162
196
|
*/
|
|
163
|
-
static async getDefaultEmbedProvider(): Promise<
|
|
197
|
+
static async getDefaultEmbedProvider(): Promise<ProviderResult> {
|
|
164
198
|
// Return cached if available
|
|
165
199
|
if (this.defaultEmbedProvider) {
|
|
166
200
|
return this.defaultEmbedProvider;
|
|
@@ -189,61 +223,46 @@ export class SDKService {
|
|
|
189
223
|
provider: p.provider,
|
|
190
224
|
model: p.models[0].id
|
|
191
225
|
};
|
|
192
|
-
|
|
226
|
+
logger.info(`Default embed provider: ${this.defaultEmbedProvider.provider}/${this.defaultEmbedProvider.model}`);
|
|
193
227
|
return this.defaultEmbedProvider;
|
|
194
228
|
}
|
|
195
229
|
}
|
|
196
230
|
|
|
197
231
|
throw new Error('No embedding models available. Please configure a model in RealTimeX Desktop.');
|
|
198
232
|
} catch (error: any) {
|
|
199
|
-
|
|
200
|
-
|
|
233
|
+
logger.warn('Failed to get default embed provider from SDK. Using hardcoded fallback.', error);
|
|
234
|
+
return {
|
|
235
|
+
provider: this.DEFAULT_EMBED_PROVIDER,
|
|
236
|
+
model: this.DEFAULT_EMBED_MODEL,
|
|
237
|
+
isDefaultFallback: true
|
|
238
|
+
};
|
|
201
239
|
}
|
|
202
240
|
}
|
|
203
241
|
|
|
204
|
-
// Default provider/model configuration
|
|
205
|
-
// realtimexai routes through RealTimeX Desktop to user's configured providers
|
|
206
|
-
static readonly DEFAULT_LLM_PROVIDER = 'realtimexai';
|
|
207
|
-
static readonly DEFAULT_LLM_MODEL = 'gpt-4o-mini';
|
|
208
|
-
static readonly DEFAULT_EMBED_PROVIDER = 'realtimexai';
|
|
209
|
-
static readonly DEFAULT_EMBED_MODEL = 'text-embedding-3-small';
|
|
210
|
-
|
|
211
242
|
/**
|
|
212
243
|
* Resolve LLM provider/model - use settings if available, otherwise use defaults
|
|
213
244
|
*/
|
|
214
|
-
static async resolveChatProvider(settings: { llm_provider?: string; llm_model?: string }): Promise<
|
|
245
|
+
static async resolveChatProvider(settings: { llm_provider?: string; llm_model?: string }): Promise<ProviderResult> {
|
|
215
246
|
// If both provider and model are set in settings, use them
|
|
216
247
|
if (settings.llm_provider && settings.llm_model) {
|
|
217
248
|
return { provider: settings.llm_provider, model: settings.llm_model };
|
|
218
249
|
}
|
|
219
250
|
|
|
220
251
|
// Try to get from SDK discovery first
|
|
221
|
-
|
|
222
|
-
return await this.getDefaultChatProvider();
|
|
223
|
-
} catch (error) {
|
|
224
|
-
// Fallback to hardcoded defaults if SDK discovery fails
|
|
225
|
-
console.log(`[SDKService] Using default LLM: ${this.DEFAULT_LLM_PROVIDER}/${this.DEFAULT_LLM_MODEL}`);
|
|
226
|
-
return { provider: this.DEFAULT_LLM_PROVIDER, model: this.DEFAULT_LLM_MODEL };
|
|
227
|
-
}
|
|
252
|
+
return await this.getDefaultChatProvider();
|
|
228
253
|
}
|
|
229
254
|
|
|
230
255
|
/**
|
|
231
256
|
* Resolve embedding provider/model - use settings if available, otherwise use defaults
|
|
232
257
|
*/
|
|
233
|
-
static async resolveEmbedProvider(settings: { embedding_provider?: string; embedding_model?: string }): Promise<
|
|
258
|
+
static async resolveEmbedProvider(settings: { embedding_provider?: string; embedding_model?: string }): Promise<ProviderResult> {
|
|
234
259
|
// If both provider and model are set in settings, use them
|
|
235
260
|
if (settings.embedding_provider && settings.embedding_model) {
|
|
236
261
|
return { provider: settings.embedding_provider, model: settings.embedding_model };
|
|
237
262
|
}
|
|
238
263
|
|
|
239
264
|
// Try to get from SDK discovery first
|
|
240
|
-
|
|
241
|
-
return await this.getDefaultEmbedProvider();
|
|
242
|
-
} catch (error) {
|
|
243
|
-
// Fallback to hardcoded defaults if SDK discovery fails
|
|
244
|
-
console.log(`[SDKService] Using default embedding: ${this.DEFAULT_EMBED_PROVIDER}/${this.DEFAULT_EMBED_MODEL}`);
|
|
245
|
-
return { provider: this.DEFAULT_EMBED_PROVIDER, model: this.DEFAULT_EMBED_MODEL };
|
|
246
|
-
}
|
|
265
|
+
return await this.getDefaultEmbedProvider();
|
|
247
266
|
}
|
|
248
267
|
|
|
249
268
|
/**
|
|
@@ -91,9 +91,7 @@ export class IntelligenceService {
|
|
|
91
91
|
return this.isConfigured && !!SDKService.getSDK();
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
async analyzeEmail(content: string, context: EmailContext, eventLogger?: EventLogger, emailId?: string): Promise<EmailAnalysis | null> {
|
|
95
|
-
console.log('[Intelligence] analyzeEmail called for:', context.subject);
|
|
96
|
-
|
|
94
|
+
async analyzeEmail(content: string, context: EmailContext, eventLogger?: EventLogger, emailId?: string): Promise<(EmailAnalysis & { _metadata?: any }) | null> {
|
|
97
95
|
const sdk = SDKService.getSDK();
|
|
98
96
|
if (!sdk) {
|
|
99
97
|
logger.warn('Intelligence service not ready, skipping analysis');
|
|
@@ -103,7 +101,7 @@ export class IntelligenceService {
|
|
|
103
101
|
return null;
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
const { provider, model } = await SDKService.resolveChatProvider({});
|
|
104
|
+
const { provider, model, isDefaultFallback } = await SDKService.resolveChatProvider({});
|
|
107
105
|
|
|
108
106
|
const cleanedContent = ContentCleaner.cleanEmailBody(content).substring(0, 2500);
|
|
109
107
|
|
|
@@ -138,6 +136,7 @@ REQUIRED JSON STRUCTURE:
|
|
|
138
136
|
await eventLogger.info('Thinking', `Analyzing email: ${context.subject}`, {
|
|
139
137
|
model,
|
|
140
138
|
provider,
|
|
139
|
+
is_fallback: isDefaultFallback,
|
|
141
140
|
content_preview: cleanedContent
|
|
142
141
|
}, emailId);
|
|
143
142
|
}
|
|
@@ -151,14 +150,24 @@ REQUIRED JSON STRUCTURE:
|
|
|
151
150
|
const rawResponse = response.response?.content || '';
|
|
152
151
|
const validated = this.parseRobustJSON<EmailAnalysis>(rawResponse, EmailAnalysisSchema);
|
|
153
152
|
|
|
154
|
-
|
|
153
|
+
const result = validated ? {
|
|
154
|
+
...validated,
|
|
155
|
+
_metadata: {
|
|
156
|
+
provider,
|
|
157
|
+
model,
|
|
158
|
+
is_fallback: isDefaultFallback,
|
|
159
|
+
timestamp: new Date().toISOString()
|
|
160
|
+
}
|
|
161
|
+
} : null;
|
|
162
|
+
|
|
163
|
+
if (eventLogger && emailId && result) {
|
|
155
164
|
await eventLogger.analysis('Decided', emailId, {
|
|
156
|
-
...
|
|
165
|
+
...result,
|
|
157
166
|
_raw_response: rawResponse
|
|
158
167
|
});
|
|
159
168
|
}
|
|
160
169
|
|
|
161
|
-
return
|
|
170
|
+
return result;
|
|
162
171
|
} catch (error: any) {
|
|
163
172
|
logger.error('Analysis failed', error);
|
|
164
173
|
if (eventLogger) await eventLogger.error('Error', error.message, emailId);
|
|
@@ -200,11 +209,11 @@ REQUIRED JSON STRUCTURE:
|
|
|
200
209
|
compiledRulesContext: string | RuleContext[],
|
|
201
210
|
eventLogger?: EventLogger,
|
|
202
211
|
emailId?: string
|
|
203
|
-
): Promise<ContextAwareAnalysis | null> {
|
|
212
|
+
): Promise<(ContextAwareAnalysis & { _metadata?: any }) | null> {
|
|
204
213
|
const sdk = SDKService.getSDK();
|
|
205
214
|
if (!sdk) return null;
|
|
206
215
|
|
|
207
|
-
const { provider, model } = await SDKService.resolveChatProvider({});
|
|
216
|
+
const { provider, model, isDefaultFallback } = await SDKService.resolveChatProvider({});
|
|
208
217
|
const cleanedContent = ContentCleaner.cleanEmailBody(content).substring(0, 2500);
|
|
209
218
|
|
|
210
219
|
let rulesContext: string;
|
|
@@ -217,7 +226,11 @@ REQUIRED JSON STRUCTURE:
|
|
|
217
226
|
const systemPrompt = `You are an AI Automation Agent. Match email against these rules:\n${rulesContext}\n\nReturn JSON with matched_rule, actions_to_execute, and draft_content.`;
|
|
218
227
|
|
|
219
228
|
if (eventLogger) {
|
|
220
|
-
await eventLogger.info('Thinking', `Context-aware analysis: ${context.subject}`, {
|
|
229
|
+
await eventLogger.info('Thinking', `Context-aware analysis: ${context.subject}`, {
|
|
230
|
+
model,
|
|
231
|
+
provider,
|
|
232
|
+
is_fallback: isDefaultFallback
|
|
233
|
+
}, emailId);
|
|
221
234
|
}
|
|
222
235
|
|
|
223
236
|
try {
|
|
@@ -229,14 +242,24 @@ REQUIRED JSON STRUCTURE:
|
|
|
229
242
|
const rawResponse = response.response?.content || '';
|
|
230
243
|
const validated = this.parseRobustJSON<ContextAwareAnalysis>(rawResponse, ContextAwareAnalysisSchema);
|
|
231
244
|
|
|
232
|
-
|
|
245
|
+
const result = validated ? {
|
|
246
|
+
...validated,
|
|
247
|
+
_metadata: {
|
|
248
|
+
provider,
|
|
249
|
+
model,
|
|
250
|
+
is_fallback: isDefaultFallback,
|
|
251
|
+
timestamp: new Date().toISOString()
|
|
252
|
+
}
|
|
253
|
+
} : null;
|
|
254
|
+
|
|
255
|
+
if (eventLogger && emailId && result) {
|
|
233
256
|
await eventLogger.analysis('Decided', emailId, {
|
|
234
|
-
...
|
|
257
|
+
...result,
|
|
235
258
|
_raw_response: rawResponse
|
|
236
259
|
});
|
|
237
260
|
}
|
|
238
261
|
|
|
239
|
-
return
|
|
262
|
+
return result;
|
|
240
263
|
} catch (error: any) {
|
|
241
264
|
logger.error('Rule analysis failed', error);
|
|
242
265
|
if (eventLogger) await eventLogger.error('Error', error.message, emailId);
|
|
@@ -244,12 +267,15 @@ REQUIRED JSON STRUCTURE:
|
|
|
244
267
|
}
|
|
245
268
|
}
|
|
246
269
|
|
|
247
|
-
async testConnection(): Promise<{ success: boolean; message: string }> {
|
|
270
|
+
async testConnection(overrides?: { provider?: string; model?: string }): Promise<{ success: boolean; message: string }> {
|
|
248
271
|
const sdk = SDKService.getSDK();
|
|
249
272
|
if (!sdk) return { success: false, message: 'SDK not linked' };
|
|
250
273
|
|
|
251
274
|
try {
|
|
252
|
-
const { provider, model } = await SDKService.resolveChatProvider({
|
|
275
|
+
const { provider, model } = await SDKService.resolveChatProvider({
|
|
276
|
+
llm_provider: overrides?.provider,
|
|
277
|
+
llm_model: overrides?.model
|
|
278
|
+
});
|
|
253
279
|
await sdk.llm.chat([{ role: 'user', content: 'Say OK' }], { provider, model });
|
|
254
280
|
return { success: true, message: `Connected to ${provider}/${model}` };
|
|
255
281
|
} catch (error: any) {
|
|
@@ -272,7 +298,7 @@ REQUIRED JSON STRUCTURE:
|
|
|
272
298
|
|
|
273
299
|
let defaultInstance: IntelligenceService | null = null;
|
|
274
300
|
|
|
275
|
-
export function getIntelligenceService(
|
|
301
|
+
export function getIntelligenceService(): IntelligenceService {
|
|
276
302
|
if (!defaultInstance) {
|
|
277
303
|
defaultInstance = new IntelligenceService();
|
|
278
304
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { SDKService } from '../services/SDKService.js';
|
|
3
|
+
import { apiRateLimit } from '../middleware/rateLimit.js';
|
|
3
4
|
const router = Router();
|
|
4
5
|
/**
|
|
5
6
|
* GET /api/sdk/providers/chat
|
|
6
7
|
* Returns available chat providers and their models
|
|
7
8
|
*/
|
|
8
|
-
router.get('/providers/chat', async (req, res) => {
|
|
9
|
+
router.get('/providers/chat', apiRateLimit, async (req, res) => {
|
|
9
10
|
try {
|
|
10
11
|
const sdk = SDKService.getSDK();
|
|
11
12
|
if (!sdk) {
|
|
@@ -15,7 +16,6 @@ router.get('/providers/chat', async (req, res) => {
|
|
|
15
16
|
res.json({ success: true, providers: providers || [] });
|
|
16
17
|
}
|
|
17
18
|
catch (error) {
|
|
18
|
-
console.warn('[SDK API] Failed to fetch chat providers:', error.message);
|
|
19
19
|
res.json({ success: false, providers: [], message: error.message });
|
|
20
20
|
}
|
|
21
21
|
});
|
|
@@ -23,7 +23,7 @@ router.get('/providers/chat', async (req, res) => {
|
|
|
23
23
|
* GET /api/sdk/providers/embed
|
|
24
24
|
* Returns available embedding providers and their models
|
|
25
25
|
*/
|
|
26
|
-
router.get('/providers/embed', async (req, res) => {
|
|
26
|
+
router.get('/providers/embed', apiRateLimit, async (req, res) => {
|
|
27
27
|
try {
|
|
28
28
|
const sdk = SDKService.getSDK();
|
|
29
29
|
if (!sdk) {
|
|
@@ -33,7 +33,6 @@ router.get('/providers/embed', async (req, res) => {
|
|
|
33
33
|
res.json({ success: true, providers: providers || [] });
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
|
-
console.warn('[SDK API] Failed to fetch embed providers:', error.message);
|
|
37
36
|
res.json({ success: false, providers: [], message: error.message });
|
|
38
37
|
}
|
|
39
38
|
});
|
|
@@ -129,9 +129,13 @@ router.patch('/', apiRateLimit, authMiddleware, validateBody(schemas.updateSetti
|
|
|
129
129
|
}));
|
|
130
130
|
// Test LLM Connection
|
|
131
131
|
router.post('/test-llm', apiRateLimit, authMiddleware, asyncHandler(async (req, res) => {
|
|
132
|
+
const { llm_provider, llm_model } = req.body;
|
|
132
133
|
const { getIntelligenceService } = await import('../services/intelligence.js');
|
|
133
134
|
const intelligence = getIntelligenceService();
|
|
134
|
-
const result = await intelligence.testConnection(
|
|
135
|
+
const result = await intelligence.testConnection({
|
|
136
|
+
provider: llm_provider,
|
|
137
|
+
model: llm_model
|
|
138
|
+
});
|
|
135
139
|
res.json(result);
|
|
136
140
|
}));
|
|
137
141
|
// Get analytics/stats
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { RealtimeXSDK } from '@realtimex/sdk';
|
|
2
|
+
import { createLogger } from '../utils/logger.js';
|
|
3
|
+
const logger = createLogger('SDKService');
|
|
2
4
|
/**
|
|
3
5
|
* Centralized SDK Service
|
|
4
6
|
* Manages RealTimeX SDK initialization and provides singleton access
|
|
@@ -6,6 +8,12 @@ import { RealtimeXSDK } from '@realtimex/sdk';
|
|
|
6
8
|
export class SDKService {
|
|
7
9
|
static instance = null;
|
|
8
10
|
static initAttempted = false;
|
|
11
|
+
// Default provider/model configuration
|
|
12
|
+
// realtimexai routes through RealTimeX Desktop to user's configured providers
|
|
13
|
+
static DEFAULT_LLM_PROVIDER = 'realtimexai';
|
|
14
|
+
static DEFAULT_LLM_MODEL = 'gpt-4o-mini';
|
|
15
|
+
static DEFAULT_EMBED_PROVIDER = 'realtimexai';
|
|
16
|
+
static DEFAULT_EMBED_MODEL = 'text-embedding-3-small';
|
|
9
17
|
/**
|
|
10
18
|
* Initialize SDK with required permissions
|
|
11
19
|
* Safe to call multiple times - returns existing instance
|
|
@@ -33,15 +41,15 @@ export class SDKService {
|
|
|
33
41
|
'vectors.write', // Store vectors
|
|
34
42
|
],
|
|
35
43
|
});
|
|
36
|
-
|
|
44
|
+
logger.info('RealTimeX SDK initialized successfully');
|
|
37
45
|
// Verify connection with Desktop App
|
|
38
46
|
// @ts-ignore - Dev Mode feature
|
|
39
47
|
this.instance.ping()
|
|
40
|
-
.then((status) =>
|
|
41
|
-
.catch((err) =>
|
|
48
|
+
.then((status) => logger.debug('Desktop App Connection:', { status }))
|
|
49
|
+
.catch((err) => logger.warn('Desktop App Connection failed', { error: err.message }));
|
|
42
50
|
}
|
|
43
51
|
catch (error) {
|
|
44
|
-
|
|
52
|
+
logger.error('Failed to initialize SDK', error);
|
|
45
53
|
this.instance = null;
|
|
46
54
|
}
|
|
47
55
|
}
|
|
@@ -77,7 +85,7 @@ export class SDKService {
|
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
87
|
catch (error) {
|
|
80
|
-
|
|
88
|
+
logger.warn('SDK not available', { error: error.message });
|
|
81
89
|
return false;
|
|
82
90
|
}
|
|
83
91
|
}
|
|
@@ -118,22 +126,38 @@ export class SDKService {
|
|
|
118
126
|
if (!providers || providers.length === 0) {
|
|
119
127
|
throw new Error('No LLM providers available. Please configure a provider in RealTimeX Desktop.');
|
|
120
128
|
}
|
|
121
|
-
//
|
|
129
|
+
// 1. Try to find the preferred default provider (realtimexai)
|
|
130
|
+
const preferredProvider = providers.find(p => p.provider === this.DEFAULT_LLM_PROVIDER);
|
|
131
|
+
if (preferredProvider && preferredProvider.models && preferredProvider.models.length > 0) {
|
|
132
|
+
// Try to find the preferred default model (gpt-4o-mini) within it
|
|
133
|
+
const preferredModel = preferredProvider.models.find(m => m.id === this.DEFAULT_LLM_MODEL) || preferredProvider.models[0];
|
|
134
|
+
this.defaultChatProvider = {
|
|
135
|
+
provider: preferredProvider.provider,
|
|
136
|
+
model: preferredModel.id
|
|
137
|
+
};
|
|
138
|
+
logger.info(`Using preferred default chat provider: ${this.defaultChatProvider.provider}/${this.defaultChatProvider.model}`);
|
|
139
|
+
return this.defaultChatProvider;
|
|
140
|
+
}
|
|
141
|
+
// 2. Fallback to the first provider with available models
|
|
122
142
|
for (const p of providers) {
|
|
123
143
|
if (p.models && p.models.length > 0) {
|
|
124
144
|
this.defaultChatProvider = {
|
|
125
145
|
provider: p.provider,
|
|
126
146
|
model: p.models[0].id
|
|
127
147
|
};
|
|
128
|
-
|
|
148
|
+
logger.info(`Defaulting to first available chat provider: ${this.defaultChatProvider.provider}/${this.defaultChatProvider.model}`);
|
|
129
149
|
return this.defaultChatProvider;
|
|
130
150
|
}
|
|
131
151
|
}
|
|
132
152
|
throw new Error('No LLM models available. Please configure a model in RealTimeX Desktop.');
|
|
133
153
|
}
|
|
134
154
|
catch (error) {
|
|
135
|
-
|
|
136
|
-
|
|
155
|
+
logger.warn('Failed to get default chat provider from SDK. Using hardcoded fallback.', error);
|
|
156
|
+
return {
|
|
157
|
+
provider: this.DEFAULT_LLM_PROVIDER,
|
|
158
|
+
model: this.DEFAULT_LLM_MODEL,
|
|
159
|
+
isDefaultFallback: true
|
|
160
|
+
};
|
|
137
161
|
}
|
|
138
162
|
}
|
|
139
163
|
/**
|
|
@@ -161,23 +185,21 @@ export class SDKService {
|
|
|
161
185
|
provider: p.provider,
|
|
162
186
|
model: p.models[0].id
|
|
163
187
|
};
|
|
164
|
-
|
|
188
|
+
logger.info(`Default embed provider: ${this.defaultEmbedProvider.provider}/${this.defaultEmbedProvider.model}`);
|
|
165
189
|
return this.defaultEmbedProvider;
|
|
166
190
|
}
|
|
167
191
|
}
|
|
168
192
|
throw new Error('No embedding models available. Please configure a model in RealTimeX Desktop.');
|
|
169
193
|
}
|
|
170
194
|
catch (error) {
|
|
171
|
-
|
|
172
|
-
|
|
195
|
+
logger.warn('Failed to get default embed provider from SDK. Using hardcoded fallback.', error);
|
|
196
|
+
return {
|
|
197
|
+
provider: this.DEFAULT_EMBED_PROVIDER,
|
|
198
|
+
model: this.DEFAULT_EMBED_MODEL,
|
|
199
|
+
isDefaultFallback: true
|
|
200
|
+
};
|
|
173
201
|
}
|
|
174
202
|
}
|
|
175
|
-
// Default provider/model configuration
|
|
176
|
-
// realtimexai routes through RealTimeX Desktop to user's configured providers
|
|
177
|
-
static DEFAULT_LLM_PROVIDER = 'realtimexai';
|
|
178
|
-
static DEFAULT_LLM_MODEL = 'gpt-4o-mini';
|
|
179
|
-
static DEFAULT_EMBED_PROVIDER = 'realtimexai';
|
|
180
|
-
static DEFAULT_EMBED_MODEL = 'text-embedding-3-small';
|
|
181
203
|
/**
|
|
182
204
|
* Resolve LLM provider/model - use settings if available, otherwise use defaults
|
|
183
205
|
*/
|
|
@@ -187,14 +209,7 @@ export class SDKService {
|
|
|
187
209
|
return { provider: settings.llm_provider, model: settings.llm_model };
|
|
188
210
|
}
|
|
189
211
|
// Try to get from SDK discovery first
|
|
190
|
-
|
|
191
|
-
return await this.getDefaultChatProvider();
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
// Fallback to hardcoded defaults if SDK discovery fails
|
|
195
|
-
console.log(`[SDKService] Using default LLM: ${this.DEFAULT_LLM_PROVIDER}/${this.DEFAULT_LLM_MODEL}`);
|
|
196
|
-
return { provider: this.DEFAULT_LLM_PROVIDER, model: this.DEFAULT_LLM_MODEL };
|
|
197
|
-
}
|
|
212
|
+
return await this.getDefaultChatProvider();
|
|
198
213
|
}
|
|
199
214
|
/**
|
|
200
215
|
* Resolve embedding provider/model - use settings if available, otherwise use defaults
|
|
@@ -205,14 +220,7 @@ export class SDKService {
|
|
|
205
220
|
return { provider: settings.embedding_provider, model: settings.embedding_model };
|
|
206
221
|
}
|
|
207
222
|
// Try to get from SDK discovery first
|
|
208
|
-
|
|
209
|
-
return await this.getDefaultEmbedProvider();
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
// Fallback to hardcoded defaults if SDK discovery fails
|
|
213
|
-
console.log(`[SDKService] Using default embedding: ${this.DEFAULT_EMBED_PROVIDER}/${this.DEFAULT_EMBED_MODEL}`);
|
|
214
|
-
return { provider: this.DEFAULT_EMBED_PROVIDER, model: this.DEFAULT_EMBED_MODEL };
|
|
215
|
-
}
|
|
223
|
+
return await this.getDefaultEmbedProvider();
|
|
216
224
|
}
|
|
217
225
|
/**
|
|
218
226
|
* Clear provider cache (useful when providers change)
|