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,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initAIHooks = initAIHooks;
4
+ exports.addProvider = addProvider;
5
+ exports.removeProvider = removeProvider;
6
+ exports.getAvailableProviders = getAvailableProviders;
7
+ exports.getProvider = getProvider;
8
+ exports.isInitialized = isInitialized;
9
+ exports.reset = reset;
10
+ const ProviderConfig_1 = require("./base/ProviderConfig");
11
+ // Global provider manager instance
12
+ let providerManager = null;
13
+ /**
14
+ * Initialize the AI hooks system with provider configurations
15
+ * @param options - Provider initialization options
16
+ * @example
17
+ * ```typescript
18
+ * import { initAIHooks } from 'npm-ai-hooks';
19
+ *
20
+ * initAIHooks({
21
+ * providers: [
22
+ * { provider: 'openai', key: 'sk-...', defaultModel: 'gpt-4' },
23
+ * { provider: 'claude', key: 'sk-ant-...', defaultModel: 'claude-3-sonnet-20240229' },
24
+ * { provider: 'groq', key: 'gsk_...', defaultModel: 'llama-3.1-70b-versatile' }
25
+ * ],
26
+ * defaultProvider: 'openai' // optional
27
+ * });
28
+ * ```
29
+ */
30
+ function initAIHooks(options) {
31
+ providerManager = new ProviderConfig_1.ProviderManager(options);
32
+ }
33
+ /**
34
+ * Add a new provider after initialization
35
+ * @param config - Provider configuration
36
+ * @example
37
+ * ```typescript
38
+ * addProvider({ provider: 'mistral', key: '...', defaultModel: 'mistral-large' });
39
+ * ```
40
+ */
41
+ function addProvider(config) {
42
+ if (!providerManager) {
43
+ throw new Error('AI hooks not initialized. Call initAIHooks() first.');
44
+ }
45
+ providerManager.addProvider(config);
46
+ }
47
+ /**
48
+ * Remove a provider
49
+ * @param provider - Provider to remove
50
+ */
51
+ function removeProvider(provider) {
52
+ if (!providerManager) {
53
+ throw new Error('AI hooks not initialized. Call initAIHooks() first.');
54
+ }
55
+ providerManager.removeProvider(provider);
56
+ }
57
+ /**
58
+ * Get available providers
59
+ * @returns Array of available provider names
60
+ */
61
+ function getAvailableProviders() {
62
+ if (!providerManager) {
63
+ throw new Error('AI hooks not initialized. Call initAIHooks() first.');
64
+ }
65
+ return providerManager.getAvailableProviders();
66
+ }
67
+ /**
68
+ * Get a provider function
69
+ * @param name - Optional provider name
70
+ * @returns Provider function and name
71
+ */
72
+ function getProvider(name) {
73
+ if (!providerManager) {
74
+ throw new Error('AI hooks not initialized. Call initAIHooks() first.');
75
+ }
76
+ return providerManager.getProvider(name);
77
+ }
78
+ /**
79
+ * Check if AI hooks is initialized
80
+ * @returns True if initialized
81
+ */
82
+ function isInitialized() {
83
+ return providerManager !== null;
84
+ }
85
+ /**
86
+ * Reset the provider manager (useful for testing)
87
+ */
88
+ function reset() {
89
+ providerManager = null;
90
+ }
@@ -1,26 +1,37 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.wrap = wrap;
7
- const dotenv_1 = __importDefault(require("dotenv"));
8
- dotenv_1.default.config();
4
+ // dotenv removed - using explicit provider initialization instead
9
5
  const providers_1 = require("./providers");
10
6
  const types_1 = require("./types");
7
+ const errors_1 = require("./errors");
11
8
  function handleError(err) {
12
9
  if (err && typeof err === "object" && "pretty" in err && typeof err.pretty === "function") {
13
- // Print pretty message and exit
10
+ // Print pretty message
14
11
  console.error(err.pretty());
15
- process.exit(1);
12
+ // Only exit in Node.js test environment, not in browser
13
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
14
+ process.exit(1);
15
+ }
16
+ // In browser or test mode, just throw the error instead of exiting
17
+ throw err;
16
18
  }
17
19
  else {
18
20
  console.error(err);
19
- process.exit(1);
21
+ // Only exit in Node.js test environment, not in browser
22
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
23
+ process.exit(1);
24
+ }
25
+ // In browser or test mode, just throw the error instead of exiting
26
+ throw err;
20
27
  }
21
- throw new Error("Process exited due to AIHookError"); // for TS never
22
28
  }
23
29
  function wrap(fn, options) {
30
+ // Validate task type immediately when wrap() is called
31
+ const validTasks = ["summarize", "translate", "explain", "rewrite", "sentiment", "codeReview"];
32
+ if (options.task && !validTasks.includes(options.task)) {
33
+ throw new errors_1.AIHookError("INVALID_TASK", `Invalid task type: ${options.task}. Valid tasks are: ${validTasks.join(", ")}`, options.provider, "Please use one of the supported task types.");
34
+ }
24
35
  return async (...args) => {
25
36
  try {
26
37
  const input = fn(...args);
@@ -29,7 +40,7 @@ function wrap(fn, options) {
29
40
  // Step 2: pick model: passed model or provider-specific default
30
41
  const model = options.model || (providerKey in types_1.DEFAULT_MODEL ? types_1.DEFAULT_MODEL[providerKey] : undefined);
31
42
  if (!model) {
32
- throw new (require('./errors').AIHookError)("NO_MODEL_FOUND", "No model found: You must specify a provider or pass a valid model.\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", options.provider, "Reference .env.example for setup instructions.");
43
+ throw new errors_1.AIHookError("NO_MODEL_FOUND", "No model found: You must specify a provider or pass a valid model.\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", options.provider, "Reference .env.example for setup instructions.");
33
44
  }
34
45
  // Step 3: build prompt
35
46
  const prompt = buildPrompt(options.task, input, options.targetLanguage);
@@ -39,8 +50,9 @@ function wrap(fn, options) {
39
50
  output = await providerFn(prompt, model);
40
51
  }
41
52
  catch (err) {
42
- if (err instanceof require('./errors').AIHookError) {
43
- handleError(err);
53
+ if (err instanceof errors_1.AIHookError) {
54
+ // For AIHookError, just re-throw it - it will be handled by the outer catch
55
+ throw err;
44
56
  }
45
57
  if (err instanceof Error) {
46
58
  throw new Error(`[ai-hooks] Unknown error calling provider: ${err.message}`);
@@ -60,11 +72,30 @@ function wrap(fn, options) {
60
72
  };
61
73
  }
62
74
  catch (err) {
75
+ if (err instanceof errors_1.AIHookError) {
76
+ // For AIHookError, just log the pretty message without the full error handling
77
+ console.error(err.pretty());
78
+ // Return a mock response to prevent the demo from crashing
79
+ return {
80
+ output: "Error occurred",
81
+ meta: {
82
+ provider: "unknown",
83
+ model: "unknown",
84
+ cached: false,
85
+ estimatedCostUSD: 0.0,
86
+ latencyMs: 0,
87
+ error: true
88
+ }
89
+ };
90
+ }
63
91
  handleError(err);
64
92
  }
65
93
  };
66
94
  }
67
95
  function buildPrompt(task, text, targetLanguage) {
96
+ if (!task) {
97
+ return text; // If no task specified, just return the text as-is
98
+ }
68
99
  switch (task) {
69
100
  case "summarize":
70
101
  return `Summarize the following text:\n${text}`;
@@ -0,0 +1,17 @@
1
+ export class AIHookError extends Error {
2
+ constructor(code, message, provider, suggestion) {
3
+ super(message);
4
+ this.code = code;
5
+ this.provider = provider;
6
+ this.suggestion = suggestion;
7
+ // Do NOT print pretty message in constructor
8
+ }
9
+ pretty() {
10
+ return `\n❌ AI-HOOK ERROR: ${this.message}` +
11
+ (this.provider ? `\n Provider: ${this.provider}` : "") +
12
+ (this.suggestion ? `\n Suggestion: ${this.suggestion}\n` : "");
13
+ }
14
+ toString() {
15
+ return this.pretty();
16
+ }
17
+ }
@@ -0,0 +1,3 @@
1
+ // src/index.ts
2
+ export { wrap } from "./wrap";
3
+ export { initAIHooks, addProvider, removeProvider, getAvailableProviders, getProvider, isInitialized, reset } from "./providers";
@@ -0,0 +1,114 @@
1
+ import axios from "axios";
2
+ import { AIHookError } from "../../errors";
3
+ export class BaseProvider {
4
+ constructor(config) {
5
+ this.config = config;
6
+ }
7
+ async call(prompt, model) {
8
+ const apiKey = this.getApiKey();
9
+ this.validateApiKey(apiKey);
10
+ // At this point, apiKey is guaranteed to be defined due to validateApiKey
11
+ const validatedApiKey = apiKey;
12
+ try {
13
+ const requestConfig = this.buildRequestConfig(prompt, model, validatedApiKey);
14
+ const response = await this.makeRequest(requestConfig);
15
+ return this.parseResponse(response);
16
+ }
17
+ catch (error) {
18
+ throw this.handleError(error);
19
+ }
20
+ }
21
+ getApiKey() {
22
+ // This method should be overridden by the provider creation logic
23
+ // to return the explicitly provided API key instead of reading from process.env
24
+ if (typeof process !== "undefined" && process.env) {
25
+ return process.env[this.config.envKey];
26
+ }
27
+ return undefined;
28
+ }
29
+ validateApiKey(apiKey) {
30
+ if (!apiKey) {
31
+ throw new AIHookError("INVALID_API_KEY", this.config.errorMessages.missingKey, this.config.name, `Set ${this.config.envKey} in your environment variables.`);
32
+ }
33
+ }
34
+ buildRequestConfig(prompt, model, apiKey) {
35
+ return {
36
+ url: this.config.baseUrl,
37
+ method: "POST",
38
+ data: this.config.requestBody(prompt, model),
39
+ headers: {
40
+ ...this.config.headers,
41
+ ...this.buildAuthHeaders(apiKey)
42
+ }
43
+ };
44
+ }
45
+ buildAuthHeaders(apiKey) {
46
+ return {
47
+ "Authorization": `Bearer ${apiKey}`
48
+ };
49
+ }
50
+ async makeRequest(config) {
51
+ return axios(config);
52
+ }
53
+ parseResponse(response) {
54
+ const output = this.config.responseParser(response);
55
+ if (!output) {
56
+ throw new AIHookError("PROVIDER_ERROR", this.config.errorMessages.emptyResponse, this.config.name, "Check your model and API key");
57
+ }
58
+ return output;
59
+ }
60
+ handleError(error) {
61
+ if (error.response) {
62
+ return this.handleHttpError(error);
63
+ }
64
+ else if (error.request) {
65
+ return new AIHookError("NETWORK_ERROR", this.config.errorMessages.networkError, this.config.name, "Check your internet connection");
66
+ }
67
+ else {
68
+ return new AIHookError("UNKNOWN_ERROR", error.message || this.config.errorMessages.unknownError, this.config.name);
69
+ }
70
+ }
71
+ getCapitalizedProviderName() {
72
+ const name = this.config.name;
73
+ // Handle special cases
74
+ if (name === "openai")
75
+ return "OpenAI";
76
+ if (name === "openrouter")
77
+ return "OpenRouter";
78
+ if (name === "xai")
79
+ return "xAI";
80
+ if (name === "claude")
81
+ return "Claude";
82
+ if (name === "gemini")
83
+ return "Gemini";
84
+ if (name === "groq")
85
+ return "Groq";
86
+ if (name === "deepseek")
87
+ return "DeepSeek";
88
+ if (name === "mistral")
89
+ return "Mistral";
90
+ if (name === "perplexity")
91
+ return "Perplexity";
92
+ // Default: capitalize first letter
93
+ return name.charAt(0).toUpperCase() + name.slice(1);
94
+ }
95
+ handleHttpError(error) {
96
+ const status = error.response.status;
97
+ const text = error.response.data?.error
98
+ ? JSON.stringify(error.response.data.error)
99
+ : error.response.statusText || "Unknown error";
100
+ const providerName = this.config.name;
101
+ switch (status) {
102
+ case 400:
103
+ return new AIHookError("BAD_REQUEST", `${this.getCapitalizedProviderName()} rejected the request: ${text}`, providerName, "Check your prompt and model");
104
+ case 401:
105
+ return new AIHookError("INVALID_API_KEY", `Invalid ${this.getCapitalizedProviderName()} API key: ${text}`, providerName, `Verify your ${this.config.envKey} environment variable`);
106
+ case 403:
107
+ return new AIHookError("MODEL_NOT_ALLOWED", `Your API key cannot access this model: ${text}`, providerName, "Try a different model or check API key permissions");
108
+ case 429:
109
+ return new AIHookError("RATE_LIMIT", `Too many requests to ${this.getCapitalizedProviderName()}: ${text}`, providerName, "Throttle requests or upgrade your plan");
110
+ default:
111
+ return new AIHookError("PROVIDER_ERROR", `${this.getCapitalizedProviderName()} API error: ${text}`, providerName);
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,114 @@
1
+ import { DEFAULT_MODEL } from "../../types";
2
+ import { BaseProvider } from "./BaseProvider";
3
+ import { providerConfigs } from "./ProviderConfigs";
4
+ import { ClaudeProvider, GeminiProvider, OpenRouterProvider } from "./SpecializedProviders";
5
+ export class ProviderManager {
6
+ constructor(options) {
7
+ this.providers = new Map();
8
+ this.providerFunctions = new Map();
9
+ this.initializeProviders(options);
10
+ }
11
+ initializeProviders(options) {
12
+ // Store provider configs
13
+ for (const config of options.providers) {
14
+ this.providers.set(config.provider, {
15
+ key: config.key,
16
+ defaultModel: config.defaultModel
17
+ });
18
+ }
19
+ // Set default provider if specified
20
+ this.defaultProvider = options.defaultProvider;
21
+ // Create provider functions
22
+ this.createProviderFunctions();
23
+ }
24
+ createProviderFunctions() {
25
+ for (const [providerName, config] of this.providers) {
26
+ const providerFunction = this.createProviderFunction(providerName, config);
27
+ this.providerFunctions.set(providerName, providerFunction);
28
+ }
29
+ }
30
+ createProviderFunction(providerName, config) {
31
+ return async (prompt, model) => {
32
+ // Use provided model or default model for this provider
33
+ const modelToUse = model || config.defaultModel || this.getDefaultModelForProvider(providerName);
34
+ // Create a temporary provider instance for this call
35
+ const provider = this.createProviderInstance(providerName, config.key);
36
+ return provider.call(prompt, modelToUse);
37
+ };
38
+ }
39
+ createProviderInstance(providerName, apiKey) {
40
+ // Import the base provider and configs
41
+ // Using imported classes directly
42
+ const config = providerConfigs[providerName];
43
+ if (!config) {
44
+ throw new Error(`Unsupported provider: ${providerName}`);
45
+ }
46
+ // Create provider instance with the provided API key
47
+ const providerConfig = { ...config };
48
+ // Override the getApiKey method to return the provided key
49
+ if (providerName === 'claude') {
50
+ const provider = new ClaudeProvider();
51
+ provider['getApiKey'] = () => apiKey;
52
+ return provider;
53
+ }
54
+ else if (providerName === 'gemini') {
55
+ const provider = new GeminiProvider();
56
+ provider['getApiKey'] = () => apiKey;
57
+ return provider;
58
+ }
59
+ else if (providerName === 'openrouter') {
60
+ const provider = new OpenRouterProvider();
61
+ provider['getApiKey'] = () => apiKey;
62
+ return provider;
63
+ }
64
+ else {
65
+ const provider = new BaseProvider(providerConfig);
66
+ provider['getApiKey'] = () => apiKey;
67
+ return provider;
68
+ }
69
+ }
70
+ getDefaultModelForProvider(providerName) {
71
+ // Import default models
72
+ // Using imported DEFAULT_MODEL directly
73
+ return DEFAULT_MODEL[providerName];
74
+ }
75
+ getAvailableProviders() {
76
+ return Array.from(this.providers.keys());
77
+ }
78
+ getProvider(name) {
79
+ const available = this.getAvailableProviders();
80
+ if (available.length === 0) {
81
+ throw new Error('No providers initialized. Please initialize providers first.');
82
+ }
83
+ // 1. If user specified provider and it's available
84
+ if (name && this.providerFunctions.has(name)) {
85
+ return { fn: this.providerFunctions.get(name), provider: name };
86
+ }
87
+ // 2. If default provider is specified and available
88
+ if (this.defaultProvider && this.providerFunctions.has(this.defaultProvider)) {
89
+ return { fn: this.providerFunctions.get(this.defaultProvider), provider: this.defaultProvider };
90
+ }
91
+ // 3. Prefer OpenRouter if available
92
+ if (this.providerFunctions.has('openrouter')) {
93
+ return { fn: this.providerFunctions.get('openrouter'), provider: 'openrouter' };
94
+ }
95
+ // 4. Use first available provider
96
+ const firstProvider = available[0];
97
+ return { fn: this.providerFunctions.get(firstProvider), provider: firstProvider };
98
+ }
99
+ addProvider(config) {
100
+ this.providers.set(config.provider, {
101
+ key: config.key,
102
+ defaultModel: config.defaultModel
103
+ });
104
+ const providerFunction = this.createProviderFunction(config.provider, {
105
+ key: config.key,
106
+ defaultModel: config.defaultModel
107
+ });
108
+ this.providerFunctions.set(config.provider, providerFunction);
109
+ }
110
+ removeProvider(provider) {
111
+ this.providers.delete(provider);
112
+ this.providerFunctions.delete(provider);
113
+ }
114
+ }
@@ -0,0 +1,182 @@
1
+ // Common response parsers
2
+ export const responseParsers = {
3
+ openaiStyle: (response) => response.data?.choices?.[0]?.message?.content,
4
+ claudeStyle: (response) => response.data?.content?.[0]?.text,
5
+ geminiStyle: (response) => response.data?.candidates?.[0]?.content?.parts?.[0]?.text,
6
+ };
7
+ // Common request body builders
8
+ export const requestBodyBuilders = {
9
+ openaiStyle: (prompt, model) => ({
10
+ model,
11
+ messages: [{ role: "user", content: prompt }]
12
+ }),
13
+ claudeStyle: (prompt, model) => ({
14
+ model,
15
+ max_tokens: 4096,
16
+ messages: [{ role: "user", content: prompt }]
17
+ }),
18
+ geminiStyle: (prompt, model) => ({
19
+ contents: [{
20
+ parts: [{ text: prompt }]
21
+ }]
22
+ }),
23
+ };
24
+ // Provider configurations
25
+ export const providerConfigs = {
26
+ openai: {
27
+ name: "openai",
28
+ baseUrl: "https://api.openai.com/v1/chat/completions",
29
+ envKey: "AI_HOOK_OPENAI_KEY",
30
+ headers: { "Content-Type": "application/json" },
31
+ requestBody: requestBodyBuilders.openaiStyle,
32
+ responseParser: responseParsers.openaiStyle,
33
+ errorMessages: {
34
+ missingKey: "Missing OpenAI API key.",
35
+ emptyResponse: "OpenAI returned empty response",
36
+ badRequest: "OpenAI rejected the request",
37
+ invalidKey: "Invalid OpenAI API key",
38
+ rateLimit: "Too many requests to OpenAI",
39
+ networkError: "Network error while contacting OpenAI",
40
+ unknownError: "Unknown error occurred"
41
+ }
42
+ },
43
+ groq: {
44
+ name: "groq",
45
+ baseUrl: "https://api.groq.com/openai/v1/chat/completions",
46
+ envKey: "AI_HOOK_GROQ_KEY",
47
+ headers: { "Content-Type": "application/json" },
48
+ requestBody: requestBodyBuilders.openaiStyle,
49
+ responseParser: responseParsers.openaiStyle,
50
+ errorMessages: {
51
+ missingKey: "Missing Groq API key.",
52
+ emptyResponse: "Groq returned empty response",
53
+ badRequest: "Groq rejected the request",
54
+ invalidKey: "Invalid Groq API key",
55
+ rateLimit: "Too many requests to Groq",
56
+ networkError: "Network error while contacting Groq",
57
+ unknownError: "Unknown error occurred"
58
+ }
59
+ },
60
+ claude: {
61
+ name: "claude",
62
+ baseUrl: "https://api.anthropic.com/v1/messages",
63
+ envKey: "AI_HOOK_CLAUDE_KEY",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ "anthropic-version": "2023-06-01"
67
+ },
68
+ requestBody: requestBodyBuilders.claudeStyle,
69
+ responseParser: responseParsers.claudeStyle,
70
+ errorMessages: {
71
+ missingKey: "Missing Claude API key.",
72
+ emptyResponse: "Claude returned empty response",
73
+ badRequest: "Claude rejected the request",
74
+ invalidKey: "Invalid Claude API key",
75
+ rateLimit: "Too many requests to Claude",
76
+ networkError: "Network error while contacting Claude",
77
+ unknownError: "Unknown error occurred"
78
+ }
79
+ },
80
+ gemini: {
81
+ name: "gemini",
82
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
83
+ envKey: "AI_HOOK_GEMINI_KEY",
84
+ headers: { "Content-Type": "application/json" },
85
+ requestBody: requestBodyBuilders.geminiStyle,
86
+ responseParser: responseParsers.geminiStyle,
87
+ errorMessages: {
88
+ missingKey: "Missing Gemini API key.",
89
+ emptyResponse: "Gemini returned empty response",
90
+ badRequest: "Gemini rejected the request",
91
+ invalidKey: "Invalid Gemini API key",
92
+ rateLimit: "Too many requests to Gemini",
93
+ networkError: "Network error while contacting Gemini",
94
+ unknownError: "Unknown error occurred"
95
+ }
96
+ },
97
+ deepseek: {
98
+ name: "deepseek",
99
+ baseUrl: "https://api.deepseek.com/v1/chat/completions",
100
+ envKey: "AI_HOOK_DEEPSEEK_KEY",
101
+ headers: { "Content-Type": "application/json" },
102
+ requestBody: requestBodyBuilders.openaiStyle,
103
+ responseParser: responseParsers.openaiStyle,
104
+ errorMessages: {
105
+ missingKey: "Missing DeepSeek API key.",
106
+ emptyResponse: "DeepSeek returned empty response",
107
+ badRequest: "DeepSeek rejected the request",
108
+ invalidKey: "Invalid DeepSeek API key",
109
+ rateLimit: "Too many requests to DeepSeek",
110
+ networkError: "Network error while contacting DeepSeek",
111
+ unknownError: "Unknown error occurred"
112
+ }
113
+ },
114
+ mistral: {
115
+ name: "mistral",
116
+ baseUrl: "https://api.mistral.ai/v1/chat/completions",
117
+ envKey: "AI_HOOK_MISTRAL_KEY",
118
+ headers: { "Content-Type": "application/json" },
119
+ requestBody: requestBodyBuilders.openaiStyle,
120
+ responseParser: responseParsers.openaiStyle,
121
+ errorMessages: {
122
+ missingKey: "Missing Mistral API key.",
123
+ emptyResponse: "Mistral returned empty response",
124
+ badRequest: "Mistral rejected the request",
125
+ invalidKey: "Invalid Mistral API key",
126
+ rateLimit: "Too many requests to Mistral",
127
+ networkError: "Network error while contacting Mistral",
128
+ unknownError: "Unknown error occurred"
129
+ }
130
+ },
131
+ xai: {
132
+ name: "xai",
133
+ baseUrl: "https://api.x.ai/v1/chat/completions",
134
+ envKey: "AI_HOOK_XAI_KEY",
135
+ headers: { "Content-Type": "application/json" },
136
+ requestBody: requestBodyBuilders.openaiStyle,
137
+ responseParser: responseParsers.openaiStyle,
138
+ errorMessages: {
139
+ missingKey: "Missing xAI API key.",
140
+ emptyResponse: "xAI returned empty response",
141
+ badRequest: "xAI rejected the request",
142
+ invalidKey: "Invalid xAI API key",
143
+ rateLimit: "Too many requests to xAI",
144
+ networkError: "Network error while contacting xAI",
145
+ unknownError: "Unknown error occurred"
146
+ }
147
+ },
148
+ perplexity: {
149
+ name: "perplexity",
150
+ baseUrl: "https://api.perplexity.ai/chat/completions",
151
+ envKey: "AI_HOOK_PERPLEXITY_KEY",
152
+ headers: { "Content-Type": "application/json" },
153
+ requestBody: requestBodyBuilders.openaiStyle,
154
+ responseParser: responseParsers.openaiStyle,
155
+ errorMessages: {
156
+ missingKey: "Missing Perplexity API key.",
157
+ emptyResponse: "Perplexity returned empty response",
158
+ badRequest: "Perplexity rejected the request",
159
+ invalidKey: "Invalid Perplexity API key",
160
+ rateLimit: "Too many requests to Perplexity",
161
+ networkError: "Network error while contacting Perplexity",
162
+ unknownError: "Unknown error occurred"
163
+ }
164
+ },
165
+ openrouter: {
166
+ name: "openrouter",
167
+ baseUrl: "https://openrouter.ai/api/v1/chat/completions",
168
+ envKey: "AI_HOOK_OPENROUTER_KEY",
169
+ headers: { "Content-Type": "application/json" },
170
+ requestBody: requestBodyBuilders.openaiStyle,
171
+ responseParser: responseParsers.openaiStyle,
172
+ errorMessages: {
173
+ missingKey: "Missing OpenRouter API key.",
174
+ emptyResponse: "OpenRouter returned empty response",
175
+ badRequest: "OpenRouter rejected the request",
176
+ invalidKey: "Invalid OpenRouter API key",
177
+ rateLimit: "Too many requests to OpenRouter",
178
+ networkError: "Network error while contacting OpenRouter",
179
+ unknownError: "Unknown error occurred"
180
+ }
181
+ }
182
+ };
@@ -0,0 +1,43 @@
1
+ export class ProviderRegistry {
2
+ constructor() {
3
+ this.providers = new Map();
4
+ this.providerFunctions = new Map();
5
+ }
6
+ register(name, provider) {
7
+ this.providers.set(name, provider);
8
+ this.providerFunctions.set(name, this.createProviderFunction(provider));
9
+ }
10
+ get(name) {
11
+ return this.providerFunctions.get(name);
12
+ }
13
+ getAvailableProviders() {
14
+ const available = [];
15
+ // Always prefer openrouter if present (matching original behavior)
16
+ if (this.providers.has("openrouter")) {
17
+ const openrouterProvider = this.providers.get("openrouter");
18
+ const apiKey = openrouterProvider['getApiKey']();
19
+ if (apiKey) {
20
+ available.push("openrouter");
21
+ }
22
+ }
23
+ // Add others in order of their presence (matching original behavior)
24
+ const otherProviders = ['groq', 'openai', 'gemini', 'claude', 'deepseek', 'xai', 'perplexity', 'mistral'];
25
+ for (const providerName of otherProviders) {
26
+ if (this.providers.has(providerName)) {
27
+ const provider = this.providers.get(providerName);
28
+ const apiKey = provider['getApiKey']();
29
+ if (apiKey && !available.includes(providerName)) {
30
+ available.push(providerName);
31
+ }
32
+ }
33
+ }
34
+ return available;
35
+ }
36
+ createProviderFunction(provider) {
37
+ return async (prompt, model) => {
38
+ return provider.call(prompt, model);
39
+ };
40
+ }
41
+ }
42
+ // Global registry instance
43
+ export const providerRegistry = new ProviderRegistry();