npm-ai-hooks 1.0.0 → 1.0.1
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 +30 -0
- package/CONTRIBUTING.md +88 -0
- package/EXAMPLES.md +53 -0
- package/ROADMAP.md +22 -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 +5 -16
- package/src/errors.ts +23 -0
- package/{dist/index.d.ts → src/index.ts} +2 -1
- package/src/providers/claude.ts +101 -0
- package/src/providers/deepkseek.ts +98 -0
- package/src/providers/gemini.ts +99 -0
- package/src/providers/groq.ts +98 -0
- package/src/providers/index.ts +84 -0
- package/src/providers/mistral.ts +98 -0
- package/src/providers/openai.ts +98 -0
- package/src/providers/openrouter.ts +100 -0
- package/src/providers/perplexity.ts +98 -0
- package/src/providers/xai.ts +98 -0
- package/src/types/claude.ts +25 -0
- package/src/types/core/providers.ts +18 -0
- package/src/types/deepseek.ts +11 -0
- package/src/types/gemini.ts +30 -0
- package/src/types/groq.ts +20 -0
- package/src/types/index.ts +55 -0
- package/src/types/mistral.ts +48 -0
- package/src/types/openai.ts +46 -0
- package/src/types/openrouter.ts +52 -0
- package/src/types/perplexity.ts +16 -0
- package/src/types/xai.ts +19 -0
- package/src/wrap.ts +93 -0
- package/tsconfig.json +11 -0
- package/dist/errors.d.ts +0 -8
- package/dist/errors.js +0 -21
- package/dist/index.js +0 -6
- package/dist/providers/groq.d.ts +0 -2
- package/dist/providers/groq.js +0 -57
- package/dist/providers/index.d.ts +0 -7
- package/dist/providers/index.js +0 -45
- package/dist/providers/openai.d.ts +0 -1
- package/dist/providers/openai.js +0 -19
- package/dist/providers/openrouter.d.ts +0 -1
- package/dist/providers/openrouter.js +0 -55
- package/dist/types/core/providers.d.ts +0 -10
- package/dist/types/core/providers.js +0 -2
- package/dist/types/groq.d.ts +0 -2
- package/dist/types/groq.js +0 -4
- package/dist/types/index.d.ts +0 -24
- package/dist/types/index.js +0 -11
- package/dist/types/openai.d.ts +0 -2
- package/dist/types/openai.js +0 -4
- package/dist/types/openrouter.d.ts +0 -2
- package/dist/types/openrouter.js +0 -4
- package/dist/wrap.d.ts +0 -5
- package/dist/wrap.js +0 -85
package/.env.example
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# OpenAI API key (for OpenRouter OpenAI models or standalone OpenAI)
|
|
2
|
+
AI_HOOK_OPENAI_KEY=your_openai_api_key_here
|
|
3
|
+
|
|
4
|
+
# OpenRouter API key
|
|
5
|
+
AI_HOOK_OPENROUTER_KEY=your_openrouter_api_key_here
|
|
6
|
+
|
|
7
|
+
# Groq API key
|
|
8
|
+
AI_HOOK_GROQ_KEY=your_groq_api_key_here
|
|
9
|
+
|
|
10
|
+
# Default provider (optional)
|
|
11
|
+
# AI_HOOK_DEFAULT_PROVIDER=openrouter
|
|
12
|
+
|
|
13
|
+
# Default model is handled by code, so no need to set it here
|
|
14
|
+
# AI_HOOK_DEFAULT_MODEL=
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
```md
|
|
2
|
+
# Contributor Covenant Code of Conduct
|
|
3
|
+
|
|
4
|
+
## Our Pledge
|
|
5
|
+
|
|
6
|
+
We as members, contributors, and maintainers pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
|
7
|
+
|
|
8
|
+
## Our Standards
|
|
9
|
+
|
|
10
|
+
Examples of behavior that contributes to a positive environment include:
|
|
11
|
+
|
|
12
|
+
- Using welcoming and inclusive language
|
|
13
|
+
- Being respectful of differing viewpoints and experiences
|
|
14
|
+
- Gracefully accepting constructive criticism
|
|
15
|
+
- Focusing on what is best for the community
|
|
16
|
+
- Showing empathy towards other community members
|
|
17
|
+
|
|
18
|
+
Examples of unacceptable behavior include:
|
|
19
|
+
|
|
20
|
+
- Harassment of public or private forms
|
|
21
|
+
- Trolling, insulting/derogatory comments
|
|
22
|
+
- Publishing others’ private information without permission
|
|
23
|
+
- Other conduct which could reasonably be considered inappropriate
|
|
24
|
+
|
|
25
|
+
## Enforcement
|
|
26
|
+
|
|
27
|
+
Instances of abusive behavior may be reported to the maintainers via [issues](https://github.com/RealTeebot/npm-ai-hooks/issues).
|
|
28
|
+
All complaints will be reviewed and addressed appropriately.
|
|
29
|
+
|
|
30
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/).
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Contributing to npm-ai-hooks
|
|
2
|
+
|
|
3
|
+
👋 Thanks for your interest in contributing to **npm-ai-hooks**!
|
|
4
|
+
This project is maintained by the [RealTeebot](https://github.com/RealTeebot) organization and welcomes community involvement at all levels — from bug fixes and documentation to major feature development.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🛠️ Development Setup
|
|
9
|
+
|
|
10
|
+
1. **Fork** the repository and clone your fork:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
git clone https://github.com/your-username/npm-ai-hooks.git
|
|
14
|
+
cd npm-ai-hooks
|
|
15
|
+
|
|
16
|
+
npm install
|
|
17
|
+
|
|
18
|
+
npm run dev
|
|
19
|
+
|
|
20
|
+
npm run test
|
|
21
|
+
|
|
22
|
+
npm-ai-hooks/
|
|
23
|
+
├─ src/
|
|
24
|
+
│ ├─ index.ts # Main entry
|
|
25
|
+
│ ├─ wrap.ts # Core hook wrapper logic
|
|
26
|
+
│ ├─ cache.ts # Caching engine
|
|
27
|
+
│ ├─ cost.ts # Cost estimation utilities
|
|
28
|
+
│ ├─ errors.ts # Unified error handling
|
|
29
|
+
│ ├─ providers/ # Provider adapters
|
|
30
|
+
│ │ ├─ openai.ts
|
|
31
|
+
│ │ ├─ claude.ts
|
|
32
|
+
│ │ └─ ...
|
|
33
|
+
├─ examples/
|
|
34
|
+
│ ├─ node-basic/
|
|
35
|
+
│ └─ react-basic/
|
|
36
|
+
├─ tests/
|
|
37
|
+
└─ ...
|
|
38
|
+
|
|
39
|
+
✨ Contributing Guidelines
|
|
40
|
+
|
|
41
|
+
TypeScript Only: All code must be written in TypeScript with strict typing enabled.
|
|
42
|
+
|
|
43
|
+
Linting: Run npm run lint before committing.
|
|
44
|
+
|
|
45
|
+
Formatting: Code must pass Prettier formatting checks.
|
|
46
|
+
|
|
47
|
+
Commit Style: Use Conventional Commits
|
|
48
|
+
(e.g., feat: add caching to DeepSeek provider).
|
|
49
|
+
|
|
50
|
+
Tests: Add unit/integration tests for any new features or bug fixes.
|
|
51
|
+
|
|
52
|
+
📦 Adding a New Provider
|
|
53
|
+
|
|
54
|
+
Create a new file in src/providers/ (e.g., mistral.ts)
|
|
55
|
+
|
|
56
|
+
Implement the required interface:
|
|
57
|
+
|
|
58
|
+
export const mistralProvider: Provider = {
|
|
59
|
+
name: "mistral",
|
|
60
|
+
isAvailable: () => !!process.env.AI_HOOK_MISTRAL_KEY,
|
|
61
|
+
generate: async (prompt, options) => { /* API call logic */ },
|
|
62
|
+
models: ["mistral-medium", "mistral-large"]
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
Register the provider in providers/index.ts
|
|
67
|
+
|
|
68
|
+
Add documentation and tests.
|
|
69
|
+
|
|
70
|
+
🧪 Testing
|
|
71
|
+
|
|
72
|
+
We use Jest for unit testing and Playwright for integration tests where relevant.
|
|
73
|
+
|
|
74
|
+
npm run test
|
|
75
|
+
npm run test:watch
|
|
76
|
+
|
|
77
|
+
📣 Communication
|
|
78
|
+
|
|
79
|
+
Issues: GitHub Issues
|
|
80
|
+
|
|
81
|
+
Discussions: GitHub Discussions
|
|
82
|
+
|
|
83
|
+
Pull Requests: Always create a PR to the main branch
|
|
84
|
+
|
|
85
|
+
🙏 Credits
|
|
86
|
+
|
|
87
|
+
Maintained with ❤️ by RealTeebot
|
|
88
|
+
and contributors.
|
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
7. EXAMPLES.md
|
|
2
|
+
# Usage Examples – npm-ai-hooks
|
|
3
|
+
|
|
4
|
+
## 🧠 In a React Frontend
|
|
5
|
+
|
|
6
|
+
Install:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install npm-ai-hooks
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
|
|
14
|
+
import { wrap } from "npm-ai-hooks";
|
|
15
|
+
|
|
16
|
+
const summarize = wrap((text: string) => text, { task: "summarize" });
|
|
17
|
+
|
|
18
|
+
export function App() {
|
|
19
|
+
const [result, setResult] = useState("");
|
|
20
|
+
|
|
21
|
+
const handleClick = async () => {
|
|
22
|
+
const res = await summarize("Explain server components in React.");
|
|
23
|
+
setResult(res.output);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div>
|
|
28
|
+
<button onClick={handleClick}>Summarize</button>
|
|
29
|
+
<p>{result}</p>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
⚙️ In a Node.js Backend
|
|
35
|
+
import { wrap } from "npm-ai-hooks";
|
|
36
|
+
|
|
37
|
+
const explain = wrap((input: string) => input, { task: "explain" });
|
|
38
|
+
|
|
39
|
+
app.post("/explain", async (req, res) => {
|
|
40
|
+
const result = await explain(req.body.text);
|
|
41
|
+
res.json(result);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
This works with Express, NestJS, Fastify, Next.js API routes — anything Node-compatible.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
✅ **With these files in place**, your repository will be structured like a professional, enterprise-ready open-source project:
|
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Roadmap – npm-ai-hooks
|
|
2
|
+
|
|
3
|
+
✅ v0.1.0 (MVP)
|
|
4
|
+
- [x] Basic `wrap()` functionality
|
|
5
|
+
- [x] OpenAI + Claude support
|
|
6
|
+
- [x] Error handling and cost estimation
|
|
7
|
+
- [x] Caching layer
|
|
8
|
+
|
|
9
|
+
🚀 v0.2.0
|
|
10
|
+
- [ ] Provider auto-discovery CLI
|
|
11
|
+
- [ ] More built-in tasks (e.g., classify, code explain)
|
|
12
|
+
- [ ] React hook wrapper (`useAI`)
|
|
13
|
+
|
|
14
|
+
💡 v0.3.0
|
|
15
|
+
- [ ] Streaming support
|
|
16
|
+
- [ ] Local model support (Ollama / Llama.cpp)
|
|
17
|
+
- [ ] SDK for Python (bridge)
|
|
18
|
+
|
|
19
|
+
🧠 Long-Term Vision
|
|
20
|
+
- Unified dashboard for usage + cost
|
|
21
|
+
- Plugin ecosystem (community providers, custom tasks)
|
|
22
|
+
- Serverless deployment starter template
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
We actively support the latest major release and security patches for the previous one.
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
|--------|-----------|
|
|
9
|
+
| 1.x.x | ✅ |
|
|
10
|
+
| 0.x.x | ❌ |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Reporting a Vulnerability
|
|
15
|
+
|
|
16
|
+
If you discover a security vulnerability, **DO NOT** open a public GitHub issue.
|
|
17
|
+
|
|
18
|
+
Instead, please report it privately by emailing:
|
|
19
|
+
**security@realteebot.dev**
|
|
20
|
+
|
|
21
|
+
We take all security issues seriously and will respond within 48 hours.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Disclosure Process
|
|
26
|
+
|
|
27
|
+
1. We confirm and investigate the vulnerability.
|
|
28
|
+
2. We create a patch and test the fix.
|
|
29
|
+
3. We coordinate a release and notify users.
|
|
30
|
+
4. We credit the reporter (if they wish).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const explain = wrap((text: string) => text, { task: "explain" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
try {
|
|
7
|
+
const result = await explain("Quantum computing is complex.");
|
|
8
|
+
console.log(result.output);
|
|
9
|
+
} catch (err: any) {
|
|
10
|
+
if (err && typeof err.pretty === "function") {
|
|
11
|
+
err.pretty();
|
|
12
|
+
} else {
|
|
13
|
+
console.error(err.message || err);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
run();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const sentiment = wrap((text: string) => text, { task: "sentiment" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const result = await sentiment("I love using AI-Hooks for my projects!");
|
|
7
|
+
console.log(result.output);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
run();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const summarize = wrap((text: string) => text, { task: "summarize" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const result = await summarize("OpenRouter is a powerful AI integration tool.");
|
|
7
|
+
console.log(result.output);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
run();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const translate = wrap((text: string) => text, { task: "translate", targetLanguage: "Urdu" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const result = await translate("Hello everyone! Welcome to AI demos.");
|
|
7
|
+
console.log(result.output);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
run();
|
package/examples/demo.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { wrap } from "../src";
|
|
2
|
+
|
|
3
|
+
const summarize = wrap((text: string) => text, { task: "summarize" });
|
|
4
|
+
|
|
5
|
+
(async () => {
|
|
6
|
+
try {
|
|
7
|
+
const result = await summarize("Gemini is made by..");
|
|
8
|
+
console.log(result.output);
|
|
9
|
+
} catch (err: any) {
|
|
10
|
+
if (err.pretty) {
|
|
11
|
+
err.pretty(); // prints nice error with suggestions
|
|
12
|
+
} else {
|
|
13
|
+
console.error(err);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const explain = wrap((text: string) => text, { task: "explain", provider: "groq" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const result = await explain("Groq provides fast text AI models.");
|
|
7
|
+
console.log(result.output);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
run();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const explain = wrap((text: string) => text, { task: "explain", provider: "groq", model: "allam-2-7b" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const result = await explain("Groq's text-2 model is versatile for many tasks.");
|
|
7
|
+
console.log(result.output);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
run();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const summarize = wrap((text: string) => text, { task: "summarize", provider: "openrouter" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const result = await summarize("OpenRouter makes AI integration seamless.");
|
|
7
|
+
console.log(result.output);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
run();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
const summarize = wrap((text: string) => text, { task: "summarize", provider: "openrouter", model: "openai/gpt-5-pro" });
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const result = await summarize("OpenRouter supports multiple models including GPT-5.");
|
|
7
|
+
console.log(result.output);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
run();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { wrap } from "../../src/wrap";
|
|
2
|
+
|
|
3
|
+
async function run() {
|
|
4
|
+
// Wrong OpenRouter model
|
|
5
|
+
try {
|
|
6
|
+
const wrongOpenRouter = wrap((text: string) => text, { task: "summarize", provider: "openrouter", model: "invalid-model" as any });
|
|
7
|
+
await wrongOpenRouter("Test text");
|
|
8
|
+
} catch (err: unknown) {
|
|
9
|
+
console.error("❌ OpenRouter error:", err);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Wrong Groq model
|
|
13
|
+
try {
|
|
14
|
+
const wrongGroq = wrap((text: string) => text, { task: "explain", provider: "groq", model: "invalid-groq-model" as any });
|
|
15
|
+
await wrongGroq("Test text");
|
|
16
|
+
} catch (err: unknown) {
|
|
17
|
+
console.error("❌ Groq error:", err);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
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 = "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
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "npm-ai-hooks",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Universal AI Hook Layer for Node.js – one wrapper for all AI providers.",
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"import": "./dist/index.js",
|
|
11
|
-
"require": "./dist/index.js"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist/**/*"
|
|
16
|
-
],
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|
|
17
6
|
"scripts": {
|
|
18
7
|
"dev": "ts-node src/index.ts",
|
|
19
8
|
"build:clean": "rimraf dist && tsc",
|
|
@@ -23,7 +12,7 @@
|
|
|
23
12
|
"demo": "npx ts-node examples/demo.ts"
|
|
24
13
|
},
|
|
25
14
|
"keywords": [],
|
|
26
|
-
"author": "
|
|
15
|
+
"author": "",
|
|
27
16
|
"license": "ISC",
|
|
28
17
|
"dependencies": {
|
|
29
18
|
"axios": "^1.12.2",
|
|
@@ -40,4 +29,4 @@
|
|
|
40
29
|
"ts-node": "^10.9.2",
|
|
41
30
|
"typescript": "^5.9.3"
|
|
42
31
|
}
|
|
43
|
-
}
|
|
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
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
export { wrap } from "./wrap";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { AIHookError } from "../errors";
|
|
3
|
+
import { ClaudeModel } from "../types/claude";
|
|
4
|
+
|
|
5
|
+
const BASE_URL = "https://api.anthropic.com/v1";
|
|
6
|
+
const ANTHROPIC_VERSION = "2023-06-01";
|
|
7
|
+
|
|
8
|
+
export async function callClaude(prompt: string, model: ClaudeModel): Promise<string> {
|
|
9
|
+
const apiKey = process.env.AI_HOOK_CLAUDE_KEY;
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
throw new AIHookError(
|
|
12
|
+
"INVALID_API_KEY",
|
|
13
|
+
"Missing Claude API key.",
|
|
14
|
+
"claude",
|
|
15
|
+
"Set AI_HOOK_CLAUDE_KEY in your environment variables."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const response = await axios.post(
|
|
21
|
+
`${BASE_URL}/messages`,
|
|
22
|
+
{
|
|
23
|
+
model,
|
|
24
|
+
max_tokens: 4096,
|
|
25
|
+
messages: [
|
|
26
|
+
{
|
|
27
|
+
role: "user",
|
|
28
|
+
content: prompt
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
headers: {
|
|
34
|
+
"x-api-key": apiKey,
|
|
35
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
36
|
+
"Content-Type": "application/json"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const output = response.data?.content?.[0]?.text;
|
|
42
|
+
if (!output) {
|
|
43
|
+
throw new AIHookError(
|
|
44
|
+
"PROVIDER_ERROR",
|
|
45
|
+
"Claude returned empty response",
|
|
46
|
+
"claude",
|
|
47
|
+
"Check your model and API key"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return output;
|
|
52
|
+
} catch (err: any) {
|
|
53
|
+
if (err.response) {
|
|
54
|
+
const status = err.response.status;
|
|
55
|
+
const text = err.response.data?.error
|
|
56
|
+
? JSON.stringify(err.response.data.error)
|
|
57
|
+
: err.response.statusText || "Unknown error";
|
|
58
|
+
|
|
59
|
+
if (status === 400)
|
|
60
|
+
throw new AIHookError(
|
|
61
|
+
"BAD_REQUEST",
|
|
62
|
+
`Claude rejected the request: ${text}`,
|
|
63
|
+
"claude",
|
|
64
|
+
"Check your prompt and model"
|
|
65
|
+
);
|
|
66
|
+
if (status === 401)
|
|
67
|
+
throw new AIHookError(
|
|
68
|
+
"INVALID_API_KEY",
|
|
69
|
+
`Invalid Claude API key: ${text}`,
|
|
70
|
+
"claude",
|
|
71
|
+
"Verify your AI_HOOK_CLAUDE_KEY environment variable"
|
|
72
|
+
);
|
|
73
|
+
if (status === 429)
|
|
74
|
+
throw new AIHookError(
|
|
75
|
+
"RATE_LIMIT",
|
|
76
|
+
`Too many requests to Claude: ${text}`,
|
|
77
|
+
"claude",
|
|
78
|
+
"Throttle requests or upgrade your plan"
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
throw new AIHookError(
|
|
82
|
+
"PROVIDER_ERROR",
|
|
83
|
+
`Claude API error: ${text}`,
|
|
84
|
+
"claude"
|
|
85
|
+
);
|
|
86
|
+
} else if (err.request) {
|
|
87
|
+
throw new AIHookError(
|
|
88
|
+
"NETWORK_ERROR",
|
|
89
|
+
"Network error while contacting Claude",
|
|
90
|
+
"claude",
|
|
91
|
+
"Check your internet connection"
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
throw new AIHookError(
|
|
95
|
+
"UNKNOWN_ERROR",
|
|
96
|
+
err.message,
|
|
97
|
+
"claude"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|