npm-ai-hooks 2.0.4 → 2.0.6
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/dist/cjs/errors.js +3 -0
- package/dist/cjs/providers/base/BaseProvider.js +43 -9
- package/dist/cjs/providers/base/ProviderConfig.js +3 -2
- package/dist/cjs/providers/base/ProviderConfigs.js +4 -1
- package/dist/cjs/providers/base/ProviderRegistry.js +5 -4
- package/dist/cjs/providers/index.js +4 -3
- package/dist/cjs/utils/keyValidator.js +9 -5
- package/dist/cjs/wrap.js +4 -2
- package/dist/esm/errors.js +3 -0
- package/dist/esm/providers/base/BaseProvider.js +43 -6
- package/dist/esm/providers/base/ProviderConfig.js +3 -2
- package/dist/esm/providers/base/ProviderConfigs.js +4 -1
- package/dist/esm/providers/base/ProviderRegistry.js +5 -4
- package/dist/esm/providers/index.js +4 -3
- package/dist/esm/utils/keyValidator.js +9 -5
- package/dist/esm/wrap.js +4 -2
- package/dist/providers/base/BaseProvider.d.ts +22 -8
- package/dist/providers/base/ProviderConfig.d.ts +2 -2
- package/dist/providers/base/ProviderConfigs.d.ts +5 -5
- package/dist/providers/base/SpecializedProviders.d.ts +11 -65
- package/dist/providers/init.d.ts +2 -2
- package/dist/wrap.d.ts +12 -2
- package/package.json +10 -8
package/dist/cjs/errors.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
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.BaseProvider = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
4
|
const errors_1 = require("../../errors");
|
|
9
5
|
class BaseProvider {
|
|
6
|
+
config;
|
|
10
7
|
constructor(config) {
|
|
11
8
|
this.config = config;
|
|
12
9
|
}
|
|
@@ -54,7 +51,43 @@ class BaseProvider {
|
|
|
54
51
|
};
|
|
55
52
|
}
|
|
56
53
|
async makeRequest(config) {
|
|
57
|
-
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(config.url, {
|
|
56
|
+
method: config.method || "POST",
|
|
57
|
+
headers: config.headers,
|
|
58
|
+
body: config.data ? JSON.stringify(config.data) : undefined,
|
|
59
|
+
});
|
|
60
|
+
let data;
|
|
61
|
+
try {
|
|
62
|
+
data = await response.json();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
data = await response.text();
|
|
66
|
+
}
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
// Emulate axios error shape for the error handler
|
|
69
|
+
throw Object.assign(new Error(`Request failed with status code ${response.status}`), {
|
|
70
|
+
response: {
|
|
71
|
+
status: response.status,
|
|
72
|
+
statusText: response.statusText,
|
|
73
|
+
data: data
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return { data };
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const err = error;
|
|
81
|
+
// If it's already an HTTP error we just threw, rethrow it
|
|
82
|
+
if (err.response) {
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
// Otherwise, it's a network error (e.g. fetch failed entirely)
|
|
86
|
+
// Emulate axios network error shape
|
|
87
|
+
throw Object.assign(new Error(err.message || "Network Error"), {
|
|
88
|
+
request: {}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
58
91
|
}
|
|
59
92
|
parseResponse(response) {
|
|
60
93
|
const output = this.config.responseParser(response);
|
|
@@ -64,14 +97,15 @@ class BaseProvider {
|
|
|
64
97
|
return output;
|
|
65
98
|
}
|
|
66
99
|
handleError(error) {
|
|
67
|
-
|
|
68
|
-
|
|
100
|
+
const err = error;
|
|
101
|
+
if (err.response) {
|
|
102
|
+
return this.handleHttpError(err);
|
|
69
103
|
}
|
|
70
|
-
else if (
|
|
104
|
+
else if (err.request) {
|
|
71
105
|
return new errors_1.AIHookError("NETWORK_ERROR", this.config.errorMessages.networkError, this.config.name, "Check your internet connection");
|
|
72
106
|
}
|
|
73
107
|
else {
|
|
74
|
-
return new errors_1.AIHookError("UNKNOWN_ERROR",
|
|
108
|
+
return new errors_1.AIHookError("UNKNOWN_ERROR", err.message || this.config.errorMessages.unknownError, this.config.name);
|
|
75
109
|
}
|
|
76
110
|
}
|
|
77
111
|
getCapitalizedProviderName() {
|
|
@@ -6,9 +6,10 @@ const BaseProvider_1 = require("./BaseProvider");
|
|
|
6
6
|
const ProviderConfigs_1 = require("./ProviderConfigs");
|
|
7
7
|
const SpecializedProviders_1 = require("./SpecializedProviders");
|
|
8
8
|
class ProviderManager {
|
|
9
|
+
providers = new Map();
|
|
10
|
+
defaultProvider;
|
|
11
|
+
providerFunctions = new Map();
|
|
9
12
|
constructor(options) {
|
|
10
|
-
this.providers = new Map();
|
|
11
|
-
this.providerFunctions = new Map();
|
|
12
13
|
this.initializeProviders(options);
|
|
13
14
|
}
|
|
14
15
|
initializeProviders(options) {
|
|
@@ -3,8 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.providerConfigs = exports.requestBodyBuilders = exports.responseParsers = void 0;
|
|
4
4
|
// Common response parsers
|
|
5
5
|
exports.responseParsers = {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
7
|
openaiStyle: (response) => response.data?.choices?.[0]?.message?.content,
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
9
|
claudeStyle: (response) => response.data?.content?.[0]?.text,
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
11
|
geminiStyle: (response) => response.data?.candidates?.[0]?.content?.parts?.[0]?.text,
|
|
9
12
|
};
|
|
10
13
|
// Common request body builders
|
|
@@ -18,7 +21,7 @@ exports.requestBodyBuilders = {
|
|
|
18
21
|
max_tokens: 4096,
|
|
19
22
|
messages: [{ role: "user", content: prompt }]
|
|
20
23
|
}),
|
|
21
|
-
geminiStyle: (prompt,
|
|
24
|
+
geminiStyle: (prompt, _model) => ({
|
|
22
25
|
contents: [{
|
|
23
26
|
parts: [{ text: prompt }]
|
|
24
27
|
}]
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.providerRegistry = exports.ProviderRegistry = void 0;
|
|
4
4
|
class ProviderRegistry {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
5
|
+
providers = new Map();
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
providerFunctions = new Map();
|
|
9
8
|
register(name, provider) {
|
|
10
9
|
this.providers.set(name, provider);
|
|
11
10
|
this.providerFunctions.set(name, this.createProviderFunction(provider));
|
|
12
11
|
}
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
13
|
get(name) {
|
|
14
14
|
return this.providerFunctions.get(name);
|
|
15
15
|
}
|
|
@@ -36,6 +36,7 @@ class ProviderRegistry {
|
|
|
36
36
|
}
|
|
37
37
|
return available;
|
|
38
38
|
}
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
40
|
createProviderFunction(provider) {
|
|
40
41
|
return async (prompt, model) => {
|
|
41
42
|
return provider.call(prompt, model);
|
|
@@ -46,7 +46,7 @@ const providers = {
|
|
|
46
46
|
xai: ProviderRegistry_1.providerRegistry.get("xai"),
|
|
47
47
|
perplexity: ProviderRegistry_1.providerRegistry.get("perplexity"),
|
|
48
48
|
mistral: ProviderRegistry_1.providerRegistry.get("mistral"),
|
|
49
|
-
mock: async (prompt,
|
|
49
|
+
mock: async (prompt, _model) => `[MOCK OUTPUT] ${prompt}`
|
|
50
50
|
};
|
|
51
51
|
// Returns an array of providers whose API keys exist in environment (legacy)
|
|
52
52
|
function getAvailableProviders() {
|
|
@@ -57,7 +57,7 @@ function getAvailableProviders() {
|
|
|
57
57
|
// Otherwise use legacy system
|
|
58
58
|
return ProviderRegistry_1.providerRegistry.getAvailableProviders();
|
|
59
59
|
}
|
|
60
|
-
//
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
61
|
function getProvider(name) {
|
|
62
62
|
// If new system is initialized, use it
|
|
63
63
|
if ((0, init_1.isInitialized)()) {
|
|
@@ -81,7 +81,7 @@ function getProvider(name) {
|
|
|
81
81
|
// 3. No valid keys found → throw error (single instruction, no fallback)
|
|
82
82
|
throw new errors_1.AIHookError("NO_PROVIDER_FOUND", "No valid AI provider API key was found.\n\nIf you are using a .env file, please ensure you have installed the 'dotenv' package (npm i dotenv) and called require('dotenv').config() at the very top of your entry file.\n\nAlternatively, you can initialize providers explicitly:\ninitAIHooks({ providers: [{ provider: 'openai', key: 'your-key-here' }] })", undefined, "Reference documentation for setup instructions.");
|
|
83
83
|
}
|
|
84
|
-
//
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
85
|
function getProviderChain(name) {
|
|
86
86
|
if ((0, init_1.isInitialized)()) {
|
|
87
87
|
return (0, init_1.getProviderChain)(name);
|
|
@@ -89,6 +89,7 @@ function getProviderChain(name) {
|
|
|
89
89
|
// Legacy fallback: wrap available providers into a chain
|
|
90
90
|
const available = getAvailableProviders();
|
|
91
91
|
const seen = new Set();
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
93
|
const chain = [];
|
|
93
94
|
const tryPush = (p) => {
|
|
94
95
|
if (!seen.has(p) && providers[p]) {
|
|
@@ -31,7 +31,7 @@ async function validateApiKey(provider, apiKey) {
|
|
|
31
31
|
case 'openrouter':
|
|
32
32
|
providerInstance = new SpecializedProviders_1.OpenRouterProvider();
|
|
33
33
|
break;
|
|
34
|
-
default:
|
|
34
|
+
default: {
|
|
35
35
|
const config = ProviderConfigs_1.providerConfigs[provider];
|
|
36
36
|
if (!config) {
|
|
37
37
|
return {
|
|
@@ -42,9 +42,12 @@ async function validateApiKey(provider, apiKey) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
providerInstance = new BaseProvider_1.BaseProvider(config);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
// Set the API key temporarily
|
|
47
|
-
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
providerInstance.getApiKey = () => apiKey;
|
|
48
51
|
// Make a minimal test request
|
|
49
52
|
const testPrompt = "Hi";
|
|
50
53
|
const testModel = getDefaultModel(provider);
|
|
@@ -56,8 +59,9 @@ async function validateApiKey(provider, apiKey) {
|
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
61
|
catch (error) {
|
|
62
|
+
const err = error;
|
|
59
63
|
// Check for common error types
|
|
60
|
-
if (
|
|
64
|
+
if (err.message?.includes('401') || err.message?.includes('Unauthorized') || err.message?.includes('Invalid API key')) {
|
|
61
65
|
return {
|
|
62
66
|
valid: false,
|
|
63
67
|
provider,
|
|
@@ -65,7 +69,7 @@ async function validateApiKey(provider, apiKey) {
|
|
|
65
69
|
message: 'API key is invalid or unauthorized'
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
|
-
if (
|
|
72
|
+
if (err.message?.includes('403') || err.message?.includes('Forbidden')) {
|
|
69
73
|
return {
|
|
70
74
|
valid: false,
|
|
71
75
|
provider,
|
|
@@ -73,7 +77,7 @@ async function validateApiKey(provider, apiKey) {
|
|
|
73
77
|
message: 'API key does not have access to this resource'
|
|
74
78
|
};
|
|
75
79
|
}
|
|
76
|
-
if (
|
|
80
|
+
if (err.message?.includes('429') || err.message?.includes('rate limit')) {
|
|
77
81
|
// Rate limit means the key is valid, just too many requests
|
|
78
82
|
return {
|
|
79
83
|
valid: true,
|
package/dist/cjs/wrap.js
CHANGED
|
@@ -6,7 +6,7 @@ const providers_1 = require("./providers");
|
|
|
6
6
|
const types_1 = require("./types");
|
|
7
7
|
const errors_1 = require("./errors");
|
|
8
8
|
function handleError(err) {
|
|
9
|
-
if (err
|
|
9
|
+
if (err instanceof errors_1.AIHookError) {
|
|
10
10
|
// Print pretty message
|
|
11
11
|
console.error(err.pretty());
|
|
12
12
|
// Only exit in Node.js test environment, not in browser
|
|
@@ -26,6 +26,7 @@ function handleError(err) {
|
|
|
26
26
|
throw err;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
30
|
function wrap(fn, options) {
|
|
30
31
|
// Validate task type immediately when wrap() is called
|
|
31
32
|
const validTasks = ["summarize", "translate", "explain", "rewrite", "sentiment", "codeReview"];
|
|
@@ -126,9 +127,10 @@ function buildPrompt(task, text, targetLanguage) {
|
|
|
126
127
|
switch (task) {
|
|
127
128
|
case "summarize":
|
|
128
129
|
return `Summarize the following text:\n${text}`;
|
|
129
|
-
case "translate":
|
|
130
|
+
case "translate": {
|
|
130
131
|
const language = targetLanguage || "English"; // default to English
|
|
131
132
|
return `Translate this text into ${language}:\n${text}`;
|
|
133
|
+
}
|
|
132
134
|
case "explain":
|
|
133
135
|
return `Explain this clearly:\n${text}`;
|
|
134
136
|
case "rewrite":
|
package/dist/esm/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
1
|
import { AIHookError } from "../../errors";
|
|
3
2
|
export class BaseProvider {
|
|
3
|
+
config;
|
|
4
4
|
constructor(config) {
|
|
5
5
|
this.config = config;
|
|
6
6
|
}
|
|
@@ -48,7 +48,43 @@ export class BaseProvider {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
async makeRequest(config) {
|
|
51
|
-
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(config.url, {
|
|
53
|
+
method: config.method || "POST",
|
|
54
|
+
headers: config.headers,
|
|
55
|
+
body: config.data ? JSON.stringify(config.data) : undefined,
|
|
56
|
+
});
|
|
57
|
+
let data;
|
|
58
|
+
try {
|
|
59
|
+
data = await response.json();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
data = await response.text();
|
|
63
|
+
}
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
// Emulate axios error shape for the error handler
|
|
66
|
+
throw Object.assign(new Error(`Request failed with status code ${response.status}`), {
|
|
67
|
+
response: {
|
|
68
|
+
status: response.status,
|
|
69
|
+
statusText: response.statusText,
|
|
70
|
+
data: data
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return { data };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const err = error;
|
|
78
|
+
// If it's already an HTTP error we just threw, rethrow it
|
|
79
|
+
if (err.response) {
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
// Otherwise, it's a network error (e.g. fetch failed entirely)
|
|
83
|
+
// Emulate axios network error shape
|
|
84
|
+
throw Object.assign(new Error(err.message || "Network Error"), {
|
|
85
|
+
request: {}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
52
88
|
}
|
|
53
89
|
parseResponse(response) {
|
|
54
90
|
const output = this.config.responseParser(response);
|
|
@@ -58,14 +94,15 @@ export class BaseProvider {
|
|
|
58
94
|
return output;
|
|
59
95
|
}
|
|
60
96
|
handleError(error) {
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
const err = error;
|
|
98
|
+
if (err.response) {
|
|
99
|
+
return this.handleHttpError(err);
|
|
63
100
|
}
|
|
64
|
-
else if (
|
|
101
|
+
else if (err.request) {
|
|
65
102
|
return new AIHookError("NETWORK_ERROR", this.config.errorMessages.networkError, this.config.name, "Check your internet connection");
|
|
66
103
|
}
|
|
67
104
|
else {
|
|
68
|
-
return new AIHookError("UNKNOWN_ERROR",
|
|
105
|
+
return new AIHookError("UNKNOWN_ERROR", err.message || this.config.errorMessages.unknownError, this.config.name);
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
108
|
getCapitalizedProviderName() {
|
|
@@ -3,9 +3,10 @@ import { BaseProvider } from "./BaseProvider";
|
|
|
3
3
|
import { providerConfigs } from "./ProviderConfigs";
|
|
4
4
|
import { ClaudeProvider, GeminiProvider, OpenRouterProvider } from "./SpecializedProviders";
|
|
5
5
|
export class ProviderManager {
|
|
6
|
+
providers = new Map();
|
|
7
|
+
defaultProvider;
|
|
8
|
+
providerFunctions = new Map();
|
|
6
9
|
constructor(options) {
|
|
7
|
-
this.providers = new Map();
|
|
8
|
-
this.providerFunctions = new Map();
|
|
9
10
|
this.initializeProviders(options);
|
|
10
11
|
}
|
|
11
12
|
initializeProviders(options) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Common response parsers
|
|
2
2
|
export const responseParsers = {
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
4
|
openaiStyle: (response) => response.data?.choices?.[0]?.message?.content,
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
6
|
claudeStyle: (response) => response.data?.content?.[0]?.text,
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
8
|
geminiStyle: (response) => response.data?.candidates?.[0]?.content?.parts?.[0]?.text,
|
|
6
9
|
};
|
|
7
10
|
// Common request body builders
|
|
@@ -15,7 +18,7 @@ export const requestBodyBuilders = {
|
|
|
15
18
|
max_tokens: 4096,
|
|
16
19
|
messages: [{ role: "user", content: prompt }]
|
|
17
20
|
}),
|
|
18
|
-
geminiStyle: (prompt,
|
|
21
|
+
geminiStyle: (prompt, _model) => ({
|
|
19
22
|
contents: [{
|
|
20
23
|
parts: [{ text: prompt }]
|
|
21
24
|
}]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export class ProviderRegistry {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
2
|
+
providers = new Map();
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
providerFunctions = new Map();
|
|
6
5
|
register(name, provider) {
|
|
7
6
|
this.providers.set(name, provider);
|
|
8
7
|
this.providerFunctions.set(name, this.createProviderFunction(provider));
|
|
9
8
|
}
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
10
|
get(name) {
|
|
11
11
|
return this.providerFunctions.get(name);
|
|
12
12
|
}
|
|
@@ -33,6 +33,7 @@ export class ProviderRegistry {
|
|
|
33
33
|
}
|
|
34
34
|
return available;
|
|
35
35
|
}
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
37
|
createProviderFunction(provider) {
|
|
37
38
|
return async (prompt, model) => {
|
|
38
39
|
return provider.call(prompt, model);
|
|
@@ -34,7 +34,7 @@ const providers = {
|
|
|
34
34
|
xai: providerRegistry.get("xai"),
|
|
35
35
|
perplexity: providerRegistry.get("perplexity"),
|
|
36
36
|
mistral: providerRegistry.get("mistral"),
|
|
37
|
-
mock: async (prompt,
|
|
37
|
+
mock: async (prompt, _model) => `[MOCK OUTPUT] ${prompt}`
|
|
38
38
|
};
|
|
39
39
|
// Returns an array of providers whose API keys exist in environment (legacy)
|
|
40
40
|
export function getAvailableProviders() {
|
|
@@ -45,7 +45,7 @@ export function getAvailableProviders() {
|
|
|
45
45
|
// Otherwise use legacy system
|
|
46
46
|
return providerRegistry.getAvailableProviders();
|
|
47
47
|
}
|
|
48
|
-
//
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
49
|
export function getProvider(name) {
|
|
50
50
|
// If new system is initialized, use it
|
|
51
51
|
if (isInitialized()) {
|
|
@@ -69,7 +69,7 @@ export function getProvider(name) {
|
|
|
69
69
|
// 3. No valid keys found → throw error (single instruction, no fallback)
|
|
70
70
|
throw new AIHookError("NO_PROVIDER_FOUND", "No valid AI provider API key was found.\n\nIf you are using a .env file, please ensure you have installed the 'dotenv' package (npm i dotenv) and called require('dotenv').config() at the very top of your entry file.\n\nAlternatively, you can initialize providers explicitly:\ninitAIHooks({ providers: [{ provider: 'openai', key: 'your-key-here' }] })", undefined, "Reference documentation for setup instructions.");
|
|
71
71
|
}
|
|
72
|
-
//
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
73
|
export function getProviderChain(name) {
|
|
74
74
|
if (isInitialized()) {
|
|
75
75
|
return getNewProviderChain(name);
|
|
@@ -77,6 +77,7 @@ export function getProviderChain(name) {
|
|
|
77
77
|
// Legacy fallback: wrap available providers into a chain
|
|
78
78
|
const available = getAvailableProviders();
|
|
79
79
|
const seen = new Set();
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
81
|
const chain = [];
|
|
81
82
|
const tryPush = (p) => {
|
|
82
83
|
if (!seen.has(p) && providers[p]) {
|
|
@@ -26,7 +26,7 @@ export async function validateApiKey(provider, apiKey) {
|
|
|
26
26
|
case 'openrouter':
|
|
27
27
|
providerInstance = new OpenRouterProvider();
|
|
28
28
|
break;
|
|
29
|
-
default:
|
|
29
|
+
default: {
|
|
30
30
|
const config = providerConfigs[provider];
|
|
31
31
|
if (!config) {
|
|
32
32
|
return {
|
|
@@ -37,9 +37,12 @@ export async function validateApiKey(provider, apiKey) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
providerInstance = new BaseProvider(config);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
40
42
|
}
|
|
41
43
|
// Set the API key temporarily
|
|
42
|
-
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
providerInstance.getApiKey = () => apiKey;
|
|
43
46
|
// Make a minimal test request
|
|
44
47
|
const testPrompt = "Hi";
|
|
45
48
|
const testModel = getDefaultModel(provider);
|
|
@@ -51,8 +54,9 @@ export async function validateApiKey(provider, apiKey) {
|
|
|
51
54
|
};
|
|
52
55
|
}
|
|
53
56
|
catch (error) {
|
|
57
|
+
const err = error;
|
|
54
58
|
// Check for common error types
|
|
55
|
-
if (
|
|
59
|
+
if (err.message?.includes('401') || err.message?.includes('Unauthorized') || err.message?.includes('Invalid API key')) {
|
|
56
60
|
return {
|
|
57
61
|
valid: false,
|
|
58
62
|
provider,
|
|
@@ -60,7 +64,7 @@ export async function validateApiKey(provider, apiKey) {
|
|
|
60
64
|
message: 'API key is invalid or unauthorized'
|
|
61
65
|
};
|
|
62
66
|
}
|
|
63
|
-
if (
|
|
67
|
+
if (err.message?.includes('403') || err.message?.includes('Forbidden')) {
|
|
64
68
|
return {
|
|
65
69
|
valid: false,
|
|
66
70
|
provider,
|
|
@@ -68,7 +72,7 @@ export async function validateApiKey(provider, apiKey) {
|
|
|
68
72
|
message: 'API key does not have access to this resource'
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
|
-
if (
|
|
75
|
+
if (err.message?.includes('429') || err.message?.includes('rate limit')) {
|
|
72
76
|
// Rate limit means the key is valid, just too many requests
|
|
73
77
|
return {
|
|
74
78
|
valid: true,
|
package/dist/esm/wrap.js
CHANGED
|
@@ -3,7 +3,7 @@ import { getProviderChain } from "./providers";
|
|
|
3
3
|
import { DEFAULT_MODEL } from "./types";
|
|
4
4
|
import { AIHookError } from "./errors";
|
|
5
5
|
function handleError(err) {
|
|
6
|
-
if (err
|
|
6
|
+
if (err instanceof AIHookError) {
|
|
7
7
|
// Print pretty message
|
|
8
8
|
console.error(err.pretty());
|
|
9
9
|
// Only exit in Node.js test environment, not in browser
|
|
@@ -23,6 +23,7 @@ function handleError(err) {
|
|
|
23
23
|
throw err;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
27
|
export function wrap(fn, options) {
|
|
27
28
|
// Validate task type immediately when wrap() is called
|
|
28
29
|
const validTasks = ["summarize", "translate", "explain", "rewrite", "sentiment", "codeReview"];
|
|
@@ -123,9 +124,10 @@ function buildPrompt(task, text, targetLanguage) {
|
|
|
123
124
|
switch (task) {
|
|
124
125
|
case "summarize":
|
|
125
126
|
return `Summarize the following text:\n${text}`;
|
|
126
|
-
case "translate":
|
|
127
|
+
case "translate": {
|
|
127
128
|
const language = targetLanguage || "English"; // default to English
|
|
128
129
|
return `Translate this text into ${language}:\n${text}`;
|
|
130
|
+
}
|
|
129
131
|
case "explain":
|
|
130
132
|
return `Explain this clearly:\n${text}`;
|
|
131
133
|
case "rewrite":
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
2
1
|
import { AIHookError } from "../../errors";
|
|
2
|
+
export interface FetchRequestConfig {
|
|
3
|
+
url: string;
|
|
4
|
+
method?: string;
|
|
5
|
+
data?: unknown;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface FetchResponse {
|
|
9
|
+
data: unknown;
|
|
10
|
+
}
|
|
3
11
|
export interface ProviderConfig {
|
|
4
12
|
name: string;
|
|
5
13
|
baseUrl: string;
|
|
6
14
|
envKey: string;
|
|
7
15
|
headers: Record<string, string>;
|
|
8
|
-
requestBody: (prompt: string, model: string) =>
|
|
9
|
-
responseParser: (response:
|
|
16
|
+
requestBody: (prompt: string, model: string) => unknown;
|
|
17
|
+
responseParser: (response: FetchResponse) => string;
|
|
10
18
|
errorMessages: {
|
|
11
19
|
missingKey: string;
|
|
12
20
|
emptyResponse: string;
|
|
@@ -23,11 +31,17 @@ export declare class BaseProvider {
|
|
|
23
31
|
call(prompt: string, model: string): Promise<string>;
|
|
24
32
|
protected getApiKey(): string | undefined;
|
|
25
33
|
protected validateApiKey(apiKey: string | undefined): void;
|
|
26
|
-
protected buildRequestConfig(prompt: string, model: string, apiKey: string):
|
|
34
|
+
protected buildRequestConfig(prompt: string, model: string, apiKey: string): FetchRequestConfig;
|
|
27
35
|
protected buildAuthHeaders(apiKey: string): Record<string, string>;
|
|
28
|
-
protected makeRequest(config:
|
|
29
|
-
protected parseResponse(response:
|
|
30
|
-
protected handleError(error:
|
|
36
|
+
protected makeRequest(config: FetchRequestConfig): Promise<FetchResponse>;
|
|
37
|
+
protected parseResponse(response: FetchResponse): string;
|
|
38
|
+
protected handleError(error: unknown): AIHookError;
|
|
31
39
|
protected getCapitalizedProviderName(): string;
|
|
32
|
-
protected handleHttpError(error:
|
|
40
|
+
protected handleHttpError(error: Error & {
|
|
41
|
+
response: {
|
|
42
|
+
status: number;
|
|
43
|
+
statusText?: string;
|
|
44
|
+
data?: any;
|
|
45
|
+
};
|
|
46
|
+
}): AIHookError;
|
|
33
47
|
}
|
|
@@ -25,11 +25,11 @@ export declare class ProviderManager {
|
|
|
25
25
|
* Used by the fallback mechanism in wrap().
|
|
26
26
|
*/
|
|
27
27
|
getProviderChain(name?: Provider): Array<{
|
|
28
|
-
fn:
|
|
28
|
+
fn: (prompt: string, model?: string) => Promise<string>;
|
|
29
29
|
provider: Provider;
|
|
30
30
|
}>;
|
|
31
31
|
getProvider(name?: Provider): {
|
|
32
|
-
fn:
|
|
32
|
+
fn: (prompt: string, model?: string) => Promise<string>;
|
|
33
33
|
provider: Provider;
|
|
34
34
|
};
|
|
35
35
|
addProvider(config: UserProviderConfig): void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ProviderConfig } from "./BaseProvider";
|
|
1
|
+
import { FetchResponse, ProviderConfig } from "./BaseProvider";
|
|
2
2
|
export declare const responseParsers: {
|
|
3
|
-
openaiStyle: (response:
|
|
4
|
-
claudeStyle: (response:
|
|
5
|
-
geminiStyle: (response:
|
|
3
|
+
openaiStyle: (response: FetchResponse) => any;
|
|
4
|
+
claudeStyle: (response: FetchResponse) => any;
|
|
5
|
+
geminiStyle: (response: FetchResponse) => any;
|
|
6
6
|
};
|
|
7
7
|
export declare const requestBodyBuilders: {
|
|
8
8
|
openaiStyle: (prompt: string, model: string) => {
|
|
@@ -20,7 +20,7 @@ export declare const requestBodyBuilders: {
|
|
|
20
20
|
content: string;
|
|
21
21
|
}[];
|
|
22
22
|
};
|
|
23
|
-
geminiStyle: (prompt: string,
|
|
23
|
+
geminiStyle: (prompt: string, _model: string) => {
|
|
24
24
|
contents: {
|
|
25
25
|
parts: {
|
|
26
26
|
text: string;
|
|
@@ -8,75 +8,21 @@ export declare class GeminiProvider extends BaseProvider {
|
|
|
8
8
|
constructor();
|
|
9
9
|
protected buildRequestConfig(prompt: string, model: string, apiKey: string): {
|
|
10
10
|
url: string;
|
|
11
|
-
headers: {
|
|
12
|
-
|
|
13
|
-
baseURL?: string;
|
|
14
|
-
allowAbsoluteUrls?: boolean;
|
|
15
|
-
transformRequest?: import("axios").AxiosRequestTransformer | import("axios").AxiosRequestTransformer[];
|
|
16
|
-
transformResponse?: import("axios").AxiosResponseTransformer | import("axios").AxiosResponseTransformer[];
|
|
17
|
-
params?: any;
|
|
18
|
-
paramsSerializer?: import("axios").ParamsSerializerOptions | import("axios").CustomParamsSerializer;
|
|
19
|
-
data?: any;
|
|
20
|
-
timeout?: number;
|
|
21
|
-
timeoutErrorMessage?: string;
|
|
22
|
-
withCredentials?: boolean;
|
|
23
|
-
adapter?: (import("axios").AxiosAdapter | ((string & {}) | "xhr" | "http" | "fetch")) | (import("axios").AxiosAdapter | ((string & {}) | "xhr" | "http" | "fetch"))[];
|
|
24
|
-
auth?: import("axios").AxiosBasicCredentials;
|
|
25
|
-
responseType?: import("axios").ResponseType;
|
|
26
|
-
responseEncoding?: (string & {}) | import("axios").responseEncoding;
|
|
27
|
-
xsrfCookieName?: string;
|
|
28
|
-
xsrfHeaderName?: string;
|
|
29
|
-
onUploadProgress?: (progressEvent: import("axios").AxiosProgressEvent) => void;
|
|
30
|
-
onDownloadProgress?: (progressEvent: import("axios").AxiosProgressEvent) => void;
|
|
31
|
-
maxContentLength?: number;
|
|
32
|
-
validateStatus?: ((status: number) => boolean) | null;
|
|
33
|
-
maxBodyLength?: number;
|
|
34
|
-
maxRedirects?: number;
|
|
35
|
-
maxRate?: number | [number, number];
|
|
36
|
-
beforeRedirect?: (options: Record<string, any>, responseDetails: {
|
|
37
|
-
headers: Record<string, string>;
|
|
38
|
-
statusCode: import("axios").HttpStatusCode;
|
|
39
|
-
}, requestDetails: {
|
|
40
|
-
headers: Record<string, string>;
|
|
41
|
-
url: string;
|
|
42
|
-
method: string;
|
|
43
|
-
}) => void;
|
|
44
|
-
socketPath?: string | null;
|
|
45
|
-
allowedSocketPaths?: string | string[] | null;
|
|
46
|
-
transport?: any;
|
|
47
|
-
httpAgent?: any;
|
|
48
|
-
httpsAgent?: any;
|
|
49
|
-
proxy?: import("axios").AxiosProxyConfig | false;
|
|
50
|
-
cancelToken?: import("axios").CancelToken | undefined;
|
|
51
|
-
decompress?: boolean;
|
|
52
|
-
transitional?: import("axios").TransitionalOptions;
|
|
53
|
-
signal?: import("axios").GenericAbortSignal;
|
|
54
|
-
insecureHTTPParser?: boolean;
|
|
55
|
-
env?: {
|
|
56
|
-
FormData?: new (...args: any[]) => object;
|
|
57
|
-
fetch?: (input: URL | Request | string, init?: RequestInit) => Promise<Response>;
|
|
58
|
-
Request?: new (input: URL | Request | string, init?: RequestInit) => Request;
|
|
59
|
-
Response?: new (body?: ArrayBuffer | ArrayBufferView | Blob | FormData | URLSearchParams | string | null, init?: ResponseInit) => Response;
|
|
11
|
+
headers: {
|
|
12
|
+
[x: string]: string;
|
|
60
13
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: import("axios").LookupAddress | import("axios").LookupAddress[], family?: import("axios").AddressFamily) => void) => void) | ((hostname: string, options: object) => Promise<[address: import("axios").LookupAddressEntry | import("axios").LookupAddressEntry[], family?: import("axios").AddressFamily] | import("axios").LookupAddress>);
|
|
64
|
-
withXSRFToken?: boolean | ((config: import("axios").InternalAxiosRequestConfig) => boolean | undefined);
|
|
65
|
-
parseReviver?: (this: any, key: string, value: any, context?: {
|
|
66
|
-
source?: string;
|
|
67
|
-
}) => any;
|
|
68
|
-
fetchOptions?: Omit<RequestInit, "body" | "headers" | "method" | "signal"> | Record<string, any>;
|
|
69
|
-
httpVersion?: 1 | 2;
|
|
70
|
-
http2Options?: Record<string, any> & {
|
|
71
|
-
sessionTimeout?: number;
|
|
72
|
-
};
|
|
73
|
-
formDataHeaderPolicy?: "legacy" | "content-only";
|
|
74
|
-
redact?: string[];
|
|
75
|
-
sensitiveHeaders?: string[];
|
|
14
|
+
method?: string;
|
|
15
|
+
data?: unknown;
|
|
76
16
|
};
|
|
77
17
|
protected buildAuthHeaders(): Record<string, string>;
|
|
78
18
|
}
|
|
79
19
|
export declare class OpenRouterProvider extends BaseProvider {
|
|
80
20
|
constructor();
|
|
81
|
-
protected handleHttpError(error:
|
|
21
|
+
protected handleHttpError(error: Error & {
|
|
22
|
+
response: {
|
|
23
|
+
status: number;
|
|
24
|
+
statusText?: string;
|
|
25
|
+
data?: any;
|
|
26
|
+
};
|
|
27
|
+
}): AIHookError;
|
|
82
28
|
}
|
package/dist/providers/init.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export declare function getAvailableProviders(): Provider[];
|
|
|
43
43
|
* @returns Provider function and name
|
|
44
44
|
*/
|
|
45
45
|
export declare function getProvider(name?: Provider): {
|
|
46
|
-
fn:
|
|
46
|
+
fn: (prompt: string, model?: string) => Promise<string>;
|
|
47
47
|
provider: Provider;
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
@@ -52,7 +52,7 @@ export declare function getProvider(name?: Provider): {
|
|
|
52
52
|
* @returns Ordered array of {fn, provider} to try in sequence
|
|
53
53
|
*/
|
|
54
54
|
export declare function getProviderChain(name?: Provider): Array<{
|
|
55
|
-
fn:
|
|
55
|
+
fn: (prompt: string, model?: string) => Promise<string>;
|
|
56
56
|
provider: Provider;
|
|
57
57
|
}>;
|
|
58
58
|
/**
|
package/dist/wrap.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WrapOptions, Provider } from "./types";
|
|
1
|
+
import { WrapOptions, TaskType, Provider } from "./types";
|
|
2
2
|
export interface MultimodalInput {
|
|
3
3
|
text?: string;
|
|
4
4
|
image?: string;
|
|
@@ -8,7 +8,17 @@ export interface MultimodalInput {
|
|
|
8
8
|
type: string;
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
+
export interface WrapResultMeta {
|
|
12
|
+
provider: Provider;
|
|
13
|
+
model: string;
|
|
14
|
+
task?: TaskType;
|
|
15
|
+
targetLanguage?: string;
|
|
16
|
+
cached: boolean;
|
|
17
|
+
estimatedCostUSD: number;
|
|
18
|
+
latencyMs: number;
|
|
19
|
+
fallback: boolean;
|
|
20
|
+
}
|
|
11
21
|
export declare function wrap<T extends (...args: any[]) => any, P extends Provider | undefined = undefined>(fn: T, options: WrapOptions<P>): (...args: Parameters<T>) => Promise<{
|
|
12
22
|
output: string;
|
|
13
|
-
meta:
|
|
23
|
+
meta: WrapResultMeta;
|
|
14
24
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "npm-ai-hooks",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Universal AI Hook Layer for Node.js and React – one wrapper for all AI providers. Inject LLM-like behavior into any JavaScript or TypeScript function with a single line, without writing prompts, handling SDKs, or locking into any provider.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test:performance": "jest tests/performance.test.ts --verbose",
|
|
35
35
|
"test:env": "jest --testNamePattern=\"Real API|Environment-based\" --verbose",
|
|
36
36
|
"test:mock": "jest --testNamePattern=\"Provider Detection|Task Tests\" --verbose",
|
|
37
|
-
"lint": "eslint .
|
|
37
|
+
"lint": "eslint .",
|
|
38
38
|
"format": "prettier --write .",
|
|
39
39
|
"prepare": "npm run build",
|
|
40
40
|
"demo": "npx ts-node examples/demo.ts",
|
|
@@ -48,19 +48,18 @@
|
|
|
48
48
|
"url": "git+https://github.com/iTeebot/npm-ai-hooks.git"
|
|
49
49
|
},
|
|
50
50
|
"license": "MIT",
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"axios": "^1.12.2"
|
|
53
|
-
},
|
|
54
51
|
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
55
53
|
"@types/jest": "^30.0.0",
|
|
56
|
-
"@types/node": "^
|
|
57
|
-
"eslint": "^
|
|
54
|
+
"@types/node": "^26.0.1",
|
|
55
|
+
"eslint": "^10.5.0",
|
|
58
56
|
"jest": "^30.2.0",
|
|
59
57
|
"prettier": "^3.6.2",
|
|
60
58
|
"rimraf": "^6.0.1",
|
|
61
59
|
"ts-jest": "^29.4.4",
|
|
62
60
|
"ts-node": "^10.9.2",
|
|
63
|
-
"typescript": "^
|
|
61
|
+
"typescript": "^6.0.3",
|
|
62
|
+
"typescript-eslint": "^8.62.0"
|
|
64
63
|
},
|
|
65
64
|
"keywords": [
|
|
66
65
|
"ai",
|
|
@@ -93,5 +92,8 @@
|
|
|
93
92
|
"allowScripts": {
|
|
94
93
|
"fsevents@2.3.3": true,
|
|
95
94
|
"unrs-resolver@1.12.2": true
|
|
95
|
+
},
|
|
96
|
+
"overrides": {
|
|
97
|
+
"js-yaml": "^5.2.0"
|
|
96
98
|
}
|
|
97
99
|
}
|