feat: add channels implementation with brain manager improvement
This commit is contained in:
72
bun.lock
72
bun.lock
@@ -8,6 +8,8 @@
|
||||
"@openrouter/sdk": "^0.12.79",
|
||||
"chalk": "^5.6.2",
|
||||
"commander": "^15.0.0",
|
||||
"discord.js": "^14.26.4",
|
||||
"gramio": "^0.12.0",
|
||||
"prettier": "^3.8.3",
|
||||
"supermemory": "^4.24.12",
|
||||
"yaml": "^2.9.0",
|
||||
@@ -22,24 +24,94 @@
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@discordjs/builders": ["@discordjs/builders@1.14.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.40", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ=="],
|
||||
|
||||
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
|
||||
|
||||
"@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="],
|
||||
|
||||
"@discordjs/rest": ["@discordjs/rest@2.6.1", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.2.0", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.5", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.40", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "6.24.1" } }, "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg=="],
|
||||
|
||||
"@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="],
|
||||
|
||||
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
|
||||
|
||||
"@gramio/callback-data": ["@gramio/callback-data@0.1.0", "", {}, "sha512-eqpR2Bod7dySDLDu8RB3xJdcrwyE3ZrhHaP+v0xOqWbgOCGY+gZp37E2E0KeXq/ko/8DLp3XeTaQD61++kiK6A=="],
|
||||
|
||||
"@gramio/composer": ["@gramio/composer@0.5.0", "", {}, "sha512-CP8DxNzDmVl2uO/L2VtjYycLi6jrPkZvTJoN/qVtct2Ms0oUKlKFgnNidH/i2R41HUow2fU0tlcGb1LR4NPHtg=="],
|
||||
|
||||
"@gramio/contexts": ["@gramio/contexts@0.9.0", "", { "peerDependencies": { "@gramio/types": "^10.1.0", "inspectable": "^3.0.1" } }, "sha512-94TQN69DyREAE9TD0ayOXHRxouOlWr7JP0Fv1oFbLwyCgKBTK7+I3JAn2RZiGWDeQ8n0oTifoEqBDPLDrMgSzg=="],
|
||||
|
||||
"@gramio/files": ["@gramio/files@0.6.1", "", { "dependencies": { "@gramio/types": "^10.0.0" } }, "sha512-by1v+a3D/9e5kO0Yl7Ob92iSXMdorqLvTscb+wwA4z6YBMGmnwL8I7CqzL0yFUUI0Xzi9S7uKbPBWm7VH4GZ8w=="],
|
||||
|
||||
"@gramio/format": ["@gramio/format@0.9.0", "", { "dependencies": { "@gramio/types": "^10.1.0" }, "peerDependencies": { "marked": "^15.0.11", "node-html-parser": ">=6.0.0" }, "optionalPeers": ["marked", "node-html-parser"] }, "sha512-dU0DWlUo01wL+yL8Pi/itFfKhxvqn8WXKQ+maYsjTFSgfo3iHF96kAsrmdwszJjYSBioWcWD2DhKJB4S1k9dXA=="],
|
||||
|
||||
"@gramio/keyboards": ["@gramio/keyboards@1.4.0", "", { "dependencies": { "@gramio/types": ">=9.6.0" } }, "sha512-sUjXV/LQmXIXRd2H0dwVXObeM+3OeUuxR/rt1hi2CUoyg5aaFy8wUfMjVMhx/9IXqHCQ7FK72zS9sqVXkvYCeg=="],
|
||||
|
||||
"@gramio/types": ["@gramio/types@10.1.0", "", {}, "sha512-vced/i5k2wbkkcOZvD3LJgv7EjPK6oJOL12u0Piag+JDDSXJt/fejPPXXG+st0vYoIQR5bd4ndrWi4Cn5PshFQ=="],
|
||||
|
||||
"@openrouter/sdk": ["@openrouter/sdk@0.12.79", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-0ZpwtnuHh3/B1piW9kHCUIQy6PAsaK/vjFdZuHxmCdAenCyUNsLA2mFpmfHNWRNb+bOO3yBc4IALa264UyzmBA=="],
|
||||
|
||||
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
|
||||
|
||||
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
|
||||
|
||||
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
|
||||
|
||||
"@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"commander": ["commander@15.0.0", "", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"discord-api-types": ["discord-api-types@0.38.49", "", {}, "sha512-XnqcWmnFZFAE8ZM8SHAw9DIV8D3Or00rMQ8iQLotrEA2PmXhl+ykaf6L6q4l474hrSUH1JaYcv+iOMRWp2p6Tg=="],
|
||||
|
||||
"discord.js": ["discord.js@14.26.4", "", { "dependencies": { "@discordjs/builders": "^1.14.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.1", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.40", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "6.24.1" } }, "sha512-4oBp8tc6Kf8IDBwAHhbsMaAqx1b5fob9SNasZT7V6yyyUydoO5i5fGuX7TmvRtR+q/WgKRnRViRoAWnG7fNyvA=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"gramio": ["gramio@0.12.0", "", { "dependencies": { "@gramio/callback-data": "^0.1.0", "@gramio/composer": "^0.5.0", "@gramio/contexts": "^0.9.0", "@gramio/files": "^0.6.1", "@gramio/format": "^0.9.0", "@gramio/keyboards": "^1.4.0", "@gramio/types": "^10.1.0", "debug": "^4.4.3" } }, "sha512-SrUGgNbur5jqe8xf5e4a1QYEX5bbIPnJV+rclcBReRwGZwUShIR0aAahe3dRRqxYIXbxFr1oShYL7q/6Nwi+BQ=="],
|
||||
|
||||
"inspectable": ["inspectable@3.0.2", "", {}, "sha512-XHygPjNXXe1TWQLlVdCYLejUklsGGo+XWS3Cn/RlkvnhkbZqcQxoXPrSh7Via5aATYJUMWVnrD5CbW2c+BtKMA=="],
|
||||
|
||||
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||
|
||||
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
|
||||
|
||||
"magic-bytes.js": ["magic-bytes.js@1.13.0", "", {}, "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="],
|
||||
|
||||
"supermemory": ["supermemory@4.24.12", "", { "bin": { "supermemory": "bin/cli" } }, "sha512-xAFextuqk4JuoW33jJaFGqT1oMppN2IgfWUrV18Fv3qAAZ6M1SR1tb+7EBq8vrEQIx4iY2MQh5p+qnfL6lI8Yw=="],
|
||||
|
||||
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="],
|
||||
|
||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||
|
||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||
|
||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
|
||||
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
|
||||
"@discordjs/rest/@sapphire/snowflake": ["@sapphire/snowflake@3.5.5", "", {}, "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ=="],
|
||||
|
||||
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"@openrouter/sdk": "^0.12.79",
|
||||
"chalk": "^5.6.2",
|
||||
"commander": "^15.0.0",
|
||||
"discord.js": "^14.26.4",
|
||||
"gramio": "^0.12.0",
|
||||
"prettier": "^3.8.3",
|
||||
"supermemory": "^4.24.12",
|
||||
"yaml": "^2.9.0",
|
||||
|
||||
@@ -1,95 +1,99 @@
|
||||
import { config } from "@/config";
|
||||
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import { mkdir, readFile, writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
export type ChannelKeys = "discord" | "telegram";
|
||||
export interface BrainDiscordConfig {
|
||||
token: string;
|
||||
}
|
||||
export interface BrainTelegramConfig {
|
||||
token: string;
|
||||
}
|
||||
export interface BrainItem {
|
||||
brainId: string;
|
||||
spaceName: string;
|
||||
displayName: string;
|
||||
baseSystemPrompt: string;
|
||||
activated: boolean;
|
||||
channel?: ChannelKeys;
|
||||
discord?: BrainDiscordConfig;
|
||||
telegram?: BrainTelegramConfig;
|
||||
}
|
||||
export type BrainItemDiscord = Omit<BrainItem, "channel" | ChannelKeys> & {
|
||||
channel: "discord";
|
||||
discord: BrainDiscordConfig;
|
||||
};
|
||||
export type BrainItemTelegram = Omit<BrainItem, "channel" | ChannelKeys> & {
|
||||
channel: "telegram";
|
||||
discord: BrainTelegramConfig;
|
||||
};
|
||||
export type BrainItemWithChannel = BrainItemDiscord | BrainItemTelegram;
|
||||
export type BrainList = BrainItem[];
|
||||
|
||||
// Layout:
|
||||
// <root>/brains.json — BrainItem[] index, mirror
|
||||
// <root>/<brainId>/brain.json — BrainItem per brain, source of truth
|
||||
|
||||
export class BrainDBManager {
|
||||
constructor(private readonly root: string = config.brainboxRoot) {}
|
||||
|
||||
private brainDir(brainId: string): string {
|
||||
return join(this.root, brainId);
|
||||
}
|
||||
|
||||
private brainFile(brainId: string): string {
|
||||
return join(this.brainDir(brainId), "brain.json");
|
||||
}
|
||||
|
||||
private indexFile(): string {
|
||||
private dbFile(): string {
|
||||
return join(this.root, "brains.json");
|
||||
}
|
||||
|
||||
private async readIndex(): Promise<BrainList> {
|
||||
private async readDB(): Promise<BrainList> {
|
||||
try {
|
||||
const content = await readFile(this.indexFile(), { encoding: "utf-8" });
|
||||
const content = await readFile(this.dbFile(), { encoding: "utf-8" });
|
||||
return JSON.parse(content) as BrainList;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async writeIndex(list: BrainList): Promise<void> {
|
||||
private async writeDB(list: BrainList): Promise<void> {
|
||||
await mkdir(this.root, { recursive: true });
|
||||
await writeFile(this.indexFile(), JSON.stringify(list, null, 2), {
|
||||
await writeFile(this.dbFile(), JSON.stringify(list, null, 2), {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
}
|
||||
|
||||
private async writeBrain(brain: BrainItem): Promise<void> {
|
||||
await mkdir(this.brainDir(brain.brainId), { recursive: true });
|
||||
await writeFile(
|
||||
this.brainFile(brain.brainId),
|
||||
JSON.stringify(brain, null, 2),
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
}
|
||||
|
||||
async loadBrain(brainId: string): Promise<BrainItem | undefined> {
|
||||
try {
|
||||
const content = await readFile(this.brainFile(brainId), {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
return JSON.parse(content) as BrainItem;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
const list = await this.readDB();
|
||||
return list.find((b) => b.brainId === brainId);
|
||||
}
|
||||
|
||||
async saveBrain(brainId: string, brain: BrainItem): Promise<void> {
|
||||
await this.writeBrain(brain);
|
||||
const list = await this.readIndex();
|
||||
const list = await this.readDB();
|
||||
const idx = list.findIndex((b) => b.brainId === brainId);
|
||||
if (idx >= 0) list[idx] = brain;
|
||||
else list.push(brain);
|
||||
await this.writeIndex(list);
|
||||
await this.writeDB(list);
|
||||
}
|
||||
|
||||
async listBrain(): Promise<Array<{ brainId: string; displayName: string }>> {
|
||||
const list = await this.readIndex();
|
||||
return list.map(({ brainId, displayName }) => ({ brainId, displayName }));
|
||||
async listAvailableBrain(): Promise<BrainItemWithChannel[]> {
|
||||
return (await this.readDB()).filter((b) =>
|
||||
this.isBrainReady(b),
|
||||
) as BrainItemWithChannel[];
|
||||
}
|
||||
|
||||
async deleteBrain(brainId: string): Promise<void> {
|
||||
await rm(this.brainDir(brainId), { recursive: true, force: true });
|
||||
const list = await this.readIndex();
|
||||
const list = await this.readDB();
|
||||
const filtered = list.filter((b) => b.brainId !== brainId);
|
||||
if (filtered.length === list.length) return;
|
||||
await this.writeIndex(filtered);
|
||||
await this.writeDB(filtered);
|
||||
}
|
||||
|
||||
async isBrainAvailable(brainId: string): Promise<boolean> {
|
||||
return (await this.loadBrain(brainId)) !== undefined;
|
||||
const item = await this.loadBrain(brainId);
|
||||
return item !== undefined && this.isBrainReady(item);
|
||||
}
|
||||
|
||||
private isBrainReady(item: BrainItem): boolean {
|
||||
if (!item.activated) return false;
|
||||
switch (item.channel) {
|
||||
case "discord":
|
||||
return !!item.discord?.token;
|
||||
case "telegram":
|
||||
return !!item.telegram?.token;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { Brain } from "@/brain";
|
||||
import type { AvailabilityStatus } from "@/openrouter/schema";
|
||||
|
||||
export interface BaseChannel {
|
||||
readonly brain: Brain;
|
||||
export abstract class BaseChannel {
|
||||
constructor(public readonly brain: Brain) {}
|
||||
|
||||
init(): Promise<void>;
|
||||
abstract init(): Promise<void>;
|
||||
|
||||
send(text: string, opts?: { replyTo?: string }): Promise<void>;
|
||||
setAvailability(status: AvailabilityStatus): Promise<void>;
|
||||
}
|
||||
abstract send(text: string, opts?: { replyTo?: string }): Promise<void>;
|
||||
|
||||
abstract setAvailability(status: AvailabilityStatus): Promise<void>;
|
||||
}
|
||||
34
src/channel/discord.ts
Normal file
34
src/channel/discord.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Client, Events, GatewayIntentBits } from "discord.js";
|
||||
import { z } from "zod";
|
||||
import type { AvailabilityStatus } from "@/openrouter/schema";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { BaseChannel } from "./base";
|
||||
|
||||
const discordConfigSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export class DiscordChannel extends BaseChannel {
|
||||
private client?: Client;
|
||||
|
||||
async init(): Promise<void> {
|
||||
const { token } = discordConfigSchema.parse({
|
||||
token: this.brain.brainbase.discord?.token,
|
||||
});
|
||||
this.client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
this.client.once(Events.ClientReady, (c) => {
|
||||
logger.success(`Discord ready as ${c.user.tag}`);
|
||||
});
|
||||
await this.client.login(token);
|
||||
}
|
||||
|
||||
async send(_text: string, _opts?: { replyTo?: string }): Promise<void> {
|
||||
// ponytail: stub — wire up this.client.channels to send
|
||||
throw new Error("DiscordChannel.send not implemented");
|
||||
}
|
||||
|
||||
async setAvailability(_status: AvailabilityStatus): Promise<void> {
|
||||
// ponytail: stub — wire up this.client.user.setPresence / setStatus
|
||||
throw new Error("DiscordChannel.setAvailability not implemented");
|
||||
}
|
||||
}
|
||||
34
src/channel/telegram.ts
Normal file
34
src/channel/telegram.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Bot } from "gramio";
|
||||
import { z } from "zod";
|
||||
import type { AvailabilityStatus } from "@/openrouter/schema";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { BaseChannel } from "./base";
|
||||
|
||||
const telegramConfigSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export class TelegramChannel extends BaseChannel {
|
||||
private bot?: Bot;
|
||||
|
||||
async init(): Promise<void> {
|
||||
const { token } = telegramConfigSchema.parse({
|
||||
token: this.brain.brainbase.telegram?.token,
|
||||
});
|
||||
this.bot = new Bot(token);
|
||||
this.bot.onStart(({ info }) => {
|
||||
logger.success(`Telegram ready as @${info.username}`);
|
||||
});
|
||||
await this.bot.start();
|
||||
}
|
||||
|
||||
async send(_text: string, _opts?: { replyTo?: string }): Promise<void> {
|
||||
// ponytail: stub — wire up this.bot.api.sendMessage
|
||||
throw new Error("TelegramChannel.send not implemented");
|
||||
}
|
||||
|
||||
async setAvailability(_status: AvailabilityStatus): Promise<void> {
|
||||
// ponytail: stub — Telegram has no presence concept; map to custom status or no-op
|
||||
throw new Error("TelegramChannel.setAvailability not implemented");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user