npm-ai-hooks 1.0.2 → 2.0.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 (73) hide show
  1. package/LICENSE +1 -1
  2. package/Readme.md +379 -137
  3. package/dist/cjs/index.js +14 -0
  4. package/dist/cjs/providers/base/BaseProvider.js +121 -0
  5. package/dist/cjs/providers/base/ProviderConfig.js +118 -0
  6. package/dist/cjs/providers/base/ProviderConfigs.js +185 -0
  7. package/dist/cjs/providers/base/ProviderRegistry.js +47 -0
  8. package/dist/cjs/providers/base/SpecializedProviders.js +58 -0
  9. package/dist/cjs/providers/index.js +82 -0
  10. package/dist/cjs/providers/init.js +90 -0
  11. package/dist/{wrap.js → cjs/wrap.js} +43 -12
  12. package/dist/esm/errors.js +17 -0
  13. package/dist/esm/index.js +3 -0
  14. package/dist/esm/providers/base/BaseProvider.js +114 -0
  15. package/dist/esm/providers/base/ProviderConfig.js +114 -0
  16. package/dist/esm/providers/base/ProviderConfigs.js +182 -0
  17. package/dist/esm/providers/base/ProviderRegistry.js +43 -0
  18. package/dist/esm/providers/base/SpecializedProviders.js +52 -0
  19. package/dist/esm/providers/index.js +75 -0
  20. package/dist/esm/providers/init.js +81 -0
  21. package/dist/esm/types/claude.js +4 -0
  22. package/dist/esm/types/core/providers.js +1 -0
  23. package/dist/esm/types/deepseek.js +4 -0
  24. package/dist/esm/types/gemini.js +4 -0
  25. package/dist/esm/types/groq.js +1 -0
  26. package/dist/esm/types/index.js +20 -0
  27. package/dist/esm/types/mistral.js +4 -0
  28. package/dist/esm/types/openai.js +4 -0
  29. package/dist/esm/types/openrouter.js +1 -0
  30. package/dist/esm/types/perplexity.js +4 -0
  31. package/dist/esm/types/xai.js +4 -0
  32. package/dist/esm/wrap.js +113 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/providers/base/BaseProvider.d.ts +33 -0
  35. package/dist/providers/base/ProviderConfig.d.ts +28 -0
  36. package/dist/providers/base/ProviderConfigs.d.ts +31 -0
  37. package/dist/providers/base/ProviderRegistry.d.ts +12 -0
  38. package/dist/providers/base/SpecializedProviders.d.ts +68 -0
  39. package/dist/providers/index.d.ts +4 -0
  40. package/dist/providers/init.d.ts +58 -0
  41. package/package.json +50 -12
  42. package/dist/index.js +0 -6
  43. package/dist/providers/claude.d.ts +0 -2
  44. package/dist/providers/claude.js +0 -60
  45. package/dist/providers/deepkseek.d.ts +0 -2
  46. package/dist/providers/deepkseek.js +0 -57
  47. package/dist/providers/gemini.d.ts +0 -2
  48. package/dist/providers/gemini.js +0 -58
  49. package/dist/providers/groq.d.ts +0 -2
  50. package/dist/providers/groq.js +0 -57
  51. package/dist/providers/index.js +0 -75
  52. package/dist/providers/mistral.d.ts +0 -2
  53. package/dist/providers/mistral.js +0 -57
  54. package/dist/providers/openai.d.ts +0 -2
  55. package/dist/providers/openai.js +0 -57
  56. package/dist/providers/openrouter.d.ts +0 -1
  57. package/dist/providers/openrouter.js +0 -55
  58. package/dist/providers/perplexity.d.ts +0 -2
  59. package/dist/providers/perplexity.js +0 -57
  60. package/dist/providers/xai.d.ts +0 -2
  61. package/dist/providers/xai.js +0 -57
  62. /package/dist/{errors.js → cjs/errors.js} +0 -0
  63. /package/dist/{types → cjs/types}/claude.js +0 -0
  64. /package/dist/{types → cjs/types}/core/providers.js +0 -0
  65. /package/dist/{types → cjs/types}/deepseek.js +0 -0
  66. /package/dist/{types → cjs/types}/gemini.js +0 -0
  67. /package/dist/{types → cjs/types}/groq.js +0 -0
  68. /package/dist/{types → cjs/types}/index.js +0 -0
  69. /package/dist/{types → cjs/types}/mistral.js +0 -0
  70. /package/dist/{types → cjs/types}/openai.js +0 -0
  71. /package/dist/{types → cjs/types}/openrouter.js +0 -0
  72. /package/dist/{types → cjs/types}/perplexity.js +0 -0
  73. /package/dist/{types → cjs/types}/xai.js +0 -0
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BaseProvider = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const errors_1 = require("../../errors");
9
+ class BaseProvider {
10
+ constructor(config) {
11
+ this.config = config;
12
+ }
13
+ async call(prompt, model) {
14
+ const apiKey = this.getApiKey();
15
+ this.validateApiKey(apiKey);
16
+ // At this point, apiKey is guaranteed to be defined due to validateApiKey
17
+ const validatedApiKey = apiKey;
18
+ try {
19
+ const requestConfig = this.buildRequestConfig(prompt, model, validatedApiKey);
20
+ const response = await this.makeRequest(requestConfig);
21
+ return this.parseResponse(response);
22
+ }
23
+ catch (error) {
24
+ throw this.handleError(error);
25
+ }
26
+ }
27
+ getApiKey() {
28
+ // This method should be overridden by the provider creation logic
29
+ // to return the explicitly provided API key instead of reading from process.env
30
+ if (typeof process !== "undefined" && process.env) {
31
+ return process.env[this.config.envKey];
32
+ }
33
+ return undefined;
34
+ }
35
+ validateApiKey(apiKey) {
36
+ if (!apiKey) {
37
+ throw new errors_1.AIHookError("INVALID_API_KEY", this.config.errorMessages.missingKey, this.config.name, `Set ${this.config.envKey} in your environment variables.`);
38
+ }
39
+ }
40
+ buildRequestConfig(prompt, model, apiKey) {
41
+ return {
42
+ url: this.config.baseUrl,
43
+ method: "POST",
44
+ data: this.config.requestBody(prompt, model),
45
+ headers: {
46
+ ...this.config.headers,
47
+ ...this.buildAuthHeaders(apiKey)
48
+ }
49
+ };
50
+ }
51
+ buildAuthHeaders(apiKey) {
52
+ return {
53
+ "Authorization": `Bearer ${apiKey}`
54
+ };
55
+ }
56
+ async makeRequest(config) {
57
+ return (0, axios_1.default)(config);
58
+ }
59
+ parseResponse(response) {
60
+ const output = this.config.responseParser(response);
61
+ if (!output) {
62
+ throw new errors_1.AIHookError("PROVIDER_ERROR", this.config.errorMessages.emptyResponse, this.config.name, "Check your model and API key");
63
+ }
64
+ return output;
65
+ }
66
+ handleError(error) {
67
+ if (error.response) {
68
+ return this.handleHttpError(error);
69
+ }
70
+ else if (error.request) {
71
+ return new errors_1.AIHookError("NETWORK_ERROR", this.config.errorMessages.networkError, this.config.name, "Check your internet connection");
72
+ }
73
+ else {
74
+ return new errors_1.AIHookError("UNKNOWN_ERROR", error.message || this.config.errorMessages.unknownError, this.config.name);
75
+ }
76
+ }
77
+ getCapitalizedProviderName() {
78
+ const name = this.config.name;
79
+ // Handle special cases
80
+ if (name === "openai")
81
+ return "OpenAI";
82
+ if (name === "openrouter")
83
+ return "OpenRouter";
84
+ if (name === "xai")
85
+ return "xAI";
86
+ if (name === "claude")
87
+ return "Claude";
88
+ if (name === "gemini")
89
+ return "Gemini";
90
+ if (name === "groq")
91
+ return "Groq";
92
+ if (name === "deepseek")
93
+ return "DeepSeek";
94
+ if (name === "mistral")
95
+ return "Mistral";
96
+ if (name === "perplexity")
97
+ return "Perplexity";
98
+ // Default: capitalize first letter
99
+ return name.charAt(0).toUpperCase() + name.slice(1);
100
+ }
101
+ handleHttpError(error) {
102
+ const status = error.response.status;
103
+ const text = error.response.data?.error
104
+ ? JSON.stringify(error.response.data.error)
105
+ : error.response.statusText || "Unknown error";
106
+ const providerName = this.config.name;
107
+ switch (status) {
108
+ case 400:
109
+ return new errors_1.AIHookError("BAD_REQUEST", `${this.getCapitalizedProviderName()} rejected the request: ${text}`, providerName, "Check your prompt and model");
110
+ case 401:
111
+ return new errors_1.AIHookError("INVALID_API_KEY", `Invalid ${this.getCapitalizedProviderName()} API key: ${text}`, providerName, `Verify your ${this.config.envKey} environment variable`);
112
+ case 403:
113
+ return new errors_1.AIHookError("MODEL_NOT_ALLOWED", `Your API key cannot access this model: ${text}`, providerName, "Try a different model or check API key permissions");
114
+ case 429:
115
+ return new errors_1.AIHookError("RATE_LIMIT", `Too many requests to ${this.getCapitalizedProviderName()}: ${text}`, providerName, "Throttle requests or upgrade your plan");
116
+ default:
117
+ return new errors_1.AIHookError("PROVIDER_ERROR", `${this.getCapitalizedProviderName()} API error: ${text}`, providerName);
118
+ }
119
+ }
120
+ }
121
+ exports.BaseProvider = BaseProvider;
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProviderManager = void 0;
4
+ const types_1 = require("../../types");
5
+ const BaseProvider_1 = require("./BaseProvider");
6
+ const ProviderConfigs_1 = require("./ProviderConfigs");
7
+ const SpecializedProviders_1 = require("./SpecializedProviders");
8
+ class ProviderManager {
9
+ constructor(options) {
10
+ this.providers = new Map();
11
+ this.providerFunctions = new Map();
12
+ this.initializeProviders(options);
13
+ }
14
+ initializeProviders(options) {
15
+ // Store provider configs
16
+ for (const config of options.providers) {
17
+ this.providers.set(config.provider, {
18
+ key: config.key,
19
+ defaultModel: config.defaultModel
20
+ });
21
+ }
22
+ // Set default provider if specified
23
+ this.defaultProvider = options.defaultProvider;
24
+ // Create provider functions
25
+ this.createProviderFunctions();
26
+ }
27
+ createProviderFunctions() {
28
+ for (const [providerName, config] of this.providers) {
29
+ const providerFunction = this.createProviderFunction(providerName, config);
30
+ this.providerFunctions.set(providerName, providerFunction);
31
+ }
32
+ }
33
+ createProviderFunction(providerName, config) {
34
+ return async (prompt, model) => {
35
+ // Use provided model or default model for this provider
36
+ const modelToUse = model || config.defaultModel || this.getDefaultModelForProvider(providerName);
37
+ // Create a temporary provider instance for this call
38
+ const provider = this.createProviderInstance(providerName, config.key);
39
+ return provider.call(prompt, modelToUse);
40
+ };
41
+ }
42
+ createProviderInstance(providerName, apiKey) {
43
+ // Import the base provider and configs
44
+ // Using imported classes directly
45
+ const config = ProviderConfigs_1.providerConfigs[providerName];
46
+ if (!config) {
47
+ throw new Error(`Unsupported provider: ${providerName}`);
48
+ }
49
+ // Create provider instance with the provided API key
50
+ const providerConfig = { ...config };
51
+ // Override the getApiKey method to return the provided key
52
+ if (providerName === 'claude') {
53
+ const provider = new SpecializedProviders_1.ClaudeProvider();
54
+ provider['getApiKey'] = () => apiKey;
55
+ return provider;
56
+ }
57
+ else if (providerName === 'gemini') {
58
+ const provider = new SpecializedProviders_1.GeminiProvider();
59
+ provider['getApiKey'] = () => apiKey;
60
+ return provider;
61
+ }
62
+ else if (providerName === 'openrouter') {
63
+ const provider = new SpecializedProviders_1.OpenRouterProvider();
64
+ provider['getApiKey'] = () => apiKey;
65
+ return provider;
66
+ }
67
+ else {
68
+ const provider = new BaseProvider_1.BaseProvider(providerConfig);
69
+ provider['getApiKey'] = () => apiKey;
70
+ return provider;
71
+ }
72
+ }
73
+ getDefaultModelForProvider(providerName) {
74
+ // Import default models
75
+ // Using imported DEFAULT_MODEL directly
76
+ return types_1.DEFAULT_MODEL[providerName];
77
+ }
78
+ getAvailableProviders() {
79
+ return Array.from(this.providers.keys());
80
+ }
81
+ getProvider(name) {
82
+ const available = this.getAvailableProviders();
83
+ if (available.length === 0) {
84
+ throw new Error('No providers initialized. Please initialize providers first.');
85
+ }
86
+ // 1. If user specified provider and it's available
87
+ if (name && this.providerFunctions.has(name)) {
88
+ return { fn: this.providerFunctions.get(name), provider: name };
89
+ }
90
+ // 2. If default provider is specified and available
91
+ if (this.defaultProvider && this.providerFunctions.has(this.defaultProvider)) {
92
+ return { fn: this.providerFunctions.get(this.defaultProvider), provider: this.defaultProvider };
93
+ }
94
+ // 3. Prefer OpenRouter if available
95
+ if (this.providerFunctions.has('openrouter')) {
96
+ return { fn: this.providerFunctions.get('openrouter'), provider: 'openrouter' };
97
+ }
98
+ // 4. Use first available provider
99
+ const firstProvider = available[0];
100
+ return { fn: this.providerFunctions.get(firstProvider), provider: firstProvider };
101
+ }
102
+ addProvider(config) {
103
+ this.providers.set(config.provider, {
104
+ key: config.key,
105
+ defaultModel: config.defaultModel
106
+ });
107
+ const providerFunction = this.createProviderFunction(config.provider, {
108
+ key: config.key,
109
+ defaultModel: config.defaultModel
110
+ });
111
+ this.providerFunctions.set(config.provider, providerFunction);
112
+ }
113
+ removeProvider(provider) {
114
+ this.providers.delete(provider);
115
+ this.providerFunctions.delete(provider);
116
+ }
117
+ }
118
+ exports.ProviderManager = ProviderManager;
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.providerConfigs = exports.requestBodyBuilders = exports.responseParsers = void 0;
4
+ // Common response parsers
5
+ exports.responseParsers = {
6
+ openaiStyle: (response) => response.data?.choices?.[0]?.message?.content,
7
+ claudeStyle: (response) => response.data?.content?.[0]?.text,
8
+ geminiStyle: (response) => response.data?.candidates?.[0]?.content?.parts?.[0]?.text,
9
+ };
10
+ // Common request body builders
11
+ exports.requestBodyBuilders = {
12
+ openaiStyle: (prompt, model) => ({
13
+ model,
14
+ messages: [{ role: "user", content: prompt }]
15
+ }),
16
+ claudeStyle: (prompt, model) => ({
17
+ model,
18
+ max_tokens: 4096,
19
+ messages: [{ role: "user", content: prompt }]
20
+ }),
21
+ geminiStyle: (prompt, model) => ({
22
+ contents: [{
23
+ parts: [{ text: prompt }]
24
+ }]
25
+ }),
26
+ };
27
+ // Provider configurations
28
+ exports.providerConfigs = {
29
+ openai: {
30
+ name: "openai",
31
+ baseUrl: "https://api.openai.com/v1/chat/completions",
32
+ envKey: "AI_HOOK_OPENAI_KEY",
33
+ headers: { "Content-Type": "application/json" },
34
+ requestBody: exports.requestBodyBuilders.openaiStyle,
35
+ responseParser: exports.responseParsers.openaiStyle,
36
+ errorMessages: {
37
+ missingKey: "Missing OpenAI API key.",
38
+ emptyResponse: "OpenAI returned empty response",
39
+ badRequest: "OpenAI rejected the request",
40
+ invalidKey: "Invalid OpenAI API key",
41
+ rateLimit: "Too many requests to OpenAI",
42
+ networkError: "Network error while contacting OpenAI",
43
+ unknownError: "Unknown error occurred"
44
+ }
45
+ },
46
+ groq: {
47
+ name: "groq",
48
+ baseUrl: "https://api.groq.com/openai/v1/chat/completions",
49
+ envKey: "AI_HOOK_GROQ_KEY",
50
+ headers: { "Content-Type": "application/json" },
51
+ requestBody: exports.requestBodyBuilders.openaiStyle,
52
+ responseParser: exports.responseParsers.openaiStyle,
53
+ errorMessages: {
54
+ missingKey: "Missing Groq API key.",
55
+ emptyResponse: "Groq returned empty response",
56
+ badRequest: "Groq rejected the request",
57
+ invalidKey: "Invalid Groq API key",
58
+ rateLimit: "Too many requests to Groq",
59
+ networkError: "Network error while contacting Groq",
60
+ unknownError: "Unknown error occurred"
61
+ }
62
+ },
63
+ claude: {
64
+ name: "claude",
65
+ baseUrl: "https://api.anthropic.com/v1/messages",
66
+ envKey: "AI_HOOK_CLAUDE_KEY",
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ "anthropic-version": "2023-06-01"
70
+ },
71
+ requestBody: exports.requestBodyBuilders.claudeStyle,
72
+ responseParser: exports.responseParsers.claudeStyle,
73
+ errorMessages: {
74
+ missingKey: "Missing Claude API key.",
75
+ emptyResponse: "Claude returned empty response",
76
+ badRequest: "Claude rejected the request",
77
+ invalidKey: "Invalid Claude API key",
78
+ rateLimit: "Too many requests to Claude",
79
+ networkError: "Network error while contacting Claude",
80
+ unknownError: "Unknown error occurred"
81
+ }
82
+ },
83
+ gemini: {
84
+ name: "gemini",
85
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
86
+ envKey: "AI_HOOK_GEMINI_KEY",
87
+ headers: { "Content-Type": "application/json" },
88
+ requestBody: exports.requestBodyBuilders.geminiStyle,
89
+ responseParser: exports.responseParsers.geminiStyle,
90
+ errorMessages: {
91
+ missingKey: "Missing Gemini API key.",
92
+ emptyResponse: "Gemini returned empty response",
93
+ badRequest: "Gemini rejected the request",
94
+ invalidKey: "Invalid Gemini API key",
95
+ rateLimit: "Too many requests to Gemini",
96
+ networkError: "Network error while contacting Gemini",
97
+ unknownError: "Unknown error occurred"
98
+ }
99
+ },
100
+ deepseek: {
101
+ name: "deepseek",
102
+ baseUrl: "https://api.deepseek.com/v1/chat/completions",
103
+ envKey: "AI_HOOK_DEEPSEEK_KEY",
104
+ headers: { "Content-Type": "application/json" },
105
+ requestBody: exports.requestBodyBuilders.openaiStyle,
106
+ responseParser: exports.responseParsers.openaiStyle,
107
+ errorMessages: {
108
+ missingKey: "Missing DeepSeek API key.",
109
+ emptyResponse: "DeepSeek returned empty response",
110
+ badRequest: "DeepSeek rejected the request",
111
+ invalidKey: "Invalid DeepSeek API key",
112
+ rateLimit: "Too many requests to DeepSeek",
113
+ networkError: "Network error while contacting DeepSeek",
114
+ unknownError: "Unknown error occurred"
115
+ }
116
+ },
117
+ mistral: {
118
+ name: "mistral",
119
+ baseUrl: "https://api.mistral.ai/v1/chat/completions",
120
+ envKey: "AI_HOOK_MISTRAL_KEY",
121
+ headers: { "Content-Type": "application/json" },
122
+ requestBody: exports.requestBodyBuilders.openaiStyle,
123
+ responseParser: exports.responseParsers.openaiStyle,
124
+ errorMessages: {
125
+ missingKey: "Missing Mistral API key.",
126
+ emptyResponse: "Mistral returned empty response",
127
+ badRequest: "Mistral rejected the request",
128
+ invalidKey: "Invalid Mistral API key",
129
+ rateLimit: "Too many requests to Mistral",
130
+ networkError: "Network error while contacting Mistral",
131
+ unknownError: "Unknown error occurred"
132
+ }
133
+ },
134
+ xai: {
135
+ name: "xai",
136
+ baseUrl: "https://api.x.ai/v1/chat/completions",
137
+ envKey: "AI_HOOK_XAI_KEY",
138
+ headers: { "Content-Type": "application/json" },
139
+ requestBody: exports.requestBodyBuilders.openaiStyle,
140
+ responseParser: exports.responseParsers.openaiStyle,
141
+ errorMessages: {
142
+ missingKey: "Missing xAI API key.",
143
+ emptyResponse: "xAI returned empty response",
144
+ badRequest: "xAI rejected the request",
145
+ invalidKey: "Invalid xAI API key",
146
+ rateLimit: "Too many requests to xAI",
147
+ networkError: "Network error while contacting xAI",
148
+ unknownError: "Unknown error occurred"
149
+ }
150
+ },
151
+ perplexity: {
152
+ name: "perplexity",
153
+ baseUrl: "https://api.perplexity.ai/chat/completions",
154
+ envKey: "AI_HOOK_PERPLEXITY_KEY",
155
+ headers: { "Content-Type": "application/json" },
156
+ requestBody: exports.requestBodyBuilders.openaiStyle,
157
+ responseParser: exports.responseParsers.openaiStyle,
158
+ errorMessages: {
159
+ missingKey: "Missing Perplexity API key.",
160
+ emptyResponse: "Perplexity returned empty response",
161
+ badRequest: "Perplexity rejected the request",
162
+ invalidKey: "Invalid Perplexity API key",
163
+ rateLimit: "Too many requests to Perplexity",
164
+ networkError: "Network error while contacting Perplexity",
165
+ unknownError: "Unknown error occurred"
166
+ }
167
+ },
168
+ openrouter: {
169
+ name: "openrouter",
170
+ baseUrl: "https://openrouter.ai/api/v1/chat/completions",
171
+ envKey: "AI_HOOK_OPENROUTER_KEY",
172
+ headers: { "Content-Type": "application/json" },
173
+ requestBody: exports.requestBodyBuilders.openaiStyle,
174
+ responseParser: exports.responseParsers.openaiStyle,
175
+ errorMessages: {
176
+ missingKey: "Missing OpenRouter API key.",
177
+ emptyResponse: "OpenRouter returned empty response",
178
+ badRequest: "OpenRouter rejected the request",
179
+ invalidKey: "Invalid OpenRouter API key",
180
+ rateLimit: "Too many requests to OpenRouter",
181
+ networkError: "Network error while contacting OpenRouter",
182
+ unknownError: "Unknown error occurred"
183
+ }
184
+ }
185
+ };
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.providerRegistry = exports.ProviderRegistry = void 0;
4
+ class ProviderRegistry {
5
+ constructor() {
6
+ this.providers = new Map();
7
+ this.providerFunctions = new Map();
8
+ }
9
+ register(name, provider) {
10
+ this.providers.set(name, provider);
11
+ this.providerFunctions.set(name, this.createProviderFunction(provider));
12
+ }
13
+ get(name) {
14
+ return this.providerFunctions.get(name);
15
+ }
16
+ getAvailableProviders() {
17
+ const available = [];
18
+ // Always prefer openrouter if present (matching original behavior)
19
+ if (this.providers.has("openrouter")) {
20
+ const openrouterProvider = this.providers.get("openrouter");
21
+ const apiKey = openrouterProvider['getApiKey']();
22
+ if (apiKey) {
23
+ available.push("openrouter");
24
+ }
25
+ }
26
+ // Add others in order of their presence (matching original behavior)
27
+ const otherProviders = ['groq', 'openai', 'gemini', 'claude', 'deepseek', 'xai', 'perplexity', 'mistral'];
28
+ for (const providerName of otherProviders) {
29
+ if (this.providers.has(providerName)) {
30
+ const provider = this.providers.get(providerName);
31
+ const apiKey = provider['getApiKey']();
32
+ if (apiKey && !available.includes(providerName)) {
33
+ available.push(providerName);
34
+ }
35
+ }
36
+ }
37
+ return available;
38
+ }
39
+ createProviderFunction(provider) {
40
+ return async (prompt, model) => {
41
+ return provider.call(prompt, model);
42
+ };
43
+ }
44
+ }
45
+ exports.ProviderRegistry = ProviderRegistry;
46
+ // Global registry instance
47
+ exports.providerRegistry = new ProviderRegistry();
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenRouterProvider = exports.GeminiProvider = exports.ClaudeProvider = void 0;
4
+ const BaseProvider_1 = require("./BaseProvider");
5
+ const ProviderConfigs_1 = require("./ProviderConfigs");
6
+ const errors_1 = require("../../errors");
7
+ // Claude provider with custom auth headers
8
+ class ClaudeProvider extends BaseProvider_1.BaseProvider {
9
+ constructor() {
10
+ super(ProviderConfigs_1.providerConfigs.claude);
11
+ }
12
+ buildAuthHeaders(apiKey) {
13
+ return {
14
+ "x-api-key": apiKey,
15
+ "anthropic-version": "2023-06-01"
16
+ };
17
+ }
18
+ }
19
+ exports.ClaudeProvider = ClaudeProvider;
20
+ // Gemini provider with custom URL and auth
21
+ class GeminiProvider extends BaseProvider_1.BaseProvider {
22
+ constructor() {
23
+ super(ProviderConfigs_1.providerConfigs.gemini);
24
+ }
25
+ buildRequestConfig(prompt, model, apiKey) {
26
+ const baseConfig = super.buildRequestConfig(prompt, model, apiKey);
27
+ return {
28
+ ...baseConfig,
29
+ url: `${this.config.baseUrl}/${model}:generateContent?key=${apiKey}`,
30
+ headers: {
31
+ ...baseConfig.headers,
32
+ // Remove Authorization header for Gemini
33
+ }
34
+ };
35
+ }
36
+ buildAuthHeaders() {
37
+ return {}; // Gemini uses API key in URL
38
+ }
39
+ }
40
+ exports.GeminiProvider = GeminiProvider;
41
+ // OpenRouter provider with custom error handling
42
+ class OpenRouterProvider extends BaseProvider_1.BaseProvider {
43
+ constructor() {
44
+ super(ProviderConfigs_1.providerConfigs.openrouter);
45
+ }
46
+ handleHttpError(error) {
47
+ const status = error.response.status;
48
+ const text = error.response.data?.error
49
+ ? JSON.stringify(error.response.data.error)
50
+ : error.response.statusText || "Unknown error";
51
+ const providerName = this.config.name;
52
+ if (status === 403) {
53
+ return new errors_1.AIHookError("MODEL_NOT_ALLOWED", `Your API key cannot access this model: ${text}`, providerName, "Try a different model or check API key permissions");
54
+ }
55
+ return super.handleHttpError(error);
56
+ }
57
+ }
58
+ exports.OpenRouterProvider = OpenRouterProvider;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.providerRegistry = exports.reset = exports.isInitialized = exports.removeProvider = exports.addProvider = exports.initAIHooks = void 0;
4
+ exports.getAvailableProviders = getAvailableProviders;
5
+ exports.getProvider = getProvider;
6
+ const errors_1 = require("../errors");
7
+ const ProviderRegistry_1 = require("./base/ProviderRegistry");
8
+ Object.defineProperty(exports, "providerRegistry", { enumerable: true, get: function () { return ProviderRegistry_1.providerRegistry; } });
9
+ const ProviderConfigs_1 = require("./base/ProviderConfigs");
10
+ const BaseProvider_1 = require("./base/BaseProvider");
11
+ const SpecializedProviders_1 = require("./base/SpecializedProviders");
12
+ // Import the new initialization system
13
+ const init_1 = require("./init");
14
+ Object.defineProperty(exports, "initAIHooks", { enumerable: true, get: function () { return init_1.initAIHooks; } });
15
+ Object.defineProperty(exports, "addProvider", { enumerable: true, get: function () { return init_1.addProvider; } });
16
+ Object.defineProperty(exports, "removeProvider", { enumerable: true, get: function () { return init_1.removeProvider; } });
17
+ Object.defineProperty(exports, "isInitialized", { enumerable: true, get: function () { return init_1.isInitialized; } });
18
+ Object.defineProperty(exports, "reset", { enumerable: true, get: function () { return init_1.reset; } });
19
+ // Initialize all providers (legacy environment-based system)
20
+ function initializeProviders() {
21
+ // Standard providers using BaseProvider
22
+ const standardProviders = ['openai', 'groq', 'deepseek', 'mistral', 'xai', 'perplexity'];
23
+ standardProviders.forEach(providerName => {
24
+ const config = ProviderConfigs_1.providerConfigs[providerName];
25
+ if (config) {
26
+ const provider = new BaseProvider_1.BaseProvider(config);
27
+ ProviderRegistry_1.providerRegistry.register(providerName, provider);
28
+ }
29
+ });
30
+ // Specialized providers
31
+ ProviderRegistry_1.providerRegistry.register("claude", new SpecializedProviders_1.ClaudeProvider());
32
+ ProviderRegistry_1.providerRegistry.register("gemini", new SpecializedProviders_1.GeminiProvider());
33
+ ProviderRegistry_1.providerRegistry.register("openrouter", new SpecializedProviders_1.OpenRouterProvider());
34
+ }
35
+ // Initialize providers on module load (legacy)
36
+ initializeProviders();
37
+ // Legacy compatibility - create the old provider map
38
+ const providers = {
39
+ openrouter: ProviderRegistry_1.providerRegistry.get("openrouter"),
40
+ groq: ProviderRegistry_1.providerRegistry.get("groq"),
41
+ openai: ProviderRegistry_1.providerRegistry.get("openai"),
42
+ gemini: ProviderRegistry_1.providerRegistry.get("gemini"),
43
+ claude: ProviderRegistry_1.providerRegistry.get("claude"),
44
+ deepseek: ProviderRegistry_1.providerRegistry.get("deepseek"),
45
+ xai: ProviderRegistry_1.providerRegistry.get("xai"),
46
+ perplexity: ProviderRegistry_1.providerRegistry.get("perplexity"),
47
+ mistral: ProviderRegistry_1.providerRegistry.get("mistral"),
48
+ mock: async (prompt, model) => `[MOCK OUTPUT] ${prompt}`
49
+ };
50
+ // Returns an array of providers whose API keys exist in environment (legacy)
51
+ function getAvailableProviders() {
52
+ // If new system is initialized, use it
53
+ if ((0, init_1.isInitialized)()) {
54
+ return (0, init_1.getAvailableProviders)();
55
+ }
56
+ // Otherwise use legacy system
57
+ return ProviderRegistry_1.providerRegistry.getAvailableProviders();
58
+ }
59
+ // Returns both the provider function and the actual provider name (legacy)
60
+ function getProvider(name) {
61
+ // If new system is initialized, use it
62
+ if ((0, init_1.isInitialized)()) {
63
+ const result = (0, init_1.getProvider)(name);
64
+ return { fn: result.fn, provider: result.provider };
65
+ }
66
+ // Otherwise use legacy system
67
+ const available = getAvailableProviders();
68
+ // 1. If user specified provider and it's available
69
+ if (name && providers[name]) {
70
+ return { fn: providers[name], provider: name };
71
+ }
72
+ // 2. If at least one provider is available, pick the first one (openrouter always preferred if present)
73
+ if (available.length > 0) {
74
+ // Only log in non-test environments to avoid Jest warnings
75
+ if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
76
+ console.log(`[ai-hooks] ✅ Auto-selected provider: ${available[0]}`);
77
+ }
78
+ return { fn: providers[available[0]], provider: available[0] };
79
+ }
80
+ // 3. No valid keys found → throw error (single instruction, no fallback)
81
+ throw new errors_1.AIHookError("NO_PROVIDER_FOUND", "No valid AI provider API key was found.\n\nAt least one provider API key is required in your .env file.\n\nPlease add one of the following to your .env (see .env.example for details):\n - AI_HOOK_OPENAI_KEY\n - AI_HOOK_OPENROUTER_KEY\n - AI_HOOK_GROQ_KEY\n", undefined, "Reference .env.example for setup instructions.");
82
+ }