@realtimex/email-automator 2.11.2 → 2.12.0

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.
Files changed (47) hide show
  1. package/api/src/routes/index.ts +2 -0
  2. package/api/src/routes/rulePacks.ts +204 -0
  3. package/api/src/services/RulePackService.ts +354 -0
  4. package/api/src/services/SDKService.ts +18 -7
  5. package/api/src/services/gmail.ts +68 -0
  6. package/api/src/services/intelligence.ts +160 -22
  7. package/api/src/services/microsoft.ts +99 -0
  8. package/api/src/services/processor.ts +146 -12
  9. package/api/src/services/rulePacks/developer.ts +179 -0
  10. package/api/src/services/rulePacks/executive.ts +143 -0
  11. package/api/src/services/rulePacks/index.ts +63 -0
  12. package/api/src/services/rulePacks/operations.ts +183 -0
  13. package/api/src/services/rulePacks/sales.ts +160 -0
  14. package/api/src/services/rulePacks/types.ts +116 -0
  15. package/api/src/services/rulePacks/universal.ts +83 -0
  16. package/api/src/services/supabase.ts +40 -0
  17. package/dist/api/src/routes/index.js +2 -0
  18. package/dist/api/src/routes/rulePacks.js +179 -0
  19. package/dist/api/src/services/RulePackService.js +296 -0
  20. package/dist/api/src/services/SDKService.js +18 -7
  21. package/dist/api/src/services/gmail.js +56 -0
  22. package/dist/api/src/services/intelligence.js +153 -21
  23. package/dist/api/src/services/microsoft.js +79 -0
  24. package/dist/api/src/services/processor.js +133 -12
  25. package/dist/api/src/services/rulePacks/developer.js +176 -0
  26. package/dist/api/src/services/rulePacks/executive.js +140 -0
  27. package/dist/api/src/services/rulePacks/index.js +58 -0
  28. package/dist/api/src/services/rulePacks/operations.js +180 -0
  29. package/dist/api/src/services/rulePacks/sales.js +157 -0
  30. package/dist/api/src/services/rulePacks/types.js +7 -0
  31. package/dist/api/src/services/rulePacks/universal.js +80 -0
  32. package/dist/assets/index-B5rXh3y8.css +1 -0
  33. package/dist/assets/index-B62ViZum.js +105 -0
  34. package/dist/index.html +2 -2
  35. package/package.json +1 -1
  36. package/supabase/migrations/20260131000000_add_rule_packs.sql +176 -0
  37. package/supabase/migrations/20260131000001_set_zero_config_defaults.sql +51 -0
  38. package/supabase/migrations/20260131095000_backfill_user_settings.sql +36 -0
  39. package/supabase/migrations/20260131100000_rule_templates_table.sql +154 -0
  40. package/supabase/migrations/20260131110000_auto_init_user_data.sql +90 -0
  41. package/supabase/migrations/20260131120000_backfill_universal_pack.sql +84 -0
  42. package/supabase/migrations/20260131130000_simplify_rules_with_categories.sql +87 -0
  43. package/supabase/migrations/20260131140000_fix_action_constraint.sql +11 -0
  44. package/supabase/migrations/20260131150000_fix_trigger_error_handling.sql +71 -0
  45. package/supabase/migrations/20260131160000_enable_intelligent_rename_by_default.sql +14 -0
  46. package/dist/assets/index-BuWrl4UD.js +0 -105
  47. package/dist/assets/index-CtDzSy0n.css +0 -1
@@ -8,6 +8,7 @@ import settingsRoutes from './settings.js';
8
8
  import emailsRoutes from './emails.js';
9
9
  import migrateRoutes from './migrate.js';
10
10
  import sdkRoutes from './sdk.js';
11
+ import rulePacksRoutes from './rulePacks.js';
11
12
 
12
13
  const router = Router();
13
14
 
@@ -20,5 +21,6 @@ router.use('/settings', settingsRoutes);
20
21
  router.use('/emails', emailsRoutes);
21
22
  router.use('/migrate', migrateRoutes);
22
23
  router.use('/sdk', sdkRoutes);
24
+ router.use('/rule-packs', rulePacksRoutes);
23
25
 
24
26
  export default router;
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Rule Pack Routes - Zero-Configuration UX
3
+ *
4
+ * Endpoints for managing rule pack installations and user role selection
5
+ */
6
+
7
+ import { Router, Request, Response } from 'express';
8
+ import { createLogger } from '../utils/logger.js';
9
+ import { RulePackService } from '../services/RulePackService.js';
10
+ import { getAllPacks, getPackById, getPacksForRole } from '../services/rulePacks/index.js';
11
+ import type { UserRole } from '../services/rulePacks/types.js';
12
+
13
+ const router = Router();
14
+ const logger = createLogger('RulePackRoutes');
15
+
16
+ /**
17
+ * GET /api/rule-packs
18
+ * List all available rule packs
19
+ */
20
+ router.get('/', async (req: Request, res: Response) => {
21
+ try {
22
+ const packs = getAllPacks();
23
+
24
+ res.json({
25
+ success: true,
26
+ packs: packs.map(pack => ({
27
+ id: pack.id,
28
+ name: pack.name,
29
+ description: pack.description,
30
+ icon: pack.icon,
31
+ ruleCount: pack.rules.length,
32
+ targetRoles: pack.target_roles
33
+ }))
34
+ });
35
+ } catch (error: any) {
36
+ logger.error('Failed to list packs', error);
37
+ res.status(500).json({ success: false, error: error.message });
38
+ }
39
+ });
40
+
41
+ /**
42
+ * GET /api/rule-packs/:packId
43
+ * Get details of a specific pack
44
+ */
45
+ router.get('/:packId', async (req: Request, res: Response) => {
46
+ try {
47
+ const packId = req.params.packId as string;
48
+ const pack = getPackById(packId);
49
+
50
+ if (!pack) {
51
+ return res.status(404).json({ success: false, error: 'Pack not found' });
52
+ }
53
+
54
+ res.json({ success: true, pack });
55
+ } catch (error: any) {
56
+ logger.error('Failed to get pack', error);
57
+ res.status(500).json({ success: false, error: error.message });
58
+ }
59
+ });
60
+
61
+ /**
62
+ * POST /api/rule-packs/install
63
+ * Install a rule pack for the authenticated user
64
+ * Body: { packId: string }
65
+ */
66
+ router.post('/install', async (req: Request, res: Response) => {
67
+ try {
68
+ const { packId } = req.body;
69
+ const supabase = (req as any).supabase;
70
+ const userId = (req as any).userId;
71
+
72
+ if (!packId) {
73
+ return res.status(400).json({ success: false, error: 'packId is required' });
74
+ }
75
+
76
+ if (!userId) {
77
+ return res.status(401).json({ success: false, error: 'Unauthorized' });
78
+ }
79
+
80
+ const service = new RulePackService(supabase);
81
+ const result = await service.installPack(userId, packId);
82
+
83
+ res.json(result);
84
+ } catch (error: any) {
85
+ logger.error('Failed to install pack', error);
86
+ res.status(500).json({ success: false, error: error.message });
87
+ }
88
+ });
89
+
90
+ /**
91
+ * POST /api/rule-packs/uninstall
92
+ * Uninstall a rule pack
93
+ * Body: { packId: string }
94
+ */
95
+ router.post('/uninstall', async (req: Request, res: Response) => {
96
+ try {
97
+ const { packId } = req.body;
98
+ const supabase = (req as any).supabase;
99
+ const userId = (req as any).userId;
100
+
101
+ if (!packId) {
102
+ return res.status(400).json({ success: false, error: 'packId is required' });
103
+ }
104
+
105
+ if (!userId) {
106
+ return res.status(401).json({ success: false, error: 'Unauthorized' });
107
+ }
108
+
109
+ const service = new RulePackService(supabase);
110
+ const result = await service.uninstallPack(userId, packId);
111
+
112
+ res.json(result);
113
+ } catch (error: any) {
114
+ logger.error('Failed to uninstall pack', error);
115
+ res.status(500).json({ success: false, error: error.message });
116
+ }
117
+ });
118
+
119
+ /**
120
+ * GET /api/rule-packs/installed
121
+ * Get user's installed packs
122
+ */
123
+ router.get('/installed', async (req: Request, res: Response) => {
124
+ try {
125
+ const supabase = (req as any).supabase;
126
+ const userId = (req as any).userId;
127
+
128
+ if (!userId) {
129
+ return res.status(401).json({ success: false, error: 'Unauthorized' });
130
+ }
131
+
132
+ const service = new RulePackService(supabase);
133
+ const installations = await service.getInstalledPacks(userId);
134
+
135
+ res.json({ success: true, installations });
136
+ } catch (error: any) {
137
+ logger.error('Failed to get installed packs', error);
138
+ res.status(500).json({ success: false, error: error.message });
139
+ }
140
+ });
141
+
142
+ /**
143
+ * POST /api/rule-packs/set-role
144
+ * Set user role and install appropriate packs
145
+ * Body: { role: UserRole }
146
+ */
147
+ router.post('/set-role', async (req: Request, res: Response) => {
148
+ try {
149
+ const { role } = req.body;
150
+ const supabase = (req as any).supabase;
151
+ const userId = (req as any).userId;
152
+
153
+ if (!role) {
154
+ return res.status(400).json({ success: false, error: 'role is required' });
155
+ }
156
+
157
+ if (!userId) {
158
+ return res.status(401).json({ success: false, error: 'Unauthorized' });
159
+ }
160
+
161
+ const validRoles: UserRole[] = ['executive', 'developer', 'sales', 'operations', 'marketing', 'other'];
162
+ if (!validRoles.includes(role as UserRole)) {
163
+ return res.status(400).json({ success: false, error: 'Invalid role' });
164
+ }
165
+
166
+ const service = new RulePackService(supabase);
167
+ const result = await service.updateUserRole(userId, role as UserRole, 'onboarding');
168
+
169
+ logger.info(`User ${userId} set role to ${role}, installed ${result.packsInstalled.length} packs`);
170
+
171
+ res.json(result);
172
+ } catch (error: any) {
173
+ logger.error('Failed to set user role', error);
174
+ res.status(500).json({ success: false, error: error.message });
175
+ }
176
+ });
177
+
178
+ /**
179
+ * GET /api/rule-packs/for-role/:role
180
+ * Get recommended packs for a specific role
181
+ */
182
+ router.get('/for-role/:role', async (req: Request, res: Response) => {
183
+ try {
184
+ const { role } = req.params;
185
+ const packs = getPacksForRole(role as UserRole);
186
+
187
+ res.json({
188
+ success: true,
189
+ role,
190
+ packs: packs.map(pack => ({
191
+ id: pack.id,
192
+ name: pack.name,
193
+ description: pack.description,
194
+ icon: pack.icon,
195
+ ruleCount: pack.rules.length
196
+ }))
197
+ });
198
+ } catch (error: any) {
199
+ logger.error('Failed to get packs for role', error);
200
+ res.status(500).json({ success: false, error: error.message });
201
+ }
202
+ });
203
+
204
+ export default router;
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Rule Pack Service - Zero-Configuration Email Automation
3
+ *
4
+ * Handles installation, management, and tracking of rule packs for users.
5
+ */
6
+
7
+ import { SupabaseClient } from '@supabase/supabase-js';
8
+ import { createLogger } from '../utils/logger.js';
9
+ import { getPackById, getPacksForRole, ALL_PACKS } from './rulePacks/index.js';
10
+ import type { RulePack, RuleTemplate, UserRole } from './rulePacks/types.js';
11
+ import type { Rule, PackInstallation } from './supabase.js';
12
+
13
+ const logger = createLogger('RulePackService');
14
+
15
+ export class RulePackService {
16
+ constructor(private supabase: SupabaseClient) {}
17
+
18
+ /**
19
+ * Install a rule pack for a user
20
+ * Creates all rules from the pack and tracks installation
21
+ */
22
+ async installPack(userId: string, packId: string): Promise<{ success: boolean; rulesCreated: number; error?: string }> {
23
+ try {
24
+ const pack = getPackById(packId);
25
+ if (!pack) {
26
+ return { success: false, rulesCreated: 0, error: `Pack '${packId}' not found` };
27
+ }
28
+
29
+ logger.info(`Installing pack '${packId}' for user ${userId}`);
30
+
31
+ // Check if pack is already installed
32
+ const { data: existing } = await this.supabase
33
+ .from('pack_installations')
34
+ .select('*')
35
+ .eq('user_id', userId)
36
+ .eq('pack_id', packId)
37
+ .is('uninstalled_at', null)
38
+ .single();
39
+
40
+ if (existing) {
41
+ logger.info(`Pack '${packId}' already installed for user ${userId}`);
42
+ return { success: true, rulesCreated: 0 }; // Already installed, no-op
43
+ }
44
+
45
+ // Create all rules from the pack
46
+ let rulesCreated = 0;
47
+ for (const template of pack.rules) {
48
+ // Check if rule already exists (by rule_template_id)
49
+ const { data: existingRule } = await this.supabase
50
+ .from('rules')
51
+ .select('id')
52
+ .eq('user_id', userId)
53
+ .eq('rule_template_id', template.id)
54
+ .single();
55
+
56
+ if (existingRule) {
57
+ logger.debug(`Rule '${template.id}' already exists, skipping`);
58
+ continue;
59
+ }
60
+
61
+ // Create rule from template
62
+ const { error: ruleError } = await this.supabase
63
+ .from('rules')
64
+ .insert({
65
+ user_id: userId,
66
+ name: template.name,
67
+ description: template.description,
68
+ intent: template.intent,
69
+ priority: template.priority,
70
+ condition: template.condition as any,
71
+ actions: template.actions as any,
72
+ instructions: template.instructions,
73
+ is_enabled: template.is_enabled_by_default,
74
+ pack: packId,
75
+ rule_template_id: template.id,
76
+ is_system_managed: true
77
+ });
78
+
79
+ if (ruleError) {
80
+ logger.error(`Failed to create rule '${template.id}'`, ruleError);
81
+ continue; // Continue with other rules even if one fails
82
+ }
83
+
84
+ rulesCreated++;
85
+ }
86
+
87
+ // Track pack installation
88
+ const { error: installError } = await this.supabase
89
+ .from('pack_installations')
90
+ .insert({
91
+ user_id: userId,
92
+ pack_id: packId,
93
+ source: 'manual'
94
+ });
95
+
96
+ if (installError) {
97
+ logger.error(`Failed to track pack installation`, installError);
98
+ // Don't fail the whole operation if tracking fails
99
+ }
100
+
101
+ logger.info(`Successfully installed pack '${packId}' with ${rulesCreated} rules`);
102
+ return { success: true, rulesCreated };
103
+
104
+ } catch (error: any) {
105
+ logger.error(`Error installing pack '${packId}'`, error);
106
+ return { success: false, rulesCreated: 0, error: error.message };
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Uninstall a rule pack for a user
112
+ * Disables all rules from the pack and marks it as uninstalled
113
+ */
114
+ async uninstallPack(userId: string, packId: string): Promise<{ success: boolean; error?: string }> {
115
+ try {
116
+ logger.info(`Uninstalling pack '${packId}' for user ${userId}`);
117
+
118
+ // Disable all rules from this pack
119
+ const { error: disableError } = await this.supabase
120
+ .from('rules')
121
+ .update({ is_enabled: false })
122
+ .eq('user_id', userId)
123
+ .eq('pack', packId);
124
+
125
+ if (disableError) {
126
+ logger.error(`Failed to disable pack rules`, disableError);
127
+ return { success: false, error: disableError.message };
128
+ }
129
+
130
+ // Mark pack as uninstalled
131
+ const { error: uninstallError } = await this.supabase
132
+ .from('pack_installations')
133
+ .update({ uninstalled_at: new Date().toISOString() })
134
+ .eq('user_id', userId)
135
+ .eq('pack_id', packId)
136
+ .is('uninstalled_at', null);
137
+
138
+ if (uninstallError) {
139
+ logger.error(`Failed to mark pack as uninstalled`, uninstallError);
140
+ return { success: false, error: uninstallError.message };
141
+ }
142
+
143
+ logger.info(`Successfully uninstalled pack '${packId}'`);
144
+ return { success: true };
145
+
146
+ } catch (error: any) {
147
+ logger.error(`Error uninstalling pack '${packId}'`, error);
148
+ return { success: false, error: error.message };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Get all installed packs for a user
154
+ */
155
+ async getInstalledPacks(userId: string): Promise<PackInstallation[]> {
156
+ const { data, error } = await this.supabase
157
+ .from('pack_installations')
158
+ .select('*')
159
+ .eq('user_id', userId)
160
+ .is('uninstalled_at', null)
161
+ .order('installed_at', { ascending: false });
162
+
163
+ if (error) {
164
+ logger.error('Failed to fetch installed packs', error);
165
+ return [];
166
+ }
167
+
168
+ return data || [];
169
+ }
170
+
171
+ /**
172
+ * Update user role and install appropriate packs
173
+ * This is the main entry point for onboarding
174
+ */
175
+ async updateUserRole(
176
+ userId: string,
177
+ role: UserRole,
178
+ source: 'onboarding' | 'manual' = 'onboarding'
179
+ ): Promise<{ success: boolean; packsInstalled: string[]; error?: string }> {
180
+ try {
181
+ logger.info(`Updating user role to '${role}' for user ${userId}`);
182
+
183
+ // 1. Update user settings
184
+ const { error: settingsError } = await this.supabase
185
+ .from('user_settings')
186
+ .update({
187
+ user_role: role,
188
+ onboarding_completed: true
189
+ })
190
+ .eq('user_id', userId);
191
+
192
+ if (settingsError) {
193
+ logger.error('Failed to update user settings', settingsError);
194
+ return { success: false, packsInstalled: [], error: settingsError.message };
195
+ }
196
+
197
+ // 2. Get packs for this role
198
+ const packs = getPacksForRole(role);
199
+ const packsInstalled: string[] = [];
200
+
201
+ // 3. Install each pack
202
+ for (const pack of packs) {
203
+ const result = await this.installPackWithSource(userId, pack.id, source);
204
+ if (result.success) {
205
+ packsInstalled.push(pack.id);
206
+ }
207
+ }
208
+
209
+ logger.info(`Successfully set up ${packsInstalled.length} packs for role '${role}'`);
210
+ return { success: true, packsInstalled };
211
+
212
+ } catch (error: any) {
213
+ logger.error(`Error updating user role`, error);
214
+ return { success: false, packsInstalled: [], error: error.message };
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Install a pack with a specific source tracking
220
+ */
221
+ private async installPackWithSource(
222
+ userId: string,
223
+ packId: string,
224
+ source: 'onboarding' | 'manual' | 'auto'
225
+ ): Promise<{ success: boolean; rulesCreated: number; error?: string }> {
226
+ const pack = getPackById(packId);
227
+ if (!pack) {
228
+ return { success: false, rulesCreated: 0, error: `Pack '${packId}' not found` };
229
+ }
230
+
231
+ // Check if already installed
232
+ const { data: existing } = await this.supabase
233
+ .from('pack_installations')
234
+ .select('*')
235
+ .eq('user_id', userId)
236
+ .eq('pack_id', packId)
237
+ .is('uninstalled_at', null)
238
+ .single();
239
+
240
+ if (existing) {
241
+ return { success: true, rulesCreated: 0 }; // Already installed
242
+ }
243
+
244
+ // Create rules
245
+ let rulesCreated = 0;
246
+ for (const template of pack.rules) {
247
+ const { data: existingRule } = await this.supabase
248
+ .from('rules')
249
+ .select('id')
250
+ .eq('user_id', userId)
251
+ .eq('rule_template_id', template.id)
252
+ .single();
253
+
254
+ if (existingRule) continue;
255
+
256
+ const { error } = await this.supabase
257
+ .from('rules')
258
+ .insert({
259
+ user_id: userId,
260
+ name: template.name,
261
+ description: template.description,
262
+ intent: template.intent,
263
+ priority: template.priority,
264
+ condition: template.condition as any,
265
+ actions: template.actions as any,
266
+ instructions: template.instructions,
267
+ is_enabled: template.is_enabled_by_default,
268
+ pack: packId,
269
+ rule_template_id: template.id,
270
+ is_system_managed: true
271
+ });
272
+
273
+ if (!error) rulesCreated++;
274
+ }
275
+
276
+ // Track installation
277
+ await this.supabase
278
+ .from('pack_installations')
279
+ .insert({
280
+ user_id: userId,
281
+ pack_id: packId,
282
+ source
283
+ });
284
+
285
+ return { success: true, rulesCreated };
286
+ }
287
+
288
+ /**
289
+ * Get pack metrics (how many users have it installed, etc.)
290
+ */
291
+ async getPackMetrics(packId: string): Promise<{
292
+ totalInstallations: number;
293
+ activeInstallations: number;
294
+ uninstallations: number;
295
+ }> {
296
+ const { data: all } = await this.supabase
297
+ .from('pack_installations')
298
+ .select('*')
299
+ .eq('pack_id', packId);
300
+
301
+ const total = all?.length || 0;
302
+ const active = all?.filter(p => !p.uninstalled_at).length || 0;
303
+ const uninstalled = all?.filter(p => p.uninstalled_at).length || 0;
304
+
305
+ return {
306
+ totalInstallations: total,
307
+ activeInstallations: active,
308
+ uninstallations: uninstalled
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Auto-install universal pack for new users
314
+ * Called during signup or first sync
315
+ */
316
+ async ensureUniversalPack(userId: string): Promise<{ success: boolean; installed: boolean }> {
317
+ try {
318
+ // Check if user already has universal pack
319
+ const { data: existing } = await this.supabase
320
+ .from('pack_installations')
321
+ .select('*')
322
+ .eq('user_id', userId)
323
+ .eq('pack_id', 'universal')
324
+ .is('uninstalled_at', null)
325
+ .single();
326
+
327
+ if (existing) {
328
+ return { success: true, installed: false }; // Already has it
329
+ }
330
+
331
+ // Install universal pack
332
+ const result = await this.installPackWithSource(userId, 'universal', 'auto');
333
+
334
+ logger.info(`Auto-installed Universal Pack for new user ${userId}`);
335
+ return { success: result.success, installed: true };
336
+
337
+ } catch (error: any) {
338
+ logger.error('Failed to ensure universal pack', error);
339
+ return { success: false, installed: false };
340
+ }
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Get singleton instance (convenience)
346
+ */
347
+ let defaultInstance: RulePackService | null = null;
348
+
349
+ export function getRulePackService(supabase: SupabaseClient): RulePackService {
350
+ if (!defaultInstance) {
351
+ defaultInstance = new RulePackService(supabase);
352
+ }
353
+ return defaultInstance;
354
+ }
@@ -161,9 +161,10 @@ export class SDKService {
161
161
 
162
162
  this.defaultChatProvider = {
163
163
  provider: preferredProvider.provider,
164
- model: preferredModel.id
164
+ model: preferredModel.id,
165
+ isDefaultFallback: false
165
166
  };
166
- logger.info(`Using preferred default chat provider: ${this.defaultChatProvider.provider}/${this.defaultChatProvider.model}`);
167
+ logger.info(`Using preferred chat provider: ${this.defaultChatProvider.provider}/${this.defaultChatProvider.model}`);
167
168
  return this.defaultChatProvider;
168
169
  }
169
170
 
@@ -172,7 +173,8 @@ export class SDKService {
172
173
  if (p.models && p.models.length > 0) {
173
174
  this.defaultChatProvider = {
174
175
  provider: p.provider,
175
- model: p.models[0].id
176
+ model: p.models[0].id,
177
+ isDefaultFallback: true
176
178
  };
177
179
  logger.info(`Defaulting to first available chat provider: ${this.defaultChatProvider.provider}/${this.defaultChatProvider.model}`);
178
180
  return this.defaultChatProvider;
@@ -221,9 +223,10 @@ export class SDKService {
221
223
  if (p.models && p.models.length > 0) {
222
224
  this.defaultEmbedProvider = {
223
225
  provider: p.provider,
224
- model: p.models[0].id
226
+ model: p.models[0].id,
227
+ isDefaultFallback: true
225
228
  };
226
- logger.info(`Default embed provider: ${this.defaultEmbedProvider.provider}/${this.defaultEmbedProvider.model}`);
229
+ logger.info(`Selected embed provider: ${this.defaultEmbedProvider.provider}/${this.defaultEmbedProvider.model}`);
227
230
  return this.defaultEmbedProvider;
228
231
  }
229
232
  }
@@ -245,7 +248,11 @@ export class SDKService {
245
248
  static async resolveChatProvider(settings: { llm_provider?: string; llm_model?: string }): Promise<ProviderResult> {
246
249
  // If both provider and model are set in settings, use them
247
250
  if (settings.llm_provider && settings.llm_model) {
248
- return { provider: settings.llm_provider, model: settings.llm_model };
251
+ return {
252
+ provider: settings.llm_provider,
253
+ model: settings.llm_model,
254
+ isDefaultFallback: false
255
+ };
249
256
  }
250
257
 
251
258
  // Try to get from SDK discovery first
@@ -258,7 +265,11 @@ export class SDKService {
258
265
  static async resolveEmbedProvider(settings: { embedding_provider?: string; embedding_model?: string }): Promise<ProviderResult> {
259
266
  // If both provider and model are set in settings, use them
260
267
  if (settings.embedding_provider && settings.embedding_model) {
261
- return { provider: settings.embedding_provider, model: settings.embedding_model };
268
+ return {
269
+ provider: settings.embedding_provider,
270
+ model: settings.embedding_model,
271
+ isDefaultFallback: false
272
+ };
262
273
  }
263
274
 
264
275
  // Try to get from SDK discovery first