@yasserkhanorg/e2e-agents 0.3.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.
Files changed (221) hide show
  1. package/LICENSE +168 -0
  2. package/README.md +620 -0
  3. package/dist/agent/analysis.d.ts +62 -0
  4. package/dist/agent/analysis.d.ts.map +1 -0
  5. package/dist/agent/analysis.js +292 -0
  6. package/dist/agent/blast_radius.d.ts +4 -0
  7. package/dist/agent/blast_radius.d.ts.map +1 -0
  8. package/dist/agent/blast_radius.js +37 -0
  9. package/dist/agent/cache_utils.d.ts +38 -0
  10. package/dist/agent/cache_utils.d.ts.map +1 -0
  11. package/dist/agent/cache_utils.js +67 -0
  12. package/dist/agent/config.d.ts +148 -0
  13. package/dist/agent/config.d.ts.map +1 -0
  14. package/dist/agent/config.js +640 -0
  15. package/dist/agent/dependency_graph.d.ts +14 -0
  16. package/dist/agent/dependency_graph.d.ts.map +1 -0
  17. package/dist/agent/dependency_graph.js +227 -0
  18. package/dist/agent/feedback.d.ts +55 -0
  19. package/dist/agent/feedback.d.ts.map +1 -0
  20. package/dist/agent/feedback.js +257 -0
  21. package/dist/agent/flags.d.ts +23 -0
  22. package/dist/agent/flags.d.ts.map +1 -0
  23. package/dist/agent/flags.js +171 -0
  24. package/dist/agent/flow_catalog.d.ts +25 -0
  25. package/dist/agent/flow_catalog.d.ts.map +1 -0
  26. package/dist/agent/flow_catalog.js +106 -0
  27. package/dist/agent/flow_mapping.d.ts +10 -0
  28. package/dist/agent/flow_mapping.d.ts.map +1 -0
  29. package/dist/agent/flow_mapping.js +84 -0
  30. package/dist/agent/framework.d.ts +13 -0
  31. package/dist/agent/framework.d.ts.map +1 -0
  32. package/dist/agent/framework.js +149 -0
  33. package/dist/agent/gap_suggestions.d.ts +14 -0
  34. package/dist/agent/gap_suggestions.d.ts.map +1 -0
  35. package/dist/agent/gap_suggestions.js +101 -0
  36. package/dist/agent/generator.d.ts +10 -0
  37. package/dist/agent/generator.d.ts.map +1 -0
  38. package/dist/agent/generator.js +115 -0
  39. package/dist/agent/git.d.ts +11 -0
  40. package/dist/agent/git.d.ts.map +1 -0
  41. package/dist/agent/git.js +90 -0
  42. package/dist/agent/handoff.d.ts +22 -0
  43. package/dist/agent/handoff.d.ts.map +1 -0
  44. package/dist/agent/handoff.js +180 -0
  45. package/dist/agent/impact-analyzer.d.ts +114 -0
  46. package/dist/agent/impact-analyzer.d.ts.map +1 -0
  47. package/dist/agent/impact-analyzer.js +557 -0
  48. package/dist/agent/index.d.ts +21 -0
  49. package/dist/agent/index.d.ts.map +1 -0
  50. package/dist/agent/index.js +38 -0
  51. package/dist/agent/model-router.d.ts +57 -0
  52. package/dist/agent/model-router.d.ts.map +1 -0
  53. package/dist/agent/model-router.js +154 -0
  54. package/dist/agent/operational_insights.d.ts +41 -0
  55. package/dist/agent/operational_insights.d.ts.map +1 -0
  56. package/dist/agent/operational_insights.js +126 -0
  57. package/dist/agent/pipeline.d.ts +23 -0
  58. package/dist/agent/pipeline.d.ts.map +1 -0
  59. package/dist/agent/pipeline.js +609 -0
  60. package/dist/agent/plan.d.ts +91 -0
  61. package/dist/agent/plan.d.ts.map +1 -0
  62. package/dist/agent/plan.js +331 -0
  63. package/dist/agent/playwright_report.d.ts +8 -0
  64. package/dist/agent/playwright_report.d.ts.map +1 -0
  65. package/dist/agent/playwright_report.js +126 -0
  66. package/dist/agent/report-generator.d.ts +24 -0
  67. package/dist/agent/report-generator.d.ts.map +1 -0
  68. package/dist/agent/report-generator.js +250 -0
  69. package/dist/agent/report.d.ts +81 -0
  70. package/dist/agent/report.d.ts.map +1 -0
  71. package/dist/agent/report.js +147 -0
  72. package/dist/agent/runner.d.ts +7 -0
  73. package/dist/agent/runner.d.ts.map +1 -0
  74. package/dist/agent/runner.js +576 -0
  75. package/dist/agent/selectors.d.ts +10 -0
  76. package/dist/agent/selectors.d.ts.map +1 -0
  77. package/dist/agent/selectors.js +75 -0
  78. package/dist/agent/spec-bridge.d.ts +101 -0
  79. package/dist/agent/spec-bridge.d.ts.map +1 -0
  80. package/dist/agent/spec-bridge.js +273 -0
  81. package/dist/agent/spec-builder.d.ts +102 -0
  82. package/dist/agent/spec-builder.d.ts.map +1 -0
  83. package/dist/agent/spec-builder.js +273 -0
  84. package/dist/agent/subsystem_risk.d.ts +23 -0
  85. package/dist/agent/subsystem_risk.d.ts.map +1 -0
  86. package/dist/agent/subsystem_risk.js +207 -0
  87. package/dist/agent/telemetry.d.ts +84 -0
  88. package/dist/agent/telemetry.d.ts.map +1 -0
  89. package/dist/agent/telemetry.js +220 -0
  90. package/dist/agent/test_path.d.ts +2 -0
  91. package/dist/agent/test_path.d.ts.map +1 -0
  92. package/dist/agent/test_path.js +23 -0
  93. package/dist/agent/tests.d.ts +18 -0
  94. package/dist/agent/tests.d.ts.map +1 -0
  95. package/dist/agent/tests.js +106 -0
  96. package/dist/agent/traceability.d.ts +22 -0
  97. package/dist/agent/traceability.d.ts.map +1 -0
  98. package/dist/agent/traceability.js +183 -0
  99. package/dist/agent/traceability_capture.d.ts +18 -0
  100. package/dist/agent/traceability_capture.d.ts.map +1 -0
  101. package/dist/agent/traceability_capture.js +313 -0
  102. package/dist/agent/traceability_ingest.d.ts +21 -0
  103. package/dist/agent/traceability_ingest.d.ts.map +1 -0
  104. package/dist/agent/traceability_ingest.js +237 -0
  105. package/dist/agent/utils.d.ts +13 -0
  106. package/dist/agent/utils.d.ts.map +1 -0
  107. package/dist/agent/utils.js +152 -0
  108. package/dist/agent/validators/selector-validator.d.ts +74 -0
  109. package/dist/agent/validators/selector-validator.d.ts.map +1 -0
  110. package/dist/agent/validators/selector-validator.js +165 -0
  111. package/dist/anthropic_provider.d.ts +65 -0
  112. package/dist/anthropic_provider.d.ts.map +1 -0
  113. package/dist/anthropic_provider.js +332 -0
  114. package/dist/api.d.ts +48 -0
  115. package/dist/api.d.ts.map +1 -0
  116. package/dist/api.js +113 -0
  117. package/dist/base_provider.d.ts +53 -0
  118. package/dist/base_provider.d.ts.map +1 -0
  119. package/dist/base_provider.js +81 -0
  120. package/dist/cli.d.ts +3 -0
  121. package/dist/cli.d.ts.map +1 -0
  122. package/dist/cli.js +843 -0
  123. package/dist/custom_provider.d.ts +20 -0
  124. package/dist/custom_provider.d.ts.map +1 -0
  125. package/dist/custom_provider.js +276 -0
  126. package/dist/e2e-test-gen/index.d.ts +51 -0
  127. package/dist/e2e-test-gen/index.d.ts.map +1 -0
  128. package/dist/e2e-test-gen/index.js +57 -0
  129. package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
  130. package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
  131. package/dist/e2e-test-gen/spec_parser.js +786 -0
  132. package/dist/e2e-test-gen/types.d.ts +185 -0
  133. package/dist/e2e-test-gen/types.d.ts.map +1 -0
  134. package/dist/e2e-test-gen/types.js +4 -0
  135. package/dist/esm/agent/analysis.js +287 -0
  136. package/dist/esm/agent/blast_radius.js +34 -0
  137. package/dist/esm/agent/cache_utils.js +63 -0
  138. package/dist/esm/agent/config.js +637 -0
  139. package/dist/esm/agent/dependency_graph.js +224 -0
  140. package/dist/esm/agent/feedback.js +253 -0
  141. package/dist/esm/agent/flags.js +160 -0
  142. package/dist/esm/agent/flow_catalog.js +103 -0
  143. package/dist/esm/agent/flow_mapping.js +81 -0
  144. package/dist/esm/agent/framework.js +145 -0
  145. package/dist/esm/agent/gap_suggestions.js +98 -0
  146. package/dist/esm/agent/generator.js +112 -0
  147. package/dist/esm/agent/git.js +87 -0
  148. package/dist/esm/agent/handoff.js +177 -0
  149. package/dist/esm/agent/impact-analyzer.js +548 -0
  150. package/dist/esm/agent/index.js +22 -0
  151. package/dist/esm/agent/model-router.js +150 -0
  152. package/dist/esm/agent/operational_insights.js +123 -0
  153. package/dist/esm/agent/pipeline.js +605 -0
  154. package/dist/esm/agent/plan.js +324 -0
  155. package/dist/esm/agent/playwright_report.js +123 -0
  156. package/dist/esm/agent/report-generator.js +247 -0
  157. package/dist/esm/agent/report.js +144 -0
  158. package/dist/esm/agent/runner.js +572 -0
  159. package/dist/esm/agent/selectors.js +71 -0
  160. package/dist/esm/agent/spec-bridge.js +267 -0
  161. package/dist/esm/agent/spec-builder.js +267 -0
  162. package/dist/esm/agent/subsystem_risk.js +204 -0
  163. package/dist/esm/agent/telemetry.js +216 -0
  164. package/dist/esm/agent/test_path.js +20 -0
  165. package/dist/esm/agent/tests.js +101 -0
  166. package/dist/esm/agent/traceability.js +180 -0
  167. package/dist/esm/agent/traceability_capture.js +310 -0
  168. package/dist/esm/agent/traceability_ingest.js +234 -0
  169. package/dist/esm/agent/utils.js +138 -0
  170. package/dist/esm/agent/validators/selector-validator.js +160 -0
  171. package/dist/esm/anthropic_provider.js +324 -0
  172. package/dist/esm/api.js +105 -0
  173. package/dist/esm/base_provider.js +77 -0
  174. package/dist/esm/cli.js +841 -0
  175. package/dist/esm/custom_provider.js +272 -0
  176. package/dist/esm/e2e-test-gen/index.js +50 -0
  177. package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
  178. package/dist/esm/e2e-test-gen/types.js +3 -0
  179. package/dist/esm/index.js +16 -0
  180. package/dist/esm/logger.js +89 -0
  181. package/dist/esm/mcp-server.js +465 -0
  182. package/dist/esm/ollama_provider.js +300 -0
  183. package/dist/esm/openai_provider.js +242 -0
  184. package/dist/esm/package.json +3 -0
  185. package/dist/esm/plan-and-test-constants.js +126 -0
  186. package/dist/esm/provider_factory.js +336 -0
  187. package/dist/esm/provider_interface.js +23 -0
  188. package/dist/esm/provider_utils.js +96 -0
  189. package/dist/index.d.ts +31 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +41 -0
  192. package/dist/logger.d.ts +23 -0
  193. package/dist/logger.d.ts.map +1 -0
  194. package/dist/logger.js +93 -0
  195. package/dist/mcp-server.d.ts +35 -0
  196. package/dist/mcp-server.d.ts.map +1 -0
  197. package/dist/mcp-server.js +469 -0
  198. package/dist/ollama_provider.d.ts +65 -0
  199. package/dist/ollama_provider.d.ts.map +1 -0
  200. package/dist/ollama_provider.js +308 -0
  201. package/dist/openai_provider.d.ts +23 -0
  202. package/dist/openai_provider.d.ts.map +1 -0
  203. package/dist/openai_provider.js +250 -0
  204. package/dist/plan-and-test-constants.d.ts +110 -0
  205. package/dist/plan-and-test-constants.d.ts.map +1 -0
  206. package/dist/plan-and-test-constants.js +132 -0
  207. package/dist/provider_factory.d.ts +99 -0
  208. package/dist/provider_factory.d.ts.map +1 -0
  209. package/dist/provider_factory.js +341 -0
  210. package/dist/provider_interface.d.ts +358 -0
  211. package/dist/provider_interface.d.ts.map +1 -0
  212. package/dist/provider_interface.js +28 -0
  213. package/dist/provider_utils.d.ts +39 -0
  214. package/dist/provider_utils.d.ts.map +1 -0
  215. package/dist/provider_utils.js +103 -0
  216. package/package.json +101 -0
  217. package/schemas/gap.schema.json +18 -0
  218. package/schemas/impact.schema.json +418 -0
  219. package/schemas/plan.schema.json +285 -0
  220. package/schemas/subsystem-risk-map.schema.json +62 -0
  221. package/schemas/traceability-input.schema.json +122 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-and-test-constants.d.ts","sourceRoot":"","sources":["../src/plan-and-test-constants.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;CAsBlB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;4CAOA,MAAM;;;sCAIZ,MAAM;qCACP,MAAM;4CACC,MAAM;;;uCAIX,MAAM;yCACJ,MAAM;;;4CAIH,MAAM;0CACR,MAAM;;;yCAIP,MAAM;8CACD,MAAM,QAAQ,MAAM;;;qCAI7B,MAAM,SAAS,MAAM;4CACd,MAAM;;;;;0CAMR,MAAM;0CACN,MAAM,SAAS,MAAM;0CACrB,MAAM;;;;;gDAWA,MAAM,EAAE;0CACd,MAAM,OAAO,MAAM;mDACV,MAAM,OAAO,MAAM;;;CAGnD,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAEX;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C"}
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
+ // See LICENSE.txt for license information.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.FLOW_GROUP_TYPES = exports.TEST_STRATEGIES = exports.PRIORITY_LEVELS = exports.PLAN_AND_TEST_MESSAGES = exports.PLANNING_CONFIG = void 0;
6
+ exports.getScenarioCount = getScenarioCount;
7
+ exports.isValidPriority = isValidPriority;
8
+ exports.getValidPriorities = getValidPriorities;
9
+ /**
10
+ * Centralized constants for plan-and-test command
11
+ * Eliminates magic strings and hardcoded values
12
+ * Makes configuration easy to modify and maintain
13
+ */
14
+ exports.PLANNING_CONFIG = {
15
+ // Number of test scenarios per priority level
16
+ SCENARIO_COUNTS: {
17
+ P0: 3,
18
+ P1: 2,
19
+ P2: 1,
20
+ },
21
+ // Default parameter values
22
+ DEFAULTS: {
23
+ MAX_TESTS: 10,
24
+ COVERAGE_THRESHOLD: 50,
25
+ PRIORITY_FILTER: ['P0', 'P1'],
26
+ },
27
+ // Limit constraints
28
+ LIMITS: {
29
+ MIN_MAX_TESTS: 1,
30
+ MAX_MAX_TESTS: 100,
31
+ MIN_COVERAGE_THRESHOLD: 0,
32
+ MAX_COVERAGE_THRESHOLD: 100,
33
+ },
34
+ };
35
+ /**
36
+ * Display messages for plan-and-test command
37
+ */
38
+ exports.PLAN_AND_TEST_MESSAGES = {
39
+ HEADER: {
40
+ MAIN: '🚀 Planning and Generating Tests',
41
+ STEP_1_ANALYSIS: '📊 Step 1: Analyzing Code Changes...',
42
+ STEP_2_PLANNING: '💡 Step 2: Planning Test Generation...',
43
+ STEP_3_GENERATION: '⚡ Step 3: Generating Tests...',
44
+ STEP_4_SUMMARY: '📈 Generation Summary',
45
+ FOUND_FLOW_GROUPS: (count) => `📦 Found ${count} flow groups (end-to-end journeys):`,
46
+ },
47
+ ANALYSIS: {
48
+ FOUND_FLOWS: (count) => `✓ Found ${count} affected flows`,
49
+ MORE_FLOWS: (count) => ` ... and ${count} more`,
50
+ FOUND_FLOW_GROUPS: (count) => `📦 Found ${count} flow groups (end-to-end journeys)`,
51
+ },
52
+ PLANNING: {
53
+ PLAN_CREATED: (count) => `✓ Plan created: ${count} flows to test`,
54
+ SKIPPING_COUNT: (count) => `⊘ Skipping ${count} flows:`,
55
+ },
56
+ SKIP_REASONS: {
57
+ MAX_LIMIT_REACHED: (limit) => `Max tests limit reached (${limit})`,
58
+ ALREADY_COVERED: (count) => `Already covered (${count} existing tests)`,
59
+ },
60
+ COVERAGE_REASONS: {
61
+ NO_COVERAGE: (priority) => `${priority} - no coverage`,
62
+ PARTIAL_COVERAGE: (priority, gaps) => `${priority} - partial coverage (${gaps} gaps)`,
63
+ },
64
+ EXECUTION: {
65
+ TEST_COUNT: (index, total) => `[${index}/${total}]`,
66
+ GENERATION_FAILED: (error) => ` ⚠️ Generation failed: ${error}`,
67
+ DRY_RUN_MODE: '📋 DRY RUN: Not executing. Run without --dry-run to generate tests.',
68
+ },
69
+ SUMMARY: {
70
+ SEPARATOR: '═'.repeat(50),
71
+ TOTAL_GENERATED: (count) => `Total Tests Generated: ${count}`,
72
+ SUCCESSFUL: (successful, total) => `Successful: ${successful}/${total}`,
73
+ TOTAL_SCENARIOS: (count) => `Total Scenarios: ${count}`,
74
+ COMPLETION: '✅ Execution complete!',
75
+ },
76
+ NEXT_STEPS: [
77
+ ' • Run tests: npx playwright test --grep @smoke',
78
+ ' • Re-run impact: npx e2e-ai-agents impact --path <app-root> --tests-root <tests-root>',
79
+ ' • Check coverage: npm run test:impact',
80
+ ],
81
+ ERRORS: {
82
+ INVALID_PRIORITY: (priorities) => `Invalid priority levels: ${priorities.join(', ')}`,
83
+ INVALID_MAX_TESTS: (min, max) => `maxTests must be between ${min} and ${max}`,
84
+ INVALID_COVERAGE_THRESHOLD: (min, max) => `coverageThreshold must be between ${min} and ${max}`,
85
+ NO_CHANGES_DETECTED: '✓ No significant changes detected',
86
+ },
87
+ };
88
+ /**
89
+ * Priority levels in the system
90
+ */
91
+ exports.PRIORITY_LEVELS = {
92
+ CRITICAL: 'P0',
93
+ HIGH: 'P1',
94
+ MEDIUM: 'P2',
95
+ };
96
+ /**
97
+ * Test strategy types for flow groups
98
+ */
99
+ exports.TEST_STRATEGIES = {
100
+ SEQUENTIAL: 'sequential',
101
+ PARALLEL: 'parallel',
102
+ MIXED: 'mixed',
103
+ };
104
+ /**
105
+ * Flow group types
106
+ */
107
+ exports.FLOW_GROUP_TYPES = {
108
+ MESSAGING_LIFECYCLE: 'messaging-lifecycle',
109
+ CHANNEL_MANAGEMENT: 'channel-management',
110
+ MESSAGING_INTERACTIONS: 'messaging-interactions',
111
+ };
112
+ /**
113
+ * Utility function to get scenario count for a priority
114
+ * Provides type-safe access to scenario counts
115
+ */
116
+ function getScenarioCount(priority) {
117
+ const normalizedPriority = priority.toUpperCase();
118
+ return (exports.PLANNING_CONFIG.SCENARIO_COUNTS[normalizedPriority] || exports.PLANNING_CONFIG.SCENARIO_COUNTS.P1);
119
+ }
120
+ /**
121
+ * Utility function to validate priority level
122
+ */
123
+ function isValidPriority(priority) {
124
+ const validPriorities = Object.values(exports.PRIORITY_LEVELS);
125
+ return validPriorities.includes(priority.toUpperCase());
126
+ }
127
+ /**
128
+ * Utility function to get all valid priority levels
129
+ */
130
+ function getValidPriorities() {
131
+ return Object.values(exports.PRIORITY_LEVELS);
132
+ }
@@ -0,0 +1,99 @@
1
+ import type { LLMProvider, ProviderConfig } from './provider_interface.js';
2
+ /**
3
+ * LLM Provider Factory
4
+ *
5
+ * Creates and configures LLM providers based on configuration.
6
+ * Supports multiple strategies:
7
+ * - Single provider (Ollama, Anthropic, etc.)
8
+ * - Hybrid provider (free primary + premium fallback)
9
+ * - Auto-selection based on environment
10
+ *
11
+ * Usage:
12
+ *
13
+ * // Create single provider
14
+ * const provider = LLMProviderFactory.create({
15
+ * type: 'ollama',
16
+ * config: { model: 'deepseek-r1:7b' }
17
+ * });
18
+ *
19
+ * // Create hybrid provider
20
+ * const provider = LLMProviderFactory.createHybrid({
21
+ * primary: { type: 'ollama', config: { model: 'deepseek-r1:7b' } },
22
+ * fallback: { type: 'anthropic', config: { apiKey: '...' } },
23
+ * useFallbackFor: ['vision']
24
+ * });
25
+ *
26
+ * // Auto-detect from environment
27
+ * const provider = LLMProviderFactory.createFromEnv();
28
+ */
29
+ export declare class LLMProviderFactory {
30
+ /**
31
+ * Create a single LLM provider
32
+ */
33
+ static create(config: ProviderConfig): LLMProvider;
34
+ /**
35
+ * Create a hybrid provider (free primary + premium fallback)
36
+ *
37
+ * Use cases:
38
+ * - Most operations use free Ollama
39
+ * - Vision tasks fall back to Claude
40
+ * - Complex diagnosis falls back to Claude
41
+ *
42
+ * This gives best cost/quality balance:
43
+ * - ~$20/month instead of $80/month (75% cost reduction)
44
+ * - Still get premium quality for vision and complex tasks
45
+ */
46
+ static createHybrid(config: HybridConfig): LLMProvider;
47
+ /**
48
+ * Auto-detect provider from environment variables
49
+ *
50
+ * Priority:
51
+ * 1. LLM_PROVIDER env var (ollama, anthropic, openai)
52
+ * 2. ANTHROPIC_API_KEY exists → Anthropic
53
+ * 3. OPENAI_API_KEY exists → OpenAI
54
+ * 4. Ollama running locally → Ollama
55
+ * 5. Error (no provider available)
56
+ */
57
+ static createFromEnv(): Promise<LLMProvider>;
58
+ /**
59
+ * Create provider from simple string format
60
+ *
61
+ * Examples:
62
+ * - "ollama" → Ollama with defaults
63
+ * - "ollama:deepseek-r1:14b" → Ollama with specific model
64
+ * - "anthropic" → Anthropic with env API key
65
+ * - "anthropic:claude-opus-4-5" → Anthropic with specific model
66
+ * - "openai" → OpenAI with env API key
67
+ * - "openai:gpt-4" → OpenAI with specific model
68
+ */
69
+ static createFromString(providerString: string): LLMProvider;
70
+ }
71
+ /**
72
+ * Hybrid Provider Configuration
73
+ */
74
+ export interface HybridConfig {
75
+ /**
76
+ * Primary provider (used for most operations)
77
+ * Typically a free provider like Ollama
78
+ */
79
+ primary: ProviderConfig;
80
+ /**
81
+ * Fallback provider (used for specific capabilities)
82
+ * Typically a premium provider like Anthropic
83
+ */
84
+ fallback: ProviderConfig;
85
+ /**
86
+ * When to use fallback provider
87
+ * Options: 'vision', 'complex-diagnosis', 'high-confidence-needed'
88
+ */
89
+ useFallbackFor?: Array<'vision' | 'complex-diagnosis' | 'high-confidence-needed'>;
90
+ }
91
+ /**
92
+ * Helper to validate provider setup
93
+ */
94
+ export declare function validateProviderSetup(provider: LLMProvider): Promise<{
95
+ valid: boolean;
96
+ message: string;
97
+ capabilities: string[];
98
+ }>;
99
+ //# sourceMappingURL=provider_factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider_factory.d.ts","sourceRoot":"","sources":["../src/provider_factory.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAKR,WAAW,EAIX,cAAc,EAEjB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,kBAAkB;IAC3B;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW;IAmBlD;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAWtD;;;;;;;;;OASG;WACU,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC;IAqElD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW;CAkC/D;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,OAAO,EAAE,cAAc,CAAC;IAExB;;;OAGG;IACH,QAAQ,EAAE,cAAc,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,mBAAmB,GAAG,wBAAwB,CAAC,CAAC;CACrF;AAqJD;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC;IACxE,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC,CAsCD"}
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
+ // See LICENSE.txt for license information.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.LLMProviderFactory = void 0;
6
+ exports.validateProviderSetup = validateProviderSetup;
7
+ const anthropic_provider_js_1 = require("./anthropic_provider.js");
8
+ const custom_provider_js_1 = require("./custom_provider.js");
9
+ const ollama_provider_js_1 = require("./ollama_provider.js");
10
+ const openai_provider_js_1 = require("./openai_provider.js");
11
+ const provider_interface_js_1 = require("./provider_interface.js");
12
+ /**
13
+ * LLM Provider Factory
14
+ *
15
+ * Creates and configures LLM providers based on configuration.
16
+ * Supports multiple strategies:
17
+ * - Single provider (Ollama, Anthropic, etc.)
18
+ * - Hybrid provider (free primary + premium fallback)
19
+ * - Auto-selection based on environment
20
+ *
21
+ * Usage:
22
+ *
23
+ * // Create single provider
24
+ * const provider = LLMProviderFactory.create({
25
+ * type: 'ollama',
26
+ * config: { model: 'deepseek-r1:7b' }
27
+ * });
28
+ *
29
+ * // Create hybrid provider
30
+ * const provider = LLMProviderFactory.createHybrid({
31
+ * primary: { type: 'ollama', config: { model: 'deepseek-r1:7b' } },
32
+ * fallback: { type: 'anthropic', config: { apiKey: '...' } },
33
+ * useFallbackFor: ['vision']
34
+ * });
35
+ *
36
+ * // Auto-detect from environment
37
+ * const provider = LLMProviderFactory.createFromEnv();
38
+ */
39
+ class LLMProviderFactory {
40
+ /**
41
+ * Create a single LLM provider
42
+ */
43
+ static create(config) {
44
+ switch (config.type) {
45
+ case 'ollama':
46
+ return new ollama_provider_js_1.OllamaProvider(config.config);
47
+ case 'anthropic':
48
+ return new anthropic_provider_js_1.AnthropicProvider(config.config);
49
+ case 'openai':
50
+ return new openai_provider_js_1.OpenAIProvider(config.config);
51
+ case 'custom':
52
+ return new custom_provider_js_1.CustomProvider(config.config);
53
+ default:
54
+ throw new Error(`Unknown provider type: ${config.type}`);
55
+ }
56
+ }
57
+ /**
58
+ * Create a hybrid provider (free primary + premium fallback)
59
+ *
60
+ * Use cases:
61
+ * - Most operations use free Ollama
62
+ * - Vision tasks fall back to Claude
63
+ * - Complex diagnosis falls back to Claude
64
+ *
65
+ * This gives best cost/quality balance:
66
+ * - ~$20/month instead of $80/month (75% cost reduction)
67
+ * - Still get premium quality for vision and complex tasks
68
+ */
69
+ static createHybrid(config) {
70
+ const primary = this.create(config.primary);
71
+ const fallback = this.create(config.fallback);
72
+ return new HybridProvider({
73
+ primary,
74
+ fallback,
75
+ useFallbackFor: config.useFallbackFor || ['vision'],
76
+ });
77
+ }
78
+ /**
79
+ * Auto-detect provider from environment variables
80
+ *
81
+ * Priority:
82
+ * 1. LLM_PROVIDER env var (ollama, anthropic, openai)
83
+ * 2. ANTHROPIC_API_KEY exists → Anthropic
84
+ * 3. OPENAI_API_KEY exists → OpenAI
85
+ * 4. Ollama running locally → Ollama
86
+ * 5. Error (no provider available)
87
+ */
88
+ static async createFromEnv() {
89
+ const providerType = process.env.LLM_PROVIDER?.toLowerCase();
90
+ if (providerType === 'ollama') {
91
+ return new ollama_provider_js_1.OllamaProvider({
92
+ baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434/v1',
93
+ model: process.env.OLLAMA_MODEL || 'deepseek-r1:7b',
94
+ });
95
+ }
96
+ if (providerType === 'openai') {
97
+ if (!process.env.OPENAI_API_KEY) {
98
+ throw new Error('OPENAI_API_KEY environment variable is required for OpenAI provider');
99
+ }
100
+ return new openai_provider_js_1.OpenAIProvider({
101
+ apiKey: process.env.OPENAI_API_KEY,
102
+ model: process.env.OPENAI_MODEL || 'gpt-4',
103
+ baseUrl: process.env.OPENAI_BASE_URL,
104
+ organizationId: process.env.OPENAI_ORG_ID,
105
+ });
106
+ }
107
+ if (providerType === 'anthropic') {
108
+ if (!process.env.ANTHROPIC_API_KEY) {
109
+ throw new Error('ANTHROPIC_API_KEY environment variable is required for Anthropic provider');
110
+ }
111
+ return new anthropic_provider_js_1.AnthropicProvider({
112
+ apiKey: process.env.ANTHROPIC_API_KEY,
113
+ model: process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-5-20250929',
114
+ });
115
+ }
116
+ if (process.env.ANTHROPIC_API_KEY) {
117
+ return new anthropic_provider_js_1.AnthropicProvider({
118
+ apiKey: process.env.ANTHROPIC_API_KEY,
119
+ model: process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-5-20250929',
120
+ });
121
+ }
122
+ if (process.env.OPENAI_API_KEY) {
123
+ return new openai_provider_js_1.OpenAIProvider({
124
+ apiKey: process.env.OPENAI_API_KEY,
125
+ model: process.env.OPENAI_MODEL || 'gpt-4',
126
+ baseUrl: process.env.OPENAI_BASE_URL,
127
+ organizationId: process.env.OPENAI_ORG_ID,
128
+ });
129
+ }
130
+ // Try Ollama as default
131
+ const ollama = new ollama_provider_js_1.OllamaProvider({});
132
+ const health = await ollama.checkHealth();
133
+ if (health.healthy) {
134
+ // eslint-disable-next-line no-console
135
+ console.log('Auto-detected Ollama provider (free, local)');
136
+ return ollama;
137
+ }
138
+ throw new Error('No LLM provider available. Please either:\n' +
139
+ '1. Install Ollama: curl -fsSL https://ollama.com/install.sh | sh\n' +
140
+ '2. Set ANTHROPIC_API_KEY environment variable\n' +
141
+ '3. Set OPENAI_API_KEY environment variable\n' +
142
+ '4. Set LLM_PROVIDER environment variable');
143
+ }
144
+ /**
145
+ * Create provider from simple string format
146
+ *
147
+ * Examples:
148
+ * - "ollama" → Ollama with defaults
149
+ * - "ollama:deepseek-r1:14b" → Ollama with specific model
150
+ * - "anthropic" → Anthropic with env API key
151
+ * - "anthropic:claude-opus-4-5" → Anthropic with specific model
152
+ * - "openai" → OpenAI with env API key
153
+ * - "openai:gpt-4" → OpenAI with specific model
154
+ */
155
+ static createFromString(providerString) {
156
+ const [type, ...modelParts] = providerString.split(':');
157
+ const model = modelParts.join(':');
158
+ switch (type.toLowerCase()) {
159
+ case 'ollama':
160
+ return new ollama_provider_js_1.OllamaProvider({
161
+ model: model || 'deepseek-r1:7b',
162
+ });
163
+ case 'anthropic':
164
+ if (!process.env.ANTHROPIC_API_KEY) {
165
+ throw new Error('ANTHROPIC_API_KEY environment variable is required');
166
+ }
167
+ return new anthropic_provider_js_1.AnthropicProvider({
168
+ apiKey: process.env.ANTHROPIC_API_KEY,
169
+ model: model || 'claude-sonnet-4-5-20250929',
170
+ });
171
+ case 'openai':
172
+ if (!process.env.OPENAI_API_KEY) {
173
+ throw new Error('OPENAI_API_KEY environment variable is required');
174
+ }
175
+ return new openai_provider_js_1.OpenAIProvider({
176
+ apiKey: process.env.OPENAI_API_KEY,
177
+ model: model || 'gpt-4',
178
+ baseUrl: process.env.OPENAI_BASE_URL,
179
+ organizationId: process.env.OPENAI_ORG_ID,
180
+ });
181
+ default:
182
+ throw new Error(`Unknown provider type: ${type}`);
183
+ }
184
+ }
185
+ }
186
+ exports.LLMProviderFactory = LLMProviderFactory;
187
+ /**
188
+ * Hybrid Provider - Mix free and premium providers
189
+ *
190
+ * Strategy:
191
+ * - Use free provider (Ollama) for most operations (~80% of requests)
192
+ * - Fall back to premium (Claude) only when needed (~20% of requests)
193
+ *
194
+ * Cost savings example:
195
+ * - Pure Claude: $80/month
196
+ * - Pure Ollama: $0/month but no vision
197
+ * - Hybrid: $20/month (75% cost reduction, keeps vision)
198
+ */
199
+ class HybridProvider {
200
+ constructor(config) {
201
+ this.name = 'hybrid';
202
+ this.capabilities = {
203
+ // Report combined capabilities
204
+ vision: true, // Fallback provides vision
205
+ streaming: true, // Both support streaming
206
+ maxTokens: 0, // Will be set in constructor
207
+ costPer1MInputTokens: 0, // Variable cost
208
+ costPer1MOutputTokens: 0, // Variable cost
209
+ supportsTools: true,
210
+ supportsPromptCaching: false,
211
+ typicalResponseTimeMs: 0, // Variable
212
+ };
213
+ this.primary = config.primary;
214
+ this.fallback = config.fallback;
215
+ this.useFallbackFor = new Set(config.useFallbackFor);
216
+ // Set combined capabilities
217
+ this.capabilities.maxTokens = Math.max(this.primary.capabilities.maxTokens, this.fallback.capabilities.maxTokens);
218
+ this.capabilities.typicalResponseTimeMs = this.primary.capabilities.typicalResponseTimeMs;
219
+ }
220
+ async generateText(prompt, options) {
221
+ // Use primary for text generation (free)
222
+ // eslint-disable-next-line no-console
223
+ console.log(`[Hybrid] Using ${this.primary.name} for text generation`);
224
+ return await this.primary.generateText(prompt, options);
225
+ }
226
+ async analyzeImage(images, prompt, options) {
227
+ // Check if vision is a fallback trigger
228
+ if (this.useFallbackFor.has('vision')) {
229
+ // Use fallback if primary doesn't support vision
230
+ if (!this.primary.capabilities.vision) {
231
+ // eslint-disable-next-line no-console
232
+ console.log(`[Hybrid] Using ${this.fallback.name} for vision analysis (primary doesn't support vision)`);
233
+ if (!this.fallback.analyzeImage) {
234
+ throw new provider_interface_js_1.UnsupportedCapabilityError(this.name, 'vision');
235
+ }
236
+ return await this.fallback.analyzeImage(images, prompt, options);
237
+ }
238
+ }
239
+ // Try primary first
240
+ if (this.primary.analyzeImage) {
241
+ // eslint-disable-next-line no-console
242
+ console.log(`[Hybrid] Using ${this.primary.name} for vision analysis`);
243
+ return await this.primary.analyzeImage(images, prompt, options);
244
+ }
245
+ throw new provider_interface_js_1.UnsupportedCapabilityError(this.name, 'vision');
246
+ }
247
+ async *streamText(prompt, options) {
248
+ // Use primary for streaming (free)
249
+ if (!this.primary.streamText) {
250
+ throw new provider_interface_js_1.UnsupportedCapabilityError(this.primary.name, 'streaming');
251
+ }
252
+ // eslint-disable-next-line no-console
253
+ console.log(`[Hybrid] Using ${this.primary.name} for streaming`);
254
+ yield* this.primary.streamText(prompt, options);
255
+ }
256
+ getUsageStats() {
257
+ const primaryStats = this.primary.getUsageStats();
258
+ const fallbackStats = this.fallback.getUsageStats();
259
+ // Combine stats
260
+ return {
261
+ requestCount: primaryStats.requestCount + fallbackStats.requestCount,
262
+ totalInputTokens: primaryStats.totalInputTokens + fallbackStats.totalInputTokens,
263
+ totalOutputTokens: primaryStats.totalOutputTokens + fallbackStats.totalOutputTokens,
264
+ totalTokens: primaryStats.totalTokens + fallbackStats.totalTokens,
265
+ totalCost: primaryStats.totalCost + fallbackStats.totalCost,
266
+ averageResponseTimeMs: (primaryStats.averageResponseTimeMs * primaryStats.requestCount +
267
+ fallbackStats.averageResponseTimeMs * fallbackStats.requestCount) /
268
+ (primaryStats.requestCount + fallbackStats.requestCount),
269
+ failedRequests: primaryStats.failedRequests + fallbackStats.failedRequests,
270
+ startTime: new Date(Math.min(primaryStats.startTime.getTime(), fallbackStats.startTime.getTime())),
271
+ lastUpdated: new Date(Math.max(primaryStats.lastUpdated.getTime(), fallbackStats.lastUpdated.getTime())),
272
+ };
273
+ }
274
+ resetUsageStats() {
275
+ this.primary.resetUsageStats();
276
+ this.fallback.resetUsageStats();
277
+ }
278
+ /**
279
+ * Get breakdown of which provider was used for what
280
+ */
281
+ getProviderBreakdown() {
282
+ const primaryStats = this.primary.getUsageStats();
283
+ const fallbackStats = this.fallback.getUsageStats();
284
+ // Calculate what it would cost if we used only fallback
285
+ const totalRequests = primaryStats.requestCount + fallbackStats.requestCount;
286
+ const fallbackCostPerRequest = fallbackStats.requestCount > 0 ? fallbackStats.totalCost / fallbackStats.requestCount : 0;
287
+ const hypotheticalFullCost = totalRequests * fallbackCostPerRequest;
288
+ const actualCost = primaryStats.totalCost + fallbackStats.totalCost;
289
+ const savings = hypotheticalFullCost - actualCost;
290
+ const savingsPercent = hypotheticalFullCost > 0 ? (savings / hypotheticalFullCost) * 100 : 0;
291
+ return {
292
+ primary: {
293
+ name: this.primary.name,
294
+ stats: primaryStats,
295
+ },
296
+ fallback: {
297
+ name: this.fallback.name,
298
+ stats: fallbackStats,
299
+ },
300
+ costSavings: `$${savings.toFixed(2)} saved (${savingsPercent.toFixed(1)}% reduction)`,
301
+ };
302
+ }
303
+ }
304
+ /**
305
+ * Helper to validate provider setup
306
+ */
307
+ async function validateProviderSetup(provider) {
308
+ const capabilities = [];
309
+ if (provider.capabilities.vision) {
310
+ capabilities.push('✓ Vision support (screenshot comparison)');
311
+ }
312
+ else {
313
+ capabilities.push('✗ No vision support');
314
+ }
315
+ if (provider.capabilities.streaming) {
316
+ capabilities.push('✓ Streaming responses');
317
+ }
318
+ if (provider.capabilities.supportsTools) {
319
+ capabilities.push('✓ Function calling');
320
+ }
321
+ capabilities.push(`✓ ${provider.capabilities.maxTokens.toLocaleString()} token context window`);
322
+ capabilities.push(`✓ Cost: $${provider.capabilities.costPer1MOutputTokens}/1M tokens`);
323
+ try {
324
+ // Try a simple request
325
+ const response = await provider.generateText('Say "OK" if you can read this', {
326
+ maxTokens: 10,
327
+ });
328
+ return {
329
+ valid: response.text.length > 0,
330
+ message: `Provider '${provider.name}' is working correctly`,
331
+ capabilities,
332
+ };
333
+ }
334
+ catch (error) {
335
+ return {
336
+ valid: false,
337
+ message: `Provider '${provider.name}' validation failed: ${error instanceof Error ? error.message : String(error)}`,
338
+ capabilities,
339
+ };
340
+ }
341
+ }