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.
- package/LICENSE +1 -1
- package/Readme.md +379 -137
- package/dist/cjs/index.js +14 -0
- package/dist/cjs/providers/base/BaseProvider.js +121 -0
- package/dist/cjs/providers/base/ProviderConfig.js +118 -0
- package/dist/cjs/providers/base/ProviderConfigs.js +185 -0
- package/dist/cjs/providers/base/ProviderRegistry.js +47 -0
- package/dist/cjs/providers/base/SpecializedProviders.js +58 -0
- package/dist/cjs/providers/index.js +82 -0
- package/dist/cjs/providers/init.js +90 -0
- package/dist/{wrap.js → cjs/wrap.js} +43 -12
- package/dist/esm/errors.js +17 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/providers/base/BaseProvider.js +114 -0
- package/dist/esm/providers/base/ProviderConfig.js +114 -0
- package/dist/esm/providers/base/ProviderConfigs.js +182 -0
- package/dist/esm/providers/base/ProviderRegistry.js +43 -0
- package/dist/esm/providers/base/SpecializedProviders.js +52 -0
- package/dist/esm/providers/index.js +75 -0
- package/dist/esm/providers/init.js +81 -0
- package/dist/esm/types/claude.js +4 -0
- package/dist/esm/types/core/providers.js +1 -0
- package/dist/esm/types/deepseek.js +4 -0
- package/dist/esm/types/gemini.js +4 -0
- package/dist/esm/types/groq.js +1 -0
- package/dist/esm/types/index.js +20 -0
- package/dist/esm/types/mistral.js +4 -0
- package/dist/esm/types/openai.js +4 -0
- package/dist/esm/types/openrouter.js +1 -0
- package/dist/esm/types/perplexity.js +4 -0
- package/dist/esm/types/xai.js +4 -0
- package/dist/esm/wrap.js +113 -0
- package/dist/index.d.ts +1 -0
- package/dist/providers/base/BaseProvider.d.ts +33 -0
- package/dist/providers/base/ProviderConfig.d.ts +28 -0
- package/dist/providers/base/ProviderConfigs.d.ts +31 -0
- package/dist/providers/base/ProviderRegistry.d.ts +12 -0
- package/dist/providers/base/SpecializedProviders.d.ts +68 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/init.d.ts +58 -0
- package/package.json +50 -12
- package/dist/index.js +0 -6
- package/dist/providers/claude.d.ts +0 -2
- package/dist/providers/claude.js +0 -60
- package/dist/providers/deepkseek.d.ts +0 -2
- package/dist/providers/deepkseek.js +0 -57
- package/dist/providers/gemini.d.ts +0 -2
- package/dist/providers/gemini.js +0 -58
- package/dist/providers/groq.d.ts +0 -2
- package/dist/providers/groq.js +0 -57
- package/dist/providers/index.js +0 -75
- package/dist/providers/mistral.d.ts +0 -2
- package/dist/providers/mistral.js +0 -57
- package/dist/providers/openai.d.ts +0 -2
- package/dist/providers/openai.js +0 -57
- package/dist/providers/openrouter.d.ts +0 -1
- package/dist/providers/openrouter.js +0 -55
- package/dist/providers/perplexity.d.ts +0 -2
- package/dist/providers/perplexity.js +0 -57
- package/dist/providers/xai.d.ts +0 -2
- package/dist/providers/xai.js +0 -57
- /package/dist/{errors.js → cjs/errors.js} +0 -0
- /package/dist/{types → cjs/types}/claude.js +0 -0
- /package/dist/{types → cjs/types}/core/providers.js +0 -0
- /package/dist/{types → cjs/types}/deepseek.js +0 -0
- /package/dist/{types → cjs/types}/gemini.js +0 -0
- /package/dist/{types → cjs/types}/groq.js +0 -0
- /package/dist/{types → cjs/types}/index.js +0 -0
- /package/dist/{types → cjs/types}/mistral.js +0 -0
- /package/dist/{types → cjs/types}/openai.js +0 -0
- /package/dist/{types → cjs/types}/openrouter.js +0 -0
- /package/dist/{types → cjs/types}/perplexity.js +0 -0
- /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
|
+
}
|