@hushhenry/ai-gateway 1.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/README.md +50 -0
- package/dist/cli.js +30 -0
- package/dist/core/auth.js +32 -0
- package/dist/core/gateway.js +48 -0
- package/dist/core/providers.js +25 -0
- package/dist/index.js +19 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# AI Gateway
|
|
2
|
+
|
|
3
|
+
A TypeScript AI proxy supporting CLI and Library modes, built with Vercel AI SDK.
|
|
4
|
+
Designed to be compatible with `pi-mono` authentication storage.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g ai-gateway
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### CLI
|
|
15
|
+
|
|
16
|
+
Start the server:
|
|
17
|
+
```bash
|
|
18
|
+
ai-gateway serve --port 8080
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The server provides OpenAI-compatible endpoints:
|
|
22
|
+
- `POST /v1/chat/completions`
|
|
23
|
+
- `GET /v1/models`
|
|
24
|
+
|
|
25
|
+
### Library
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { AiGateway } from 'ai-gateway';
|
|
29
|
+
|
|
30
|
+
const gateway = new AiGateway();
|
|
31
|
+
// Use gateway.fetch with your Hono/Node server
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Credentials are looked up in:
|
|
37
|
+
1. `~/.config/ai-gateway/auth.json`
|
|
38
|
+
2. `~/.config/pi/auth.json`
|
|
39
|
+
|
|
40
|
+
Format:
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"openai": {
|
|
44
|
+
"apiKey": "sk-..."
|
|
45
|
+
},
|
|
46
|
+
"anthropic": {
|
|
47
|
+
"apiKey": "sk-ant-..."
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const node_server_1 = require("@hono/node-server");
|
|
6
|
+
const gateway_js_1 = require("./core/gateway.js");
|
|
7
|
+
const program = new commander_1.Command();
|
|
8
|
+
program
|
|
9
|
+
.name('ai-gateway')
|
|
10
|
+
.description('AI Proxy Gateway supporting Vercel AI SDK and various providers')
|
|
11
|
+
.version('1.0.0');
|
|
12
|
+
program.command('serve')
|
|
13
|
+
.description('Start the AI Gateway server')
|
|
14
|
+
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
15
|
+
.action((options) => {
|
|
16
|
+
const port = parseInt(options.port);
|
|
17
|
+
console.log(`Starting AI Gateway on port ${port}...`);
|
|
18
|
+
const gateway = new gateway_js_1.AiGateway();
|
|
19
|
+
(0, node_server_1.serve)({
|
|
20
|
+
fetch: gateway.fetch,
|
|
21
|
+
port
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
program.command('login')
|
|
25
|
+
.description('Configure providers (TUI)')
|
|
26
|
+
.action(() => {
|
|
27
|
+
console.log('TUI Login not yet implemented in this minimal build.');
|
|
28
|
+
console.log('Please edit ~/.config/ai-gateway/auth.json manually for now.');
|
|
29
|
+
});
|
|
30
|
+
program.parse();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadAuth = loadAuth;
|
|
4
|
+
exports.getCredentials = getCredentials;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const DEFAULT_CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.config', 'ai-gateway');
|
|
9
|
+
const PI_CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.config', 'pi');
|
|
10
|
+
const AUTH_FILE = 'auth.json';
|
|
11
|
+
function loadAuth(configPath) {
|
|
12
|
+
const pathsToCheck = [];
|
|
13
|
+
if (configPath)
|
|
14
|
+
pathsToCheck.push(configPath);
|
|
15
|
+
pathsToCheck.push((0, path_1.join)(DEFAULT_CONFIG_DIR, AUTH_FILE));
|
|
16
|
+
pathsToCheck.push((0, path_1.join)(PI_CONFIG_DIR, AUTH_FILE));
|
|
17
|
+
for (const p of pathsToCheck) {
|
|
18
|
+
if ((0, fs_1.existsSync)(p)) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse((0, fs_1.readFileSync)(p, 'utf-8'));
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
console.warn(`Failed to parse auth file at ${p}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
async function getCredentials(providerId, configPath) {
|
|
30
|
+
const auth = loadAuth(configPath);
|
|
31
|
+
return auth[providerId] || null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AiGateway = void 0;
|
|
4
|
+
const hono_1 = require("hono");
|
|
5
|
+
const ai_1 = require("ai");
|
|
6
|
+
const providers_1 = require("./providers");
|
|
7
|
+
const auth_1 = require("./auth");
|
|
8
|
+
class AiGateway {
|
|
9
|
+
app;
|
|
10
|
+
configPath;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.configPath = options.configPath;
|
|
13
|
+
this.app = new hono_1.Hono();
|
|
14
|
+
this.setupRoutes();
|
|
15
|
+
}
|
|
16
|
+
setupRoutes() {
|
|
17
|
+
this.app.get('/v1/models', async (c) => {
|
|
18
|
+
const auth = (0, auth_1.loadAuth)(this.configPath);
|
|
19
|
+
const models = Object.keys(auth).map(id => ({
|
|
20
|
+
id,
|
|
21
|
+
object: 'model',
|
|
22
|
+
created: Date.now(),
|
|
23
|
+
owned_by: 'ai-gateway'
|
|
24
|
+
}));
|
|
25
|
+
return c.json({ object: 'list', data: models });
|
|
26
|
+
});
|
|
27
|
+
this.app.post('/v1/chat/completions', async (c) => {
|
|
28
|
+
const body = await c.req.json();
|
|
29
|
+
const modelId = body.model;
|
|
30
|
+
try {
|
|
31
|
+
const provider = await (0, providers_1.getProvider)(modelId, this.configPath);
|
|
32
|
+
const result = await (0, ai_1.streamText)({
|
|
33
|
+
model: provider,
|
|
34
|
+
messages: body.messages,
|
|
35
|
+
temperature: body.temperature,
|
|
36
|
+
topP: body.top_p,
|
|
37
|
+
maxTokens: body.max_tokens,
|
|
38
|
+
});
|
|
39
|
+
return result.toDataStreamResponse();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return c.json({ error: { message: error.message } }, 500);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
fetch = (req) => this.app.fetch(req);
|
|
47
|
+
}
|
|
48
|
+
exports.AiGateway = AiGateway;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getProvider = getProvider;
|
|
4
|
+
const auth_1 = require("./auth");
|
|
5
|
+
async function getProvider(modelId, configPath) {
|
|
6
|
+
// Basic routing logic: provider:model
|
|
7
|
+
const [providerBrand, ...modelNameParts] = modelId.split(':');
|
|
8
|
+
const modelName = modelNameParts.join(':');
|
|
9
|
+
const creds = await (0, auth_1.getCredentials)(providerBrand, configPath);
|
|
10
|
+
if (!creds)
|
|
11
|
+
throw new Error(`No credentials found for provider: ${providerBrand}`);
|
|
12
|
+
switch (providerBrand) {
|
|
13
|
+
case 'openai':
|
|
14
|
+
const { createOpenAI } = await import('@ai-sdk/openai');
|
|
15
|
+
return createOpenAI({ apiKey: creds.apiKey })(modelName);
|
|
16
|
+
case 'anthropic':
|
|
17
|
+
const { createAnthropic } = await import('@ai-sdk/anthropic');
|
|
18
|
+
return createAnthropic({ apiKey: creds.apiKey })(modelName);
|
|
19
|
+
case 'google':
|
|
20
|
+
const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
|
|
21
|
+
return createGoogleGenerativeAI({ apiKey: creds.apiKey })(modelName);
|
|
22
|
+
default:
|
|
23
|
+
throw new Error(`Unsupported provider: ${providerBrand}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./core/gateway.js"), exports);
|
|
18
|
+
__exportStar(require("./core/auth.js"), exports);
|
|
19
|
+
__exportStar(require("./core/providers.js"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hushhenry/ai-gateway",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A TypeScript AI proxy supporting CLI and Library modes, built with Vercel AI SDK and pi-mono TUI/OAuth logic.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ai-gateway": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"start": "node dist/cli.js serve",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ai",
|
|
20
|
+
"proxy",
|
|
21
|
+
"gateway",
|
|
22
|
+
"vercel-ai-sdk",
|
|
23
|
+
"openai",
|
|
24
|
+
"github-copilot",
|
|
25
|
+
"oauth"
|
|
26
|
+
],
|
|
27
|
+
"author": "hushhenry",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@ai-sdk/anthropic": "^1.1.0",
|
|
31
|
+
"@ai-sdk/google": "^1.1.0",
|
|
32
|
+
"@ai-sdk/openai": "^1.1.0",
|
|
33
|
+
"@hono/node-server": "^1.19.9",
|
|
34
|
+
"ai": "^4.1.0",
|
|
35
|
+
"commander": "^12.0.0",
|
|
36
|
+
"hono": "^4.0.0",
|
|
37
|
+
"zod": "^3.23.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.19.33",
|
|
41
|
+
"typescript": "^5.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|