npm-ai-hooks 1.0.0-beta.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/.env.example +14 -0
- package/CODE_OF_CONDUCT.md +35 -0
- package/CONTRIBUTING.md +88 -0
- package/EXAMPLES.md +53 -0
- package/LICENSE +15 -0
- package/ROADMAP.md +22 -0
- package/Readme.md +268 -0
- package/SECURITY.md +30 -0
- package/examples/basic/explain.ts +18 -0
- package/examples/basic/rewrite.ts +10 -0
- package/examples/basic/sentiment.ts +10 -0
- package/examples/basic/summarize.ts +10 -0
- package/examples/basic/translate.ts +10 -0
- package/examples/demo.ts +16 -0
- package/examples/model-switch/groq-default.ts +10 -0
- package/examples/model-switch/groq-text2.ts +10 -0
- package/examples/model-switch/openrouter-default.ts +10 -0
- package/examples/model-switch/openrouter-gpt5.ts +10 -0
- package/examples/model-switch/wrong-models.ts +21 -0
- package/examples/openrouter/summarize-switch-model-demo.ts +25 -0
- package/examples/openrouter/translate-switch-model-demo.ts +24 -0
- package/examples/openrouter/translate-to-urdu-demo.ts +19 -0
- package/examples/openrouter-openai/gpt5-demo.ts +25 -0
- package/jest.config.js +11 -0
- package/package.json +32 -0
- package/src/errors.ts +23 -0
- package/src/index.ts +2 -0
- package/src/providers/groq.ts +98 -0
- package/src/providers/index.ts +54 -0
- package/src/providers/openai.ts +19 -0
- package/src/providers/openrouter.ts +100 -0
- package/src/types/core/providers.ts +13 -0
- package/src/types/groq.ts +20 -0
- package/src/types/index.ts +37 -0
- package/src/types/openai.ts +3 -0
- package/src/types/openrouter.ts +52 -0
- package/src/wrap.ts +93 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import { wrap } from "../../src/wrap";
|
|
4
|
+
import { OpenRouterModel } from "../../src/types/openrouter";
|
|
5
|
+
|
|
6
|
+
const summarize = (model: OpenRouterModel) =>
|
|
7
|
+
wrap((text: string) => text, { task: "summarize", provider: "openrouter", model });
|
|
8
|
+
|
|
9
|
+
async function run() {
|
|
10
|
+
const text = "OpenRouter allows developers to switch AI models easily.";
|
|
11
|
+
|
|
12
|
+
const models: OpenRouterModel[] = [
|
|
13
|
+
"openai/gpt-oss-20b:free",
|
|
14
|
+
"openai/gpt-5-pro",
|
|
15
|
+
"openai/gpt-5-mini"
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const model of models) {
|
|
19
|
+
console.log(`\n--- Using model: ${model} ---`);
|
|
20
|
+
const result = await summarize(model)(text);
|
|
21
|
+
console.log(result.output);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
run();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import { wrap } from "../../src/wrap";
|
|
4
|
+
import { OpenRouterModel } from "../../src/types/openrouter";
|
|
5
|
+
|
|
6
|
+
const translate = (model: OpenRouterModel) =>
|
|
7
|
+
wrap((text: string) => text, { task: "translate", provider: "openrouter", model });
|
|
8
|
+
|
|
9
|
+
async function run() {
|
|
10
|
+
const text = "Bonjour tout le monde!";
|
|
11
|
+
|
|
12
|
+
const models: OpenRouterModel[] = [
|
|
13
|
+
"openai/gpt-4.1",
|
|
14
|
+
"openai/gpt-5-chat"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
for (const model of models) {
|
|
18
|
+
console.log(`\n--- Using model: ${model} ---`);
|
|
19
|
+
const result = await translate(model)(text);
|
|
20
|
+
console.log(result.output);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
run();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import { wrap } from "../../src/wrap";
|
|
4
|
+
|
|
5
|
+
// Wrap the text function for translation to Urdu
|
|
6
|
+
const translate = wrap((text: string) => text, {
|
|
7
|
+
task: "translate",
|
|
8
|
+
provider: "openrouter",
|
|
9
|
+
targetLanguage: "Roman Urdu" // specify target language
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
async function run() {
|
|
13
|
+
const text = "Hello everyone! Welcome to OpenRouter demos.";
|
|
14
|
+
|
|
15
|
+
const result = await translate(text);
|
|
16
|
+
console.log(result.output);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
run();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import { wrap } from "../../src/wrap";
|
|
4
|
+
import { OpenRouterModel } from "../../src/types/openrouter";
|
|
5
|
+
|
|
6
|
+
const summarize = (model: OpenRouterModel) =>
|
|
7
|
+
wrap((text: string) => text, { task: "summarize", provider: "openrouter", model });
|
|
8
|
+
|
|
9
|
+
async function run() {
|
|
10
|
+
const text = "This is a demonstration of switching OpenRouter models to GPT-5 variants.";
|
|
11
|
+
|
|
12
|
+
const models: OpenRouterModel[] = [
|
|
13
|
+
"openai/gpt-5",
|
|
14
|
+
"openai/gpt-5-pro",
|
|
15
|
+
"openai/gpt-5-mini"
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const model of models) {
|
|
19
|
+
console.log(`\n--- Using model: ${model} ---`);
|
|
20
|
+
const result = await summarize(model)(text);
|
|
21
|
+
console.log(result.output);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
run();
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "npm-ai-hooks",
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
|
+
"description": "**Universal AI Hook Layer for Node.js – one wrapper for all AI providers.**\r 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
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "ts-node src/index.ts",
|
|
8
|
+
"build:clean": "rimraf dist && tsc",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"test": "jest --verbose",
|
|
11
|
+
"prepare": "npm run build",
|
|
12
|
+
"demo": "npx ts-node examples/demo.ts"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"axios": "^1.12.2",
|
|
19
|
+
"dotenv": "^17.2.3"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/jest": "^30.0.0",
|
|
23
|
+
"@types/node": "^24.7.0",
|
|
24
|
+
"eslint": "^9.37.0",
|
|
25
|
+
"jest": "^30.2.0",
|
|
26
|
+
"prettier": "^3.6.2",
|
|
27
|
+
"rimraf": "^6.0.1",
|
|
28
|
+
"ts-jest": "^29.4.4",
|
|
29
|
+
"ts-node": "^10.9.2",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class AIHookError extends Error {
|
|
2
|
+
code: string;
|
|
3
|
+
provider?: string;
|
|
4
|
+
suggestion?: string;
|
|
5
|
+
|
|
6
|
+
constructor(code: string, message: string, provider?: string, suggestion?: string) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.provider = provider;
|
|
10
|
+
this.suggestion = suggestion;
|
|
11
|
+
// Do NOT print pretty message in constructor
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pretty() {
|
|
15
|
+
return `\n❌ AI-HOOK ERROR: ${this.message}` +
|
|
16
|
+
(this.provider ? `\n Provider: ${this.provider}` : "") +
|
|
17
|
+
(this.suggestion ? `\n Suggestion: ${this.suggestion}\n` : "");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
toString() {
|
|
21
|
+
return this.pretty();
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { AIHookError } from "../errors";
|
|
3
|
+
import { GroqModel } from "../types/groq";
|
|
4
|
+
|
|
5
|
+
const BASE_URL = "https://api.groq.com/openai/v1";
|
|
6
|
+
|
|
7
|
+
export async function callGroq(prompt: string, model: GroqModel): Promise<string> {
|
|
8
|
+
const apiKey = process.env.AI_HOOK_GROQ_KEY;
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
throw new AIHookError(
|
|
11
|
+
"INVALID_API_KEY",
|
|
12
|
+
"Missing Groq API key.",
|
|
13
|
+
"groq",
|
|
14
|
+
"Set AI_HOOK_GROQ_KEY in your environment variables."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const response = await axios.post(
|
|
20
|
+
`${BASE_URL}/chat/completions`,
|
|
21
|
+
{
|
|
22
|
+
model,
|
|
23
|
+
messages: [
|
|
24
|
+
{
|
|
25
|
+
role: "user",
|
|
26
|
+
content: prompt
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
headers: {
|
|
32
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const output = response.data?.choices?.[0]?.message?.content;
|
|
39
|
+
if (!output) {
|
|
40
|
+
throw new AIHookError(
|
|
41
|
+
"PROVIDER_ERROR",
|
|
42
|
+
"Groq returned empty response",
|
|
43
|
+
"groq",
|
|
44
|
+
"Check your model and API key"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return output;
|
|
49
|
+
} catch (err: any) {
|
|
50
|
+
if (err.response) {
|
|
51
|
+
const status = err.response.status;
|
|
52
|
+
const text = err.response.data?.error
|
|
53
|
+
? JSON.stringify(err.response.data.error)
|
|
54
|
+
: err.response.statusText || "Unknown error";
|
|
55
|
+
|
|
56
|
+
if (status === 400)
|
|
57
|
+
throw new AIHookError(
|
|
58
|
+
"BAD_REQUEST",
|
|
59
|
+
`Groq rejected the request: ${text}`,
|
|
60
|
+
"groq",
|
|
61
|
+
"Check your prompt and model"
|
|
62
|
+
);
|
|
63
|
+
if (status === 401)
|
|
64
|
+
throw new AIHookError(
|
|
65
|
+
"INVALID_API_KEY",
|
|
66
|
+
`Invalid Groq API key: ${text}`,
|
|
67
|
+
"groq",
|
|
68
|
+
"Verify your AI_HOOK_GROQ_KEY environment variable"
|
|
69
|
+
);
|
|
70
|
+
if (status === 429)
|
|
71
|
+
throw new AIHookError(
|
|
72
|
+
"RATE_LIMIT",
|
|
73
|
+
`Too many requests to Groq: ${text}`,
|
|
74
|
+
"groq",
|
|
75
|
+
"Throttle requests or upgrade your plan"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
throw new AIHookError(
|
|
79
|
+
"PROVIDER_ERROR",
|
|
80
|
+
`Groq API error: ${text}`,
|
|
81
|
+
"groq"
|
|
82
|
+
);
|
|
83
|
+
} else if (err.request) {
|
|
84
|
+
throw new AIHookError(
|
|
85
|
+
"NETWORK_ERROR",
|
|
86
|
+
"Network error while contacting Groq",
|
|
87
|
+
"groq",
|
|
88
|
+
"Check your internet connection"
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
throw new AIHookError(
|
|
92
|
+
"UNKNOWN_ERROR",
|
|
93
|
+
err.message,
|
|
94
|
+
"groq"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AIHookError } from "../errors";
|
|
2
|
+
import { callOpenAI } from "./openai";
|
|
3
|
+
import { callOpenRouter } from "./openrouter";
|
|
4
|
+
import { callGroq } from "./groq";
|
|
5
|
+
import { Provider } from "../types";
|
|
6
|
+
import { ProviderFunction, ProviderMap } from "../types/core/providers";
|
|
7
|
+
|
|
8
|
+
const providers: ProviderMap = {
|
|
9
|
+
openai: callOpenAI,
|
|
10
|
+
openrouter: callOpenRouter,
|
|
11
|
+
groq: callGroq,
|
|
12
|
+
mock: async (prompt: string, model?: string) => `[MOCK OUTPUT] ${prompt}`
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Returns an array of providers whose API keys exist in environment
|
|
16
|
+
export function getAvailableProviders(): Provider[] {
|
|
17
|
+
const available: Provider[] = [];
|
|
18
|
+
// Always prefer openrouter if present
|
|
19
|
+
if (process.env.AI_HOOK_OPENROUTER_KEY) {
|
|
20
|
+
available.push("openrouter");
|
|
21
|
+
}
|
|
22
|
+
// Add others in order of their presence
|
|
23
|
+
if (process.env.AI_HOOK_GROQ_KEY && !available.includes("groq")) {
|
|
24
|
+
available.push("groq");
|
|
25
|
+
}
|
|
26
|
+
if (process.env.AI_HOOK_OPENAI_KEY && !available.includes("openai")) {
|
|
27
|
+
available.push("openai");
|
|
28
|
+
}
|
|
29
|
+
return available;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ✅ Returns both the provider function and the actual provider name
|
|
33
|
+
export function getProvider(name?: Provider): { fn: ProviderFunction<any>; provider: Provider | "mock" } {
|
|
34
|
+
const available = getAvailableProviders();
|
|
35
|
+
|
|
36
|
+
// 1. If user specified provider and it's available
|
|
37
|
+
if (name && providers[name]) {
|
|
38
|
+
return { fn: providers[name], provider: name };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. If at least one provider is available, pick the first one (openrouter always preferred if present)
|
|
42
|
+
if (available.length > 0) {
|
|
43
|
+
console.log(`[ai-hooks] ✅ Auto-selected provider: ${available[0]}`);
|
|
44
|
+
return { fn: providers[available[0]], provider: available[0] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. No valid keys found → throw error (single instruction, no fallback)
|
|
48
|
+
throw new AIHookError(
|
|
49
|
+
"NO_PROVIDER_FOUND",
|
|
50
|
+
"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",
|
|
51
|
+
undefined,
|
|
52
|
+
"Reference .env.example for setup instructions."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
export async function callOpenAI(prompt: string, model: string) {
|
|
4
|
+
const key = process.env.AI_HOOK_OPENAI_KEY;
|
|
5
|
+
if (!key) throw new Error("Missing AI_HOOK_OPENAI_KEY");
|
|
6
|
+
|
|
7
|
+
const response = await axios.post(
|
|
8
|
+
"https://api.openai.com/v1/chat/completions",
|
|
9
|
+
{
|
|
10
|
+
model,
|
|
11
|
+
messages: [{ role: "user", content: prompt }]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return response.data.choices[0].message.content;
|
|
19
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { AIHookError } from "../errors";
|
|
3
|
+
|
|
4
|
+
const BASE_URL = "https://openrouter.ai/api/v1";
|
|
5
|
+
|
|
6
|
+
export async function callOpenRouter(prompt: string, model: string): Promise<string> {
|
|
7
|
+
const apiKey = process.env.AI_HOOK_OPENROUTER_KEY;
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new AIHookError(
|
|
10
|
+
"INVALID_API_KEY",
|
|
11
|
+
"Missing OpenRouter API key.",
|
|
12
|
+
"openrouter",
|
|
13
|
+
"Set AI_HOOK_OPENROUTER_KEY in your environment variables."
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const response = await axios.post(
|
|
19
|
+
`${BASE_URL}/chat/completions`,
|
|
20
|
+
{
|
|
21
|
+
model,
|
|
22
|
+
messages: [{ role: "user", content: prompt }],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${apiKey}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const output = response.data?.choices?.[0]?.message?.content;
|
|
33
|
+
if (!output) {
|
|
34
|
+
throw new AIHookError(
|
|
35
|
+
"PROVIDER_ERROR",
|
|
36
|
+
"OpenRouter returned empty response.",
|
|
37
|
+
"openrouter",
|
|
38
|
+
"Check that the model name is correct and your API key has access to it."
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return output;
|
|
43
|
+
} catch (err: any) {
|
|
44
|
+
if (err.response) {
|
|
45
|
+
const status = err.response.status;
|
|
46
|
+
// Make sure we get a string message
|
|
47
|
+
const text = err.response.data?.error
|
|
48
|
+
? JSON.stringify(err.response.data.error)
|
|
49
|
+
: err.response.statusText || "Unknown error";
|
|
50
|
+
|
|
51
|
+
if (status === 400) throw new AIHookError(
|
|
52
|
+
"BAD_REQUEST",
|
|
53
|
+
`OpenRouter rejected the request: ${text}`,
|
|
54
|
+
"openrouter",
|
|
55
|
+
"Check your prompt, model name, and payload format."
|
|
56
|
+
);
|
|
57
|
+
if (status === 401) throw new AIHookError(
|
|
58
|
+
"INVALID_API_KEY",
|
|
59
|
+
`Invalid OpenRouter API key: ${text}`,
|
|
60
|
+
"openrouter",
|
|
61
|
+
"Verify your AI_HOOK_OPENROUTER_KEY environment variable."
|
|
62
|
+
);
|
|
63
|
+
if (status === 403) throw new AIHookError(
|
|
64
|
+
"MODEL_NOT_ALLOWED",
|
|
65
|
+
`Your API key cannot access this model: ${text}`,
|
|
66
|
+
"openrouter",
|
|
67
|
+
"Try a different model or check API key permissions."
|
|
68
|
+
);
|
|
69
|
+
if (status === 429) throw new AIHookError(
|
|
70
|
+
"RATE_LIMIT",
|
|
71
|
+
`Too many requests to OpenRouter: ${text}`,
|
|
72
|
+
"openrouter",
|
|
73
|
+
"Consider throttling requests or upgrading your plan."
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
throw new AIHookError(
|
|
77
|
+
"PROVIDER_ERROR",
|
|
78
|
+
`OpenRouter API error: ${text}`,
|
|
79
|
+
"openrouter",
|
|
80
|
+
"Check your model, prompt, and API key."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
else if (err.request) {
|
|
85
|
+
throw new AIHookError(
|
|
86
|
+
"NETWORK_ERROR",
|
|
87
|
+
"Network error while contacting OpenRouter.",
|
|
88
|
+
"openrouter",
|
|
89
|
+
"Check your internet connection."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new AIHookError(
|
|
94
|
+
"UNKNOWN_ERROR",
|
|
95
|
+
err.message,
|
|
96
|
+
"openrouter"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Provider, ProviderModels } from "../index";
|
|
2
|
+
|
|
3
|
+
export interface ProviderFunction<P extends Provider = Provider> {
|
|
4
|
+
(prompt: string, model: ProviderModels[P]): Promise<string>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ProviderMap {
|
|
8
|
+
openai: ProviderFunction<"openai">;
|
|
9
|
+
openrouter: ProviderFunction<"openrouter">;
|
|
10
|
+
groq: ProviderFunction<"groq">;
|
|
11
|
+
mock: ProviderFunction<any>; // generic fallback for mock
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type GroqModel =
|
|
2
|
+
| "allam-2-7b"
|
|
3
|
+
| "deepseek-r1-distill-llama-70b"
|
|
4
|
+
| "gemma2-9b-it"
|
|
5
|
+
| "groq/compound"
|
|
6
|
+
| "groq/compound-mini"
|
|
7
|
+
| "llama-3.1-8b-instant"
|
|
8
|
+
| "llama-3.3-70b-versatile"
|
|
9
|
+
| "meta-llama/llama-4-maverick-17b-128e-instruct"
|
|
10
|
+
| "meta-llama/llama-4-scout-17b-16e-instruct"
|
|
11
|
+
| "meta-llama/llama-guard-4-12b"
|
|
12
|
+
| "meta-llama/llama-prompt-guard-2-22m"
|
|
13
|
+
| "meta-llama/llama-prompt-guard-2-86m"
|
|
14
|
+
| "moonshotai/kimi-k2-instruct"
|
|
15
|
+
| "moonshotai/kimi-k2-instruct-0905"
|
|
16
|
+
| "openai/gpt-oss-120b"
|
|
17
|
+
| "openai/gpt-oss-20b"
|
|
18
|
+
| "qwen/qwen3-32b";
|
|
19
|
+
|
|
20
|
+
export const GroqDefaultModel: GroqModel = "groq/compound";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { OpenAIModel, OpenAIDefaultModel } from "./openai";
|
|
2
|
+
import { OpenRouterModel, OpenRouterDefaultModel } from "./openrouter";
|
|
3
|
+
import { GroqModel, GroqDefaultModel } from "./groq";
|
|
4
|
+
|
|
5
|
+
export type Provider = "openai" | "openrouter" | "groq";
|
|
6
|
+
|
|
7
|
+
export type ProviderModels = {
|
|
8
|
+
openai: OpenAIModel;
|
|
9
|
+
openrouter: OpenRouterModel;
|
|
10
|
+
groq: GroqModel;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_MODEL: { [P in Provider]: ProviderModels[P] } = {
|
|
14
|
+
openai: OpenAIDefaultModel,
|
|
15
|
+
openrouter: OpenRouterDefaultModel,
|
|
16
|
+
groq: GroqDefaultModel,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Task types
|
|
20
|
+
export type TaskType =
|
|
21
|
+
| "summarize"
|
|
22
|
+
| "translate"
|
|
23
|
+
| "explain"
|
|
24
|
+
| "rewrite"
|
|
25
|
+
| "sentiment"
|
|
26
|
+
| "codeReview";
|
|
27
|
+
|
|
28
|
+
// Wrap options
|
|
29
|
+
export type WrapOptions<P extends Provider | undefined = undefined> =
|
|
30
|
+
| {
|
|
31
|
+
provider: P extends Provider ? P : never;
|
|
32
|
+
model?: P extends Provider ? ProviderModels[P] : never;
|
|
33
|
+
task: TaskType;
|
|
34
|
+
targetLanguage?: string; // new optional
|
|
35
|
+
}
|
|
36
|
+
| { provider?: never; model?: never; task: TaskType; targetLanguage?: string };
|
|
37
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type OpenRouterModel = "openai/gpt-oss-20b:free"
|
|
2
|
+
| "openai/gpt-4.1"
|
|
3
|
+
| "openai/gpt-5-pro"
|
|
4
|
+
| "openai/gpt-5-codex"
|
|
5
|
+
| "openai/gpt-5-chat"
|
|
6
|
+
| "openai/gpt-5"
|
|
7
|
+
| "openai/gpt-5-mini"
|
|
8
|
+
| "openai/gpt-5-nano"
|
|
9
|
+
| "openai/gpt-oss-120b"
|
|
10
|
+
| "openai/gpt-oss-20b"
|
|
11
|
+
| "openai/o3-pro"
|
|
12
|
+
| "openai/codex-mini"
|
|
13
|
+
| "openai/o3"
|
|
14
|
+
| "openai/o4-mini"
|
|
15
|
+
| "openai/gpt-4.1-mini"
|
|
16
|
+
| "openai/gpt-4.1-nano"
|
|
17
|
+
| "openai/o1-pro"
|
|
18
|
+
| "openai/gpt-4o-mini-search-preview"
|
|
19
|
+
| "openai/gpt-4o-search-preview"
|
|
20
|
+
| "openai/o3-mini-high"
|
|
21
|
+
| "openai/o3-mini"
|
|
22
|
+
| "openai/o1"
|
|
23
|
+
| "openai/gpt-4o-2024-11-20"
|
|
24
|
+
| "openai/o1-mini"
|
|
25
|
+
| "openai/o1-mini-2024-09-12"
|
|
26
|
+
| "openai/chatgpt-4o-latest"
|
|
27
|
+
| "openai/gpt-4o-2024-08-06"
|
|
28
|
+
| "openai/gpt-4o-mini"
|
|
29
|
+
| "openai/gpt-4o-mini-2024-07-18"
|
|
30
|
+
| "openai/gpt-4o"
|
|
31
|
+
| "openai/gpt-4o:extended"
|
|
32
|
+
| "openai/gpt-4o-2024-05-13"
|
|
33
|
+
| "openai/gpt-4-turbo"
|
|
34
|
+
| "openai/gpt-3.5-turbo-0613"
|
|
35
|
+
| "openai/gpt-4-turbo-preview"
|
|
36
|
+
| "openai/gpt-4-1106-preview"
|
|
37
|
+
| "openai/gpt-3.5-turbo-instruct"
|
|
38
|
+
| "openai/gpt-3.5-turbo-16k"
|
|
39
|
+
| "openai/gpt-3.5-turbo"
|
|
40
|
+
| "openai/gpt-4"
|
|
41
|
+
| "openai/gpt-4-0314"
|
|
42
|
+
| "openai/gpt-4.5-preview"
|
|
43
|
+
| "openai/o1-preview"
|
|
44
|
+
| "openai/o1-preview-2024-09-12"
|
|
45
|
+
| "openai/gpt-4-vision-preview"
|
|
46
|
+
| "openai/gpt-3.5-turbo-1106"
|
|
47
|
+
| "openai/gpt-4-32k"
|
|
48
|
+
| "openai/gpt-4-32k-0314"
|
|
49
|
+
| "openai/gpt-3.5-turbo-0301"
|
|
50
|
+
| "openai/gpt-3.5-turbo-0125" ;
|
|
51
|
+
|
|
52
|
+
export const OpenRouterDefaultModel: OpenRouterModel = "openai/gpt-oss-20b:free" ;
|
package/src/wrap.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import { getProvider } from "./providers";
|
|
4
|
+
import { WrapOptions, TaskType, Provider, DEFAULT_MODEL } from "./types";
|
|
5
|
+
|
|
6
|
+
function handleError(err: unknown): never {
|
|
7
|
+
if (err && typeof err === "object" && "pretty" in err && typeof (err as any).pretty === "function") {
|
|
8
|
+
// Print pretty message and exit
|
|
9
|
+
console.error((err as any).pretty());
|
|
10
|
+
process.exit(1);
|
|
11
|
+
} else {
|
|
12
|
+
console.error(err);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
throw new Error("Process exited due to AIHookError"); // for TS never
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function wrap<T extends (...args: any[]) => any, P extends Provider | undefined = undefined>(
|
|
19
|
+
fn: T,
|
|
20
|
+
options: WrapOptions<P>
|
|
21
|
+
): (...args: Parameters<T>) => Promise<{ output: string; meta: any }> {
|
|
22
|
+
return async (...args: Parameters<T>) => {
|
|
23
|
+
try {
|
|
24
|
+
const input = fn(...args);
|
|
25
|
+
|
|
26
|
+
// Step 1: get provider function and the actual provider name
|
|
27
|
+
const { fn: providerFn, provider: providerKey } = getProvider(options.provider as Provider | undefined);
|
|
28
|
+
|
|
29
|
+
// Step 2: pick model: passed model or provider-specific default
|
|
30
|
+
const model = options.model || (providerKey in DEFAULT_MODEL ? DEFAULT_MODEL[providerKey as Provider] : undefined);
|
|
31
|
+
|
|
32
|
+
if (!model) {
|
|
33
|
+
throw new (require('./errors').AIHookError)(
|
|
34
|
+
"NO_MODEL_FOUND",
|
|
35
|
+
"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",
|
|
36
|
+
options.provider as Provider | undefined,
|
|
37
|
+
"Reference .env.example for setup instructions."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Step 3: build prompt
|
|
42
|
+
const prompt = buildPrompt(options.task, input, (options as any).targetLanguage);
|
|
43
|
+
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
let output: string;
|
|
46
|
+
try {
|
|
47
|
+
output = await providerFn(prompt, model);
|
|
48
|
+
} catch (err: unknown) {
|
|
49
|
+
if (err instanceof require('./errors').AIHookError) {
|
|
50
|
+
handleError(err);
|
|
51
|
+
}
|
|
52
|
+
if (err instanceof Error) {
|
|
53
|
+
throw new Error(`[ai-hooks] Unknown error calling provider: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`[ai-hooks] Unknown non-error thrown by provider: ${String(err)}`);
|
|
56
|
+
}
|
|
57
|
+
const endTime = Date.now();
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
output,
|
|
61
|
+
meta: {
|
|
62
|
+
provider: providerKey,
|
|
63
|
+
model,
|
|
64
|
+
cached: false,
|
|
65
|
+
estimatedCostUSD: 0.0,
|
|
66
|
+
latencyMs: endTime - startTime
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
} catch (err) {
|
|
70
|
+
handleError(err);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildPrompt(task: TaskType, text: string, targetLanguage?: string) {
|
|
76
|
+
switch (task) {
|
|
77
|
+
case "summarize":
|
|
78
|
+
return `Summarize the following text:\n${text}`;
|
|
79
|
+
case "translate":
|
|
80
|
+
const language = targetLanguage || "English"; // default to English
|
|
81
|
+
return `Translate this text into ${language}:\n${text}`;
|
|
82
|
+
case "explain":
|
|
83
|
+
return `Explain this clearly:\n${text}`;
|
|
84
|
+
case "rewrite":
|
|
85
|
+
return `Rewrite this text with better clarity:\n${text}`;
|
|
86
|
+
case "sentiment":
|
|
87
|
+
return `Analyze the sentiment of this text:\n${text}`;
|
|
88
|
+
case "codeReview":
|
|
89
|
+
return `Review this code and suggest improvements:\n${text}`;
|
|
90
|
+
default:
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
}
|