@alfe.ai/ai-proxy-local 0.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/dist/cli.d.ts +1 -0
- package/dist/cli.js +56 -0
- package/dist/cli.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +52 -0
- package/dist/server.js.map +1 -0
- package/package.json +28 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createProxyServer } from "./server.js";
|
|
3
|
+
import { getAiProxyUrlFromToken, readConfig } from "@alfe.ai/config";
|
|
4
|
+
//#region src/cli.ts
|
|
5
|
+
/**
|
|
6
|
+
* CLI entry point for the local AI proxy.
|
|
7
|
+
*
|
|
8
|
+
* Reads the API key from ~/.alfe/config.toml, derives the ai-proxy URL
|
|
9
|
+
* from the key prefix, and starts the proxy server.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* alfe-ai-proxy [--port 18193]
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_PORT = 18193;
|
|
15
|
+
function parsePort(args) {
|
|
16
|
+
const idx = args.indexOf("--port");
|
|
17
|
+
if (idx !== -1 && args[idx + 1]) {
|
|
18
|
+
const port = parseInt(args[idx + 1], 10);
|
|
19
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
20
|
+
console.error(`Invalid port: ${args[idx + 1]}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
return port;
|
|
24
|
+
}
|
|
25
|
+
return DEFAULT_PORT;
|
|
26
|
+
}
|
|
27
|
+
async function main() {
|
|
28
|
+
const port = parsePort(process.argv.slice(2));
|
|
29
|
+
let apiKey;
|
|
30
|
+
let proxyUrl;
|
|
31
|
+
try {
|
|
32
|
+
apiKey = (await readConfig()).api_key;
|
|
33
|
+
proxyUrl = getAiProxyUrlFromToken(apiKey);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(`[ai-proxy-local] Failed to read config: ${err instanceof Error ? err.message : err}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const server = createProxyServer({
|
|
39
|
+
port,
|
|
40
|
+
apiKey,
|
|
41
|
+
proxyUrl
|
|
42
|
+
});
|
|
43
|
+
server.listen(port, "127.0.0.1", () => {
|
|
44
|
+
console.log(`[ai-proxy-local] listening on http://127.0.0.1:${port}`);
|
|
45
|
+
console.log(`[ai-proxy-local] forwarding to ${proxyUrl}`);
|
|
46
|
+
});
|
|
47
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => {
|
|
48
|
+
console.log(`[ai-proxy-local] received ${signal}, shutting down`);
|
|
49
|
+
server.close(() => process.exit(0));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
main();
|
|
53
|
+
//#endregion
|
|
54
|
+
export {};
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * CLI entry point for the local AI proxy.\n *\n * Reads the API key from ~/.alfe/config.toml, derives the ai-proxy URL\n * from the key prefix, and starts the proxy server.\n *\n * Usage:\n * alfe-ai-proxy [--port 18193]\n */\n\nimport { readConfig, getAiProxyUrlFromToken } from \"@alfe.ai/config\";\nimport { createProxyServer } from \"./server.js\";\n\nconst DEFAULT_PORT = 18193;\n\nfunction parsePort(args: string[]): number {\n const idx = args.indexOf(\"--port\");\n if (idx !== -1 && args[idx + 1]) {\n const port = parseInt(args[idx + 1], 10);\n if (Number.isNaN(port) || port < 1 || port > 65535) {\n console.error(`Invalid port: ${args[idx + 1]}`);\n process.exit(1);\n }\n return port;\n }\n return DEFAULT_PORT;\n}\n\nasync function main() {\n const port = parsePort(process.argv.slice(2));\n\n let apiKey: string;\n let proxyUrl: string;\n\n try {\n const cfg = await readConfig();\n apiKey = cfg.api_key;\n proxyUrl = getAiProxyUrlFromToken(apiKey);\n } catch (err) {\n console.error(\n `[ai-proxy-local] Failed to read config: ${err instanceof Error ? err.message : err}`,\n );\n process.exit(1);\n }\n\n const server = createProxyServer({ port, apiKey, proxyUrl });\n\n server.listen(port, \"127.0.0.1\", () => {\n console.log(`[ai-proxy-local] listening on http://127.0.0.1:${port}`);\n console.log(`[ai-proxy-local] forwarding to ${proxyUrl}`);\n });\n\n // Graceful shutdown\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n console.log(`[ai-proxy-local] received ${signal}, shutting down`);\n server.close(() => process.exit(0));\n });\n }\n}\n\nmain();\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,eAAe;AAErB,SAAS,UAAU,MAAwB;CACzC,MAAM,MAAM,KAAK,QAAQ,SAAS;AAClC,KAAI,QAAQ,MAAM,KAAK,MAAM,IAAI;EAC/B,MAAM,OAAO,SAAS,KAAK,MAAM,IAAI,GAAG;AACxC,MAAI,OAAO,MAAM,KAAK,IAAI,OAAO,KAAK,OAAO,OAAO;AAClD,WAAQ,MAAM,iBAAiB,KAAK,MAAM,KAAK;AAC/C,WAAQ,KAAK,EAAE;;AAEjB,SAAO;;AAET,QAAO;;AAGT,eAAe,OAAO;CACpB,MAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,EAAE,CAAC;CAE7C,IAAI;CACJ,IAAI;AAEJ,KAAI;AAEF,YADY,MAAM,YAAY,EACjB;AACb,aAAW,uBAAuB,OAAO;UAClC,KAAK;AACZ,UAAQ,MACN,2CAA2C,eAAe,QAAQ,IAAI,UAAU,MACjF;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,SAAS,kBAAkB;EAAE;EAAM;EAAQ;EAAU,CAAC;AAE5D,QAAO,OAAO,MAAM,mBAAmB;AACrC,UAAQ,IAAI,kDAAkD,OAAO;AACrE,UAAQ,IAAI,kCAAkC,WAAW;GACzD;AAGF,MAAK,MAAM,UAAU,CAAC,UAAU,UAAU,CACxC,SAAQ,GAAG,cAAc;AACvB,UAAQ,IAAI,6BAA6B,OAAO,iBAAiB;AACjE,SAAO,YAAY,QAAQ,KAAK,EAAE,CAAC;GACnC;;AAIN,MAAM"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as node_http0 from "node:http";
|
|
2
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
|
|
4
|
+
//#region src/server.d.ts
|
|
5
|
+
|
|
6
|
+
interface ProxyOptions {
|
|
7
|
+
port: number;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
proxyUrl: string;
|
|
10
|
+
}
|
|
11
|
+
declare function createProxyServer(options: ProxyOptions): node_http0.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
12
|
+
//# sourceMappingURL=server.d.ts.map
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ProxyOptions, createProxyServer };
|
|
15
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;;AAwBuD,UANtC,YAAA,CAMsC;;;;;iBAAvC,iBAAA,UAA2B,eAAY,UAAA,CAAA,cAAA,wBAAA"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { request } from "node:https";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
import { createLogger } from "@auriclabs/logger";
|
|
5
|
+
//#region src/server.ts
|
|
6
|
+
/**
|
|
7
|
+
* @alfe.ai/ai-proxy-local — lightweight HTTP proxy that injects the agent's
|
|
8
|
+
* Alfe API key and forwards requests to the cloud ai-proxy.
|
|
9
|
+
*
|
|
10
|
+
* Runs on localhost so OpenClaw can point its baseUrl here. The proxy adds
|
|
11
|
+
* the `x-api-key` header and streams responses back (critical for SSE).
|
|
12
|
+
*
|
|
13
|
+
* Uses only Node built-ins (http, https, url) — zero external deps beyond
|
|
14
|
+
* @alfe.ai/config for reading ~/.alfe/config.toml.
|
|
15
|
+
*/
|
|
16
|
+
const log = createLogger("AiProxyServer");
|
|
17
|
+
function createProxyServer(options) {
|
|
18
|
+
const { apiKey, proxyUrl } = options;
|
|
19
|
+
const target = new URL(proxyUrl);
|
|
20
|
+
return createServer((req, res) => {
|
|
21
|
+
const upstreamUrl = new URL(req.url ?? "/", proxyUrl);
|
|
22
|
+
const headers = {
|
|
23
|
+
...req.headers,
|
|
24
|
+
"x-api-key": apiKey,
|
|
25
|
+
host: target.host
|
|
26
|
+
};
|
|
27
|
+
delete headers["connection"];
|
|
28
|
+
delete headers["keep-alive"];
|
|
29
|
+
const upstreamReq = request({
|
|
30
|
+
hostname: target.hostname,
|
|
31
|
+
port: target.port || 443,
|
|
32
|
+
path: upstreamUrl.pathname + upstreamUrl.search,
|
|
33
|
+
method: req.method,
|
|
34
|
+
headers
|
|
35
|
+
}, (upstreamRes) => {
|
|
36
|
+
res.writeHead(upstreamRes.statusCode ?? 502, upstreamRes.headers);
|
|
37
|
+
upstreamRes.pipe(res);
|
|
38
|
+
});
|
|
39
|
+
upstreamReq.on("error", (err) => {
|
|
40
|
+
log.error({ err }, "Upstream error");
|
|
41
|
+
if (!res.headersSent) {
|
|
42
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
43
|
+
res.end(JSON.stringify({ error: "ai-proxy-local: upstream unreachable" }));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
req.pipe(upstreamReq);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { createProxyServer };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":["httpsRequest"],"sources":["../src/server.ts"],"sourcesContent":["/**\n * @alfe.ai/ai-proxy-local — lightweight HTTP proxy that injects the agent's\n * Alfe API key and forwards requests to the cloud ai-proxy.\n *\n * Runs on localhost so OpenClaw can point its baseUrl here. The proxy adds\n * the `x-api-key` header and streams responses back (critical for SSE).\n *\n * Uses only Node built-ins (http, https, url) — zero external deps beyond\n * @alfe.ai/config for reading ~/.alfe/config.toml.\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { request as httpsRequest } from \"node:https\";\nimport { URL } from \"node:url\";\nimport { createLogger } from \"@auriclabs/logger\";\n\nconst log = createLogger(\"AiProxyServer\");\n\nexport interface ProxyOptions {\n port: number;\n apiKey: string;\n proxyUrl: string;\n}\n\nexport function createProxyServer(options: ProxyOptions) {\n const { apiKey, proxyUrl } = options;\n const target = new URL(proxyUrl);\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const upstreamUrl = new URL(req.url ?? \"/\", proxyUrl);\n\n const headers: Record<string, string | string[] | undefined> = {\n ...req.headers,\n \"x-api-key\": apiKey,\n host: target.host,\n };\n\n // Remove hop-by-hop headers that shouldn't be forwarded\n delete headers[\"connection\"];\n delete headers[\"keep-alive\"];\n\n const upstreamReq = httpsRequest(\n {\n hostname: target.hostname,\n port: target.port || 443,\n path: upstreamUrl.pathname + upstreamUrl.search,\n method: req.method,\n headers,\n },\n (upstreamRes) => {\n res.writeHead(upstreamRes.statusCode ?? 502, upstreamRes.headers);\n upstreamRes.pipe(res);\n },\n );\n\n upstreamReq.on(\"error\", (err) => {\n log.error({ err }, \"Upstream error\");\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"ai-proxy-local: upstream unreachable\" }));\n }\n });\n\n req.pipe(upstreamReq);\n });\n\n return server;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,MAAM,MAAM,aAAa,gBAAgB;AAQzC,SAAgB,kBAAkB,SAAuB;CACvD,MAAM,EAAE,QAAQ,aAAa;CAC7B,MAAM,SAAS,IAAI,IAAI,SAAS;AAwChC,QAtCe,cAAc,KAAsB,QAAwB;EACzE,MAAM,cAAc,IAAI,IAAI,IAAI,OAAO,KAAK,SAAS;EAErD,MAAM,UAAyD;GAC7D,GAAG,IAAI;GACP,aAAa;GACb,MAAM,OAAO;GACd;AAGD,SAAO,QAAQ;AACf,SAAO,QAAQ;EAEf,MAAM,cAAcA,QAClB;GACE,UAAU,OAAO;GACjB,MAAM,OAAO,QAAQ;GACrB,MAAM,YAAY,WAAW,YAAY;GACzC,QAAQ,IAAI;GACZ;GACD,GACA,gBAAgB;AACf,OAAI,UAAU,YAAY,cAAc,KAAK,YAAY,QAAQ;AACjE,eAAY,KAAK,IAAI;IAExB;AAED,cAAY,GAAG,UAAU,QAAQ;AAC/B,OAAI,MAAM,EAAE,KAAK,EAAE,iBAAiB;AACpC,OAAI,CAAC,IAAI,aAAa;AACpB,QAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,QAAI,IAAI,KAAK,UAAU,EAAE,OAAO,wCAAwC,CAAC,CAAC;;IAE5E;AAEF,MAAI,KAAK,YAAY;GACrB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alfe.ai/ai-proxy-local",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"alfe-ai-proxy": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/server.js",
|
|
9
|
+
"types": "./dist/server.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/server.d.ts",
|
|
13
|
+
"import": "./dist/server.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@auriclabs/logger": "^0.1.1",
|
|
21
|
+
"@alfe.ai/config": "^0.0.1"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsdown",
|
|
25
|
+
"dev": "tsdown --watch",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
}
|
|
28
|
+
}
|