alvin-bot 4.4.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 +43 -0
- package/BACKLOG.md +223 -0
- package/CHANGELOG.md +63 -0
- package/CLAUDE.example.md +152 -0
- package/CODE_OF_CONDUCT.md +52 -0
- package/CONTRIBUTING.md +72 -0
- package/LICENSE +21 -0
- package/README.md +529 -0
- package/SECURITY.md +38 -0
- package/SOUL.example.md +60 -0
- package/TOOLS.example.md +42 -0
- package/alvin-bot.config.example.json +24 -0
- package/bin/cli.js +1088 -0
- package/dist/.metadata_never_index +0 -0
- package/dist/claude.js +102 -0
- package/dist/config.js +65 -0
- package/dist/engine.js +90 -0
- package/dist/find-claude-binary.js +98 -0
- package/dist/handlers/commands.js +1489 -0
- package/dist/handlers/document.js +187 -0
- package/dist/handlers/message.js +200 -0
- package/dist/handlers/photo.js +154 -0
- package/dist/handlers/platform-message.js +275 -0
- package/dist/handlers/video.js +237 -0
- package/dist/handlers/voice.js +148 -0
- package/dist/i18n.js +299 -0
- package/dist/index.js +442 -0
- package/dist/init-data-dir.js +81 -0
- package/dist/middleware/auth.js +215 -0
- package/dist/migrate.js +139 -0
- package/dist/paths.js +87 -0
- package/dist/platforms/discord.js +161 -0
- package/dist/platforms/index.js +130 -0
- package/dist/platforms/signal.js +205 -0
- package/dist/platforms/slack.js +318 -0
- package/dist/platforms/telegram.js +111 -0
- package/dist/platforms/types.js +8 -0
- package/dist/platforms/whatsapp.js +648 -0
- package/dist/providers/claude-sdk-provider.js +173 -0
- package/dist/providers/codex-cli-provider.js +121 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/openai-compatible.js +388 -0
- package/dist/providers/registry.js +209 -0
- package/dist/providers/tool-executor.js +450 -0
- package/dist/providers/types.js +205 -0
- package/dist/services/access.js +144 -0
- package/dist/services/asset-index.js +230 -0
- package/dist/services/browser-manager.js +161 -0
- package/dist/services/browser.js +121 -0
- package/dist/services/compaction.js +129 -0
- package/dist/services/cron.js +462 -0
- package/dist/services/custom-tools.js +317 -0
- package/dist/services/delivery-queue.js +154 -0
- package/dist/services/elevenlabs.js +58 -0
- package/dist/services/embeddings.js +386 -0
- package/dist/services/exec-guard.js +46 -0
- package/dist/services/fallback-order.js +151 -0
- package/dist/services/heartbeat.js +192 -0
- package/dist/services/hooks.js +44 -0
- package/dist/services/imagegen.js +72 -0
- package/dist/services/language-detect.js +144 -0
- package/dist/services/markdown.js +63 -0
- package/dist/services/mcp.js +252 -0
- package/dist/services/memory.js +133 -0
- package/dist/services/personality.js +227 -0
- package/dist/services/plugins.js +171 -0
- package/dist/services/reminders.js +97 -0
- package/dist/services/restart.js +48 -0
- package/dist/services/security-audit.js +66 -0
- package/dist/services/self-search.js +129 -0
- package/dist/services/session.js +93 -0
- package/dist/services/skills.js +287 -0
- package/dist/services/standing-orders.js +29 -0
- package/dist/services/subagents.js +142 -0
- package/dist/services/sudo.js +243 -0
- package/dist/services/telegram.js +113 -0
- package/dist/services/tool-discovery.js +214 -0
- package/dist/services/usage-tracker.js +137 -0
- package/dist/services/users.js +199 -0
- package/dist/services/voice.js +95 -0
- package/dist/tui/index.js +507 -0
- package/dist/web/canvas.js +30 -0
- package/dist/web/doctor-api.js +606 -0
- package/dist/web/openai-compat.js +252 -0
- package/dist/web/server.js +1351 -0
- package/dist/web/setup-api.js +1078 -0
- package/docs/mcp.example.json +16 -0
- package/docs/screenshots/00-Login.png +0 -0
- package/docs/screenshots/01-Chat-Dark-Conversation.png +0 -0
- package/docs/screenshots/02-Chat.png +0 -0
- package/docs/screenshots/03-Dashboard-Overview.png +0 -0
- package/docs/screenshots/04-AI-Models-and-Providers.png +0 -0
- package/docs/screenshots/05-Personality-Editor.png +0 -0
- package/docs/screenshots/06-Memory-Manager.png +0 -0
- package/docs/screenshots/07-Active-Sessions.png +0 -0
- package/docs/screenshots/08-File-Browser.png +0 -0
- package/docs/screenshots/09-Scheduled-Jobs.png +0 -0
- package/docs/screenshots/10-Custom-Tools.png +0 -0
- package/docs/screenshots/11-Plugins-and-MCP.png +0 -0
- package/docs/screenshots/12-Messaging-Platforms.png +0 -0
- package/docs/screenshots/12.1-Messaging-Platforms-WhatsApp-Groups-List.png +0 -0
- package/docs/screenshots/12.2-Messaging-Platforms-WA-Group-Details.png +0 -0
- package/docs/screenshots/13-User-Management.png +0 -0
- package/docs/screenshots/14-Web-Terminal.png +0 -0
- package/docs/screenshots/15-Maintenance-and-Health.png +0 -0
- package/docs/screenshots/16-Settings-and-Env.png +0 -0
- package/docs/screenshots/TG-commands.png +0 -0
- package/docs/screenshots/TG.png +0 -0
- package/docs/screenshots/_Mac-Installer.png +0 -0
- package/docs/tools.example.json +33 -0
- package/install.sh +165 -0
- package/package.json +190 -0
- package/plugins/calendar/index.js +270 -0
- package/plugins/email/index.js +231 -0
- package/plugins/finance/index.js +254 -0
- package/plugins/notes/index.js +227 -0
- package/plugins/smarthome/index.js +230 -0
- package/plugins/weather/index.js +122 -0
- package/skills/apple-notes/SKILL.md +31 -0
- package/skills/browse/SKILL.md +136 -0
- package/skills/code-project/SKILL.md +43 -0
- package/skills/data-analysis/SKILL.md +39 -0
- package/skills/document-creation/SKILL.md +48 -0
- package/skills/email-summary/SKILL.md +46 -0
- package/skills/github/SKILL.md +42 -0
- package/skills/summarize/SKILL.md +28 -0
- package/skills/system-admin/SKILL.md +39 -0
- package/skills/weather/SKILL.md +34 -0
- package/skills/web-research/SKILL.md +35 -0
- package/web/public/canvas.html +52 -0
- package/web/public/css/style.css +555 -0
- package/web/public/index.html +189 -0
- package/web/public/js/app.js +3102 -0
- package/web/public/js/i18n.js +1048 -0
- package/web/public/js/icons.js +104 -0
- package/web/public/login.html +48 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-Compatible API — /v1/chat/completions + /v1/models
|
|
3
|
+
*
|
|
4
|
+
* Allows external tools (e.g., OpenClaw) to use Claude via Alvin-Bot's
|
|
5
|
+
* Agent SDK. Routes through Claude Code CLI OAuth (Max subscription).
|
|
6
|
+
*
|
|
7
|
+
* Auth: Bearer token (WEBHOOK_TOKEN from .env)
|
|
8
|
+
*/
|
|
9
|
+
import crypto from "crypto";
|
|
10
|
+
import { ClaudeSDKProvider } from "../providers/claude-sdk-provider.js";
|
|
11
|
+
import { config } from "../config.js";
|
|
12
|
+
// Lazy-initialized provider (shares nothing with Telegram sessions)
|
|
13
|
+
let provider = null;
|
|
14
|
+
function getProvider() {
|
|
15
|
+
if (!provider) {
|
|
16
|
+
provider = new ClaudeSDKProvider();
|
|
17
|
+
}
|
|
18
|
+
return provider;
|
|
19
|
+
}
|
|
20
|
+
// ── Auth ────────────────────────────────────────────────
|
|
21
|
+
function checkBearer(req) {
|
|
22
|
+
if (!config.webhookToken)
|
|
23
|
+
return false; // No token = disabled
|
|
24
|
+
const auth = req.headers.authorization || "";
|
|
25
|
+
return auth === `Bearer ${config.webhookToken}`;
|
|
26
|
+
}
|
|
27
|
+
// ── Models Endpoint ─────────────────────────────────────
|
|
28
|
+
function handleModels(res) {
|
|
29
|
+
const now = Math.floor(Date.now() / 1000);
|
|
30
|
+
res.setHeader("Content-Type", "application/json");
|
|
31
|
+
res.end(JSON.stringify({
|
|
32
|
+
object: "list",
|
|
33
|
+
data: [
|
|
34
|
+
{ id: "alvin-opus-4", object: "model", created: now, owned_by: "alvin-bot" },
|
|
35
|
+
{ id: "alvin-sonnet-4", object: "model", created: now, owned_by: "alvin-bot" },
|
|
36
|
+
{ id: "alvin-haiku-4", object: "model", created: now, owned_by: "alvin-bot" },
|
|
37
|
+
],
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
function buildPromptFromMessages(messages) {
|
|
41
|
+
let systemPrompt = "";
|
|
42
|
+
const conversationParts = [];
|
|
43
|
+
for (const msg of messages) {
|
|
44
|
+
if (msg.role === "system") {
|
|
45
|
+
systemPrompt += (systemPrompt ? "\n\n" : "") + msg.content;
|
|
46
|
+
}
|
|
47
|
+
else if (msg.role === "user") {
|
|
48
|
+
conversationParts.push(`User: ${msg.content}`);
|
|
49
|
+
}
|
|
50
|
+
else if (msg.role === "assistant") {
|
|
51
|
+
conversationParts.push(`Assistant: ${msg.content}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Single message: extract raw content without "User:" prefix
|
|
55
|
+
if (conversationParts.length <= 1) {
|
|
56
|
+
const lastUser = messages.filter(m => m.role === "user").pop();
|
|
57
|
+
return { prompt: lastUser?.content || "", systemPrompt };
|
|
58
|
+
}
|
|
59
|
+
// Multi-turn: format as conversation context
|
|
60
|
+
return { prompt: conversationParts.join("\n\n"), systemPrompt };
|
|
61
|
+
}
|
|
62
|
+
async function handleChatCompletions(req, res, body) {
|
|
63
|
+
// Parse request
|
|
64
|
+
let oaiReq;
|
|
65
|
+
try {
|
|
66
|
+
oaiReq = JSON.parse(body);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
70
|
+
res.end(JSON.stringify({ error: { message: "Invalid JSON body", type: "invalid_request_error" } }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!oaiReq.messages || !Array.isArray(oaiReq.messages) || oaiReq.messages.length === 0) {
|
|
74
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
75
|
+
res.end(JSON.stringify({ error: { message: "messages array is required", type: "invalid_request_error" } }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const { prompt, systemPrompt } = buildPromptFromMessages(oaiReq.messages);
|
|
79
|
+
const completionId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
|
|
80
|
+
const created = Math.floor(Date.now() / 1000);
|
|
81
|
+
const model = oaiReq.model || "claude-opus-4";
|
|
82
|
+
// Optional session resumption via header
|
|
83
|
+
const sessionId = req.headers["x-session-id"] || null;
|
|
84
|
+
const p = getProvider();
|
|
85
|
+
if (oaiReq.stream !== false) {
|
|
86
|
+
// ── Streaming Response (SSE) ──────────────────────
|
|
87
|
+
res.writeHead(200, {
|
|
88
|
+
"Content-Type": "text/event-stream",
|
|
89
|
+
"Cache-Control": "no-cache",
|
|
90
|
+
"Connection": "keep-alive",
|
|
91
|
+
"X-Accel-Buffering": "no",
|
|
92
|
+
});
|
|
93
|
+
// Initial role chunk
|
|
94
|
+
const roleChunk = {
|
|
95
|
+
id: completionId,
|
|
96
|
+
object: "chat.completion.chunk",
|
|
97
|
+
created,
|
|
98
|
+
model,
|
|
99
|
+
choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }],
|
|
100
|
+
};
|
|
101
|
+
res.write(`data: ${JSON.stringify(roleChunk)}\n\n`);
|
|
102
|
+
let outputSessionId = "";
|
|
103
|
+
let inputTokens = 0;
|
|
104
|
+
let outputTokens = 0;
|
|
105
|
+
try {
|
|
106
|
+
for await (const chunk of p.query({ prompt, systemPrompt, sessionId })) {
|
|
107
|
+
if (chunk.type === "text" && chunk.delta) {
|
|
108
|
+
const sseChunk = {
|
|
109
|
+
id: completionId,
|
|
110
|
+
object: "chat.completion.chunk",
|
|
111
|
+
created,
|
|
112
|
+
model,
|
|
113
|
+
choices: [{ index: 0, delta: { content: chunk.delta }, finish_reason: null }],
|
|
114
|
+
};
|
|
115
|
+
res.write(`data: ${JSON.stringify(sseChunk)}\n\n`);
|
|
116
|
+
}
|
|
117
|
+
if (chunk.type === "done") {
|
|
118
|
+
outputSessionId = chunk.sessionId || "";
|
|
119
|
+
inputTokens = chunk.inputTokens || 0;
|
|
120
|
+
outputTokens = chunk.outputTokens || 0;
|
|
121
|
+
// Final chunk with finish_reason
|
|
122
|
+
const doneChunk = {
|
|
123
|
+
id: completionId,
|
|
124
|
+
object: "chat.completion.chunk",
|
|
125
|
+
created,
|
|
126
|
+
model,
|
|
127
|
+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
|
128
|
+
...(inputTokens || outputTokens ? {
|
|
129
|
+
usage: { prompt_tokens: inputTokens, completion_tokens: outputTokens, total_tokens: inputTokens + outputTokens },
|
|
130
|
+
} : {}),
|
|
131
|
+
};
|
|
132
|
+
res.write(`data: ${JSON.stringify(doneChunk)}\n\n`);
|
|
133
|
+
}
|
|
134
|
+
if (chunk.type === "error") {
|
|
135
|
+
const errChunk = {
|
|
136
|
+
id: completionId,
|
|
137
|
+
object: "chat.completion.chunk",
|
|
138
|
+
created,
|
|
139
|
+
model,
|
|
140
|
+
choices: [{ index: 0, delta: { content: `\n\n[Error: ${chunk.error}]` }, finish_reason: "stop" }],
|
|
141
|
+
};
|
|
142
|
+
res.write(`data: ${JSON.stringify(errChunk)}\n\n`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
148
|
+
const errChunk = {
|
|
149
|
+
id: completionId,
|
|
150
|
+
object: "chat.completion.chunk",
|
|
151
|
+
created,
|
|
152
|
+
model,
|
|
153
|
+
choices: [{ index: 0, delta: { content: `\n\n[Error: ${errMsg}]` }, finish_reason: "stop" }],
|
|
154
|
+
};
|
|
155
|
+
res.write(`data: ${JSON.stringify(errChunk)}\n\n`);
|
|
156
|
+
}
|
|
157
|
+
// Emit session ID for multi-turn support
|
|
158
|
+
if (outputSessionId) {
|
|
159
|
+
res.write(`data: ${JSON.stringify({ session_id: outputSessionId })}\n\n`);
|
|
160
|
+
}
|
|
161
|
+
res.write("data: [DONE]\n\n");
|
|
162
|
+
res.end();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// ── Non-Streaming Response ────────────────────────
|
|
166
|
+
let fullText = "";
|
|
167
|
+
let inputTokens = 0;
|
|
168
|
+
let outputTokens = 0;
|
|
169
|
+
let outputSessionId = "";
|
|
170
|
+
try {
|
|
171
|
+
for await (const chunk of p.query({ prompt, systemPrompt, sessionId })) {
|
|
172
|
+
if (chunk.type === "text" && chunk.delta) {
|
|
173
|
+
fullText += chunk.delta;
|
|
174
|
+
}
|
|
175
|
+
if (chunk.type === "done") {
|
|
176
|
+
outputSessionId = chunk.sessionId || "";
|
|
177
|
+
inputTokens = chunk.inputTokens || 0;
|
|
178
|
+
outputTokens = chunk.outputTokens || 0;
|
|
179
|
+
}
|
|
180
|
+
if (chunk.type === "error") {
|
|
181
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
182
|
+
res.end(JSON.stringify({
|
|
183
|
+
error: { message: chunk.error || "Provider error", type: "server_error" },
|
|
184
|
+
}));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
191
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
192
|
+
res.end(JSON.stringify({
|
|
193
|
+
error: { message: errMsg, type: "server_error" },
|
|
194
|
+
}));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
res.writeHead(200, {
|
|
198
|
+
"Content-Type": "application/json",
|
|
199
|
+
...(outputSessionId ? { "x-session-id": outputSessionId } : {}),
|
|
200
|
+
});
|
|
201
|
+
res.end(JSON.stringify({
|
|
202
|
+
id: completionId,
|
|
203
|
+
object: "chat.completion",
|
|
204
|
+
created,
|
|
205
|
+
model,
|
|
206
|
+
choices: [{
|
|
207
|
+
index: 0,
|
|
208
|
+
message: { role: "assistant", content: fullText },
|
|
209
|
+
finish_reason: "stop",
|
|
210
|
+
}],
|
|
211
|
+
usage: {
|
|
212
|
+
prompt_tokens: inputTokens,
|
|
213
|
+
completion_tokens: outputTokens,
|
|
214
|
+
total_tokens: inputTokens + outputTokens,
|
|
215
|
+
},
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// ── Exported Handler ────────────────────────────────────
|
|
220
|
+
/**
|
|
221
|
+
* Handle OpenAI-compatible API requests (/v1/...).
|
|
222
|
+
* Returns true if the request was handled, false if not an /v1/ route.
|
|
223
|
+
*/
|
|
224
|
+
export function handleOpenAICompat(req, res, urlPath, body) {
|
|
225
|
+
// Only handle /v1/ routes
|
|
226
|
+
if (!urlPath.startsWith("/v1/"))
|
|
227
|
+
return false;
|
|
228
|
+
// Auth check (Bearer token required)
|
|
229
|
+
if (!checkBearer(req)) {
|
|
230
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
231
|
+
res.end(JSON.stringify({
|
|
232
|
+
error: { message: "Invalid API key", type: "authentication_error" },
|
|
233
|
+
}));
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
// GET /v1/models
|
|
237
|
+
if (urlPath === "/v1/models" && req.method === "GET") {
|
|
238
|
+
handleModels(res);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
// POST /v1/chat/completions
|
|
242
|
+
if (urlPath === "/v1/chat/completions" && req.method === "POST") {
|
|
243
|
+
handleChatCompletions(req, res, body);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
// Unknown /v1/ route
|
|
247
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
248
|
+
res.end(JSON.stringify({
|
|
249
|
+
error: { message: `Unknown endpoint: ${urlPath}`, type: "invalid_request_error" },
|
|
250
|
+
}));
|
|
251
|
+
return true;
|
|
252
|
+
}
|