@4via6/relay 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/.env.example +50 -0
- package/README.md +253 -0
- package/dist/auth.d.ts +3 -0
- package/dist/auth.js +32 -0
- package/dist/auth.js.map +1 -0
- package/dist/bot.d.ts +2 -0
- package/dist/bot.js +14 -0
- package/dist/bot.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/admin.d.ts +2 -0
- package/dist/commands/admin.js +420 -0
- package/dist/commands/admin.js.map +1 -0
- package/dist/commands/chat.d.ts +2 -0
- package/dist/commands/chat.js +80 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.js +162 -0
- package/dist/commands/files.js.map +1 -0
- package/dist/commands/history.d.ts +2 -0
- package/dist/commands/history.js +152 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +21 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +105 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/media.d.ts +2 -0
- package/dist/commands/media.js +248 -0
- package/dist/commands/media.js.map +1 -0
- package/dist/commands/monitor.d.ts +2 -0
- package/dist/commands/monitor.js +136 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +114 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/shell.d.ts +2 -0
- package/dist/commands/shell.js +90 -0
- package/dist/commands/shell.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/claude.d.ts +52 -0
- package/dist/providers/claude.js +449 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex.d.ts +45 -0
- package/dist/providers/codex.js +400 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.js +40 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/opencode.d.ts +40 -0
- package/dist/providers/opencode.js +498 -0
- package/dist/providers/opencode.js.map +1 -0
- package/dist/providers/types.d.ts +173 -0
- package/dist/providers/types.js +6 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/session.d.ts +10 -0
- package/dist/session.js +65 -0
- package/dist/session.js.map +1 -0
- package/dist/utils/chunker.d.ts +1 -0
- package/dist/utils/chunker.js +24 -0
- package/dist/utils/chunker.js.map +1 -0
- package/dist/utils/errors.d.ts +12 -0
- package/dist/utils/errors.js +85 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/files.d.ts +15 -0
- package/dist/utils/files.js +81 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/html.d.ts +4 -0
- package/dist/utils/html.js +10 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/media.d.ts +15 -0
- package/dist/utils/media.js +103 -0
- package/dist/utils/media.js.map +1 -0
- package/dist/utils/store.d.ts +15 -0
- package/dist/utils/store.js +44 -0
- package/dist/utils/store.js.map +1 -0
- package/dist/utils/stream.d.ts +21 -0
- package/dist/utils/stream.js +149 -0
- package/dist/utils/stream.js.map +1 -0
- package/dist/utils/stt.d.ts +7 -0
- package/dist/utils/stt.js +118 -0
- package/dist/utils/stt.js.map +1 -0
- package/dist/utils/system-prompt.d.ts +5 -0
- package/dist/utils/system-prompt.js +64 -0
- package/dist/utils/system-prompt.js.map +1 -0
- package/dist/utils/timeout.d.ts +2 -0
- package/dist/utils/timeout.js +18 -0
- package/dist/utils/timeout.js.map +1 -0
- package/docs/commands.md +351 -0
- package/docs/configuration.md +160 -0
- package/docs/features.md +443 -0
- package/docs/getting-started.md +102 -0
- package/docs/providers.md +236 -0
- package/docs/troubleshooting.md +287 -0
- package/package.json +54 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple JSON-file-backed persistence store.
|
|
3
|
+
* Atomic writes via tmp+rename to prevent corruption on crash.
|
|
4
|
+
*/
|
|
5
|
+
export declare class JsonStore<T> {
|
|
6
|
+
private filePath;
|
|
7
|
+
private defaultValue;
|
|
8
|
+
constructor(filename: string, defaultValue: T);
|
|
9
|
+
/** Load current value from disk, or return default if file missing/corrupt. */
|
|
10
|
+
load(): T;
|
|
11
|
+
/** Overwrite the file with new data (atomic write). */
|
|
12
|
+
save(data: T): void;
|
|
13
|
+
/** Read-modify-write helper. */
|
|
14
|
+
update(fn: (current: T) => T): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const DATA_DIR = process.env.RELAY_DATA_DIR ?? join(process.cwd(), ".relay");
|
|
4
|
+
function ensureDir(dir) {
|
|
5
|
+
if (!existsSync(dir)) {
|
|
6
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Simple JSON-file-backed persistence store.
|
|
11
|
+
* Atomic writes via tmp+rename to prevent corruption on crash.
|
|
12
|
+
*/
|
|
13
|
+
export class JsonStore {
|
|
14
|
+
filePath;
|
|
15
|
+
defaultValue;
|
|
16
|
+
constructor(filename, defaultValue) {
|
|
17
|
+
ensureDir(DATA_DIR);
|
|
18
|
+
this.filePath = join(DATA_DIR, filename);
|
|
19
|
+
this.defaultValue = defaultValue;
|
|
20
|
+
}
|
|
21
|
+
/** Load current value from disk, or return default if file missing/corrupt. */
|
|
22
|
+
load() {
|
|
23
|
+
try {
|
|
24
|
+
if (!existsSync(this.filePath))
|
|
25
|
+
return structuredClone(this.defaultValue);
|
|
26
|
+
const raw = readFileSync(this.filePath, "utf-8");
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return structuredClone(this.defaultValue);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Overwrite the file with new data (atomic write). */
|
|
34
|
+
save(data) {
|
|
35
|
+
const tmp = this.filePath + ".tmp";
|
|
36
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
37
|
+
renameSync(tmp, this.filePath);
|
|
38
|
+
}
|
|
39
|
+
/** Read-modify-write helper. */
|
|
40
|
+
update(fn) {
|
|
41
|
+
this.save(fn(this.load()));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/utils/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;AAE7E,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,SAAS;IACZ,QAAQ,CAAS;IACjB,YAAY,CAAI;IAExB,YAAY,QAAgB,EAAE,YAAe;QAC3C,SAAS,CAAC,QAAQ,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;IAED,+EAA+E;IAC/E,IAAI;QACF,IAAI,CAAC;YACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,OAAO,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1E,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,uDAAuD;IACvD,IAAI,CAAC,IAAO;QACV,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC;QACnC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAED,gCAAgC;IAChC,MAAM,CAAC,EAAqB;QAC1B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Context } from "grammy";
|
|
2
|
+
export declare function isStreamingEnabled(): boolean;
|
|
3
|
+
export interface StreamPromptOptions {
|
|
4
|
+
ctx: Context;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
parts: Array<{
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: "file";
|
|
11
|
+
mime: string;
|
|
12
|
+
filename?: string;
|
|
13
|
+
url: string;
|
|
14
|
+
}>;
|
|
15
|
+
model?: {
|
|
16
|
+
providerID: string;
|
|
17
|
+
modelID: string;
|
|
18
|
+
} | null;
|
|
19
|
+
system?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function streamPrompt({ ctx, sessionId, parts, model, system, }: StreamPromptOptions): Promise<void>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { InputFile } from "grammy";
|
|
2
|
+
import { getProvider } from "../providers/index.js";
|
|
3
|
+
import { chunkMessage } from "./chunker.js";
|
|
4
|
+
import { formatCatchError, EMPTY_RESPONSE_MSG } from "./errors.js";
|
|
5
|
+
import { sendResponseFiles } from "./files.js";
|
|
6
|
+
const EDIT_INTERVAL = Number(process.env.STREAM_EDIT_INTERVAL_MS) || 2000;
|
|
7
|
+
export function isStreamingEnabled() {
|
|
8
|
+
return process.env.STREAMING_ENABLED === "true";
|
|
9
|
+
}
|
|
10
|
+
export async function streamPrompt({ ctx, sessionId, parts, model, system, }) {
|
|
11
|
+
const provider = getProvider();
|
|
12
|
+
if (!provider.promptStream) {
|
|
13
|
+
// Fallback to non-streaming prompt
|
|
14
|
+
const result = await provider.prompt(sessionId, parts[0]?.type === "text" ? parts[0].text : "", {
|
|
15
|
+
parts: parts,
|
|
16
|
+
...(model && { model }),
|
|
17
|
+
...(system && { system }),
|
|
18
|
+
});
|
|
19
|
+
await sendFinalText(ctx, result.text);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Send placeholder
|
|
23
|
+
const placeholder = await ctx.reply("Thinking...");
|
|
24
|
+
const chatId = placeholder.chat.id;
|
|
25
|
+
const messageId = placeholder.message_id;
|
|
26
|
+
// Keep typing indicator active
|
|
27
|
+
const typingInterval = setInterval(() => {
|
|
28
|
+
ctx.replyWithChatAction("typing").catch(() => { });
|
|
29
|
+
}, 4000);
|
|
30
|
+
let accumulated = "";
|
|
31
|
+
let toolStatus = "";
|
|
32
|
+
let lastEditTime = 0;
|
|
33
|
+
let errorMsg = null;
|
|
34
|
+
const collectedFiles = [];
|
|
35
|
+
try {
|
|
36
|
+
const stream = provider.promptStream(sessionId, parts[0]?.type === "text" ? parts[0].text : "", {
|
|
37
|
+
parts: parts,
|
|
38
|
+
...(model && { model }),
|
|
39
|
+
...(system && { system }),
|
|
40
|
+
});
|
|
41
|
+
for await (const chunk of stream) {
|
|
42
|
+
if (chunk.type === "text") {
|
|
43
|
+
accumulated += chunk.content;
|
|
44
|
+
}
|
|
45
|
+
else if (chunk.type === "tool_use") {
|
|
46
|
+
toolStatus = chunk.content;
|
|
47
|
+
}
|
|
48
|
+
else if (chunk.type === "file" && chunk.file) {
|
|
49
|
+
collectedFiles.push(chunk.file);
|
|
50
|
+
}
|
|
51
|
+
else if (chunk.type === "done") {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
// Throttled edit
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
if (now - lastEditTime >= EDIT_INTERVAL) {
|
|
57
|
+
const display = buildDisplayText(accumulated, toolStatus);
|
|
58
|
+
await safeEditMessage(ctx, chatId, messageId, display);
|
|
59
|
+
lastEditTime = now;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
errorMsg = formatCatchError(err, "streaming response");
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
clearInterval(typingInterval);
|
|
68
|
+
}
|
|
69
|
+
// Final response
|
|
70
|
+
if (errorMsg) {
|
|
71
|
+
await safeEditMessage(ctx, chatId, messageId, errorMsg, true);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!accumulated.trim()) {
|
|
75
|
+
await safeEditMessage(ctx, chatId, messageId, EMPTY_RESPONSE_MSG, true);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await sendFinalResponse(ctx, chatId, messageId, accumulated);
|
|
79
|
+
// Send any collected file attachments
|
|
80
|
+
if (collectedFiles.length > 0) {
|
|
81
|
+
await sendResponseFiles(ctx, collectedFiles);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function buildDisplayText(text, toolStatus) {
|
|
85
|
+
let display = text || "Thinking...";
|
|
86
|
+
if (toolStatus) {
|
|
87
|
+
display += `\n\n${toolStatus}`;
|
|
88
|
+
}
|
|
89
|
+
// Telegram message limit is 4096
|
|
90
|
+
if (display.length > 4000) {
|
|
91
|
+
display = display.slice(-4000) + "\n\n(streaming...)";
|
|
92
|
+
}
|
|
93
|
+
return display;
|
|
94
|
+
}
|
|
95
|
+
async function safeEditMessage(ctx, chatId, messageId, text, html = false) {
|
|
96
|
+
try {
|
|
97
|
+
await ctx.api.editMessageText(chatId, messageId, text, html ? { parse_mode: "HTML" } : undefined);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Ignore "message is not modified" and rate limit errors
|
|
101
|
+
const desc = err?.description ?? err?.message ?? "";
|
|
102
|
+
if (desc.includes("message is not modified") ||
|
|
103
|
+
desc.includes("MESSAGE_NOT_MODIFIED")) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (desc.includes("Too Many Requests") || desc.includes("retry_after")) {
|
|
107
|
+
const retryAfter = err?.parameters?.retry_after ?? 3;
|
|
108
|
+
await new Promise((r) => setTimeout(r, retryAfter * 1000));
|
|
109
|
+
try {
|
|
110
|
+
await ctx.api.editMessageText(chatId, messageId, text, html ? { parse_mode: "HTML" } : undefined);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Give up
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function sendFinalResponse(ctx, chatId, placeholderMsgId, text) {
|
|
119
|
+
// Delete placeholder
|
|
120
|
+
try {
|
|
121
|
+
await ctx.api.deleteMessage(chatId, placeholderMsgId);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// May fail if message was already deleted
|
|
125
|
+
}
|
|
126
|
+
await sendFinalText(ctx, text, chatId);
|
|
127
|
+
}
|
|
128
|
+
async function sendFinalText(ctx, text, chatId) {
|
|
129
|
+
const targetChatId = chatId ?? ctx.chat?.id;
|
|
130
|
+
if (!targetChatId)
|
|
131
|
+
return;
|
|
132
|
+
// Send as file if very large
|
|
133
|
+
if (text.length > 20000) {
|
|
134
|
+
const buffer = Buffer.from(text, "utf-8");
|
|
135
|
+
await ctx.api.sendDocument(targetChatId, new InputFile(buffer, "response.txt"));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Chunk and send
|
|
139
|
+
const chunks = chunkMessage(text);
|
|
140
|
+
for (const chunk of chunks) {
|
|
141
|
+
try {
|
|
142
|
+
await ctx.api.sendMessage(targetChatId, chunk, { parse_mode: "Markdown" });
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
await ctx.api.sendMessage(targetChatId, chunk);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/utils/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface TranscriptionResult {
|
|
2
|
+
text: string;
|
|
3
|
+
provider: "openai" | "groq" | "assemblyai";
|
|
4
|
+
}
|
|
5
|
+
export declare function isSttAvailable(): boolean;
|
|
6
|
+
export declare function getSttProvider(): string | null;
|
|
7
|
+
export declare function transcribeAudio(buffer: Buffer, filename: string): Promise<TranscriptionResult>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export function isSttAvailable() {
|
|
2
|
+
return !!(process.env.OPENAI_API_KEY ||
|
|
3
|
+
process.env.GROQ_API_KEY ||
|
|
4
|
+
process.env.ASSEMBLYAI_API_KEY);
|
|
5
|
+
}
|
|
6
|
+
export function getSttProvider() {
|
|
7
|
+
const provider = (process.env.STT_PROVIDER ?? "auto");
|
|
8
|
+
if (provider !== "auto") {
|
|
9
|
+
const keyMap = {
|
|
10
|
+
groq: process.env.GROQ_API_KEY,
|
|
11
|
+
openai: process.env.OPENAI_API_KEY,
|
|
12
|
+
assemblyai: process.env.ASSEMBLYAI_API_KEY,
|
|
13
|
+
};
|
|
14
|
+
return keyMap[provider] ? provider : null;
|
|
15
|
+
}
|
|
16
|
+
// Auto-detect: cheapest first (Groq > AssemblyAI > OpenAI)
|
|
17
|
+
if (process.env.GROQ_API_KEY)
|
|
18
|
+
return "groq";
|
|
19
|
+
if (process.env.ASSEMBLYAI_API_KEY)
|
|
20
|
+
return "assemblyai";
|
|
21
|
+
if (process.env.OPENAI_API_KEY)
|
|
22
|
+
return "openai";
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
export async function transcribeAudio(buffer, filename) {
|
|
26
|
+
const provider = getSttProvider();
|
|
27
|
+
if (!provider) {
|
|
28
|
+
throw new Error("No STT provider configured. Set GROQ_API_KEY, OPENAI_API_KEY, or ASSEMBLYAI_API_KEY.");
|
|
29
|
+
}
|
|
30
|
+
switch (provider) {
|
|
31
|
+
case "groq":
|
|
32
|
+
return transcribeWithGroq(buffer, filename);
|
|
33
|
+
case "openai":
|
|
34
|
+
return transcribeWithOpenAI(buffer, filename);
|
|
35
|
+
case "assemblyai":
|
|
36
|
+
return transcribeWithAssemblyAI(buffer);
|
|
37
|
+
default:
|
|
38
|
+
throw new Error(`Unknown STT provider: ${provider}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function transcribeWithOpenAI(buffer, filename) {
|
|
42
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
43
|
+
const model = process.env.OPENAI_STT_MODEL ?? "gpt-4o-mini-transcribe";
|
|
44
|
+
const formData = new FormData();
|
|
45
|
+
formData.append("file", new Blob([new Uint8Array(buffer)]), filename);
|
|
46
|
+
formData.append("model", model);
|
|
47
|
+
const response = await fetch("https://api.openai.com/v1/audio/transcriptions", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
50
|
+
body: formData,
|
|
51
|
+
});
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`Voice transcription failed (OpenAI, HTTP ${response.status})`);
|
|
54
|
+
}
|
|
55
|
+
const data = (await response.json());
|
|
56
|
+
return { text: data.text, provider: "openai" };
|
|
57
|
+
}
|
|
58
|
+
async function transcribeWithGroq(buffer, filename) {
|
|
59
|
+
const apiKey = process.env.GROQ_API_KEY;
|
|
60
|
+
const model = process.env.GROQ_STT_MODEL ?? "whisper-large-v3-turbo";
|
|
61
|
+
const formData = new FormData();
|
|
62
|
+
formData.append("file", new Blob([new Uint8Array(buffer)]), filename);
|
|
63
|
+
formData.append("model", model);
|
|
64
|
+
const response = await fetch("https://api.groq.com/openai/v1/audio/transcriptions", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
67
|
+
body: formData,
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`Voice transcription failed (Groq, HTTP ${response.status})`);
|
|
71
|
+
}
|
|
72
|
+
const data = (await response.json());
|
|
73
|
+
return { text: data.text, provider: "groq" };
|
|
74
|
+
}
|
|
75
|
+
async function transcribeWithAssemblyAI(buffer) {
|
|
76
|
+
const apiKey = process.env.ASSEMBLYAI_API_KEY;
|
|
77
|
+
// Step 1: Upload audio
|
|
78
|
+
const uploadResp = await fetch("https://api.assemblyai.com/v2/upload", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: apiKey,
|
|
82
|
+
"Content-Type": "application/octet-stream",
|
|
83
|
+
},
|
|
84
|
+
body: new Uint8Array(buffer),
|
|
85
|
+
});
|
|
86
|
+
if (!uploadResp.ok) {
|
|
87
|
+
throw new Error(`Voice transcription failed (AssemblyAI upload, HTTP ${uploadResp.status})`);
|
|
88
|
+
}
|
|
89
|
+
const { upload_url } = (await uploadResp.json());
|
|
90
|
+
// Step 2: Create transcript
|
|
91
|
+
const transcriptResp = await fetch("https://api.assemblyai.com/v2/transcript", {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: apiKey,
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify({ audio_url: upload_url }),
|
|
98
|
+
});
|
|
99
|
+
if (!transcriptResp.ok) {
|
|
100
|
+
throw new Error(`Voice transcription failed (AssemblyAI, HTTP ${transcriptResp.status})`);
|
|
101
|
+
}
|
|
102
|
+
const { id } = (await transcriptResp.json());
|
|
103
|
+
// Step 3: Poll for completion (max 60s)
|
|
104
|
+
const deadline = Date.now() + 60_000;
|
|
105
|
+
while (Date.now() < deadline) {
|
|
106
|
+
const pollResp = await fetch(`https://api.assemblyai.com/v2/transcript/${id}`, { headers: { Authorization: apiKey } });
|
|
107
|
+
const result = (await pollResp.json());
|
|
108
|
+
if (result.status === "completed") {
|
|
109
|
+
return { text: result.text ?? "", provider: "assemblyai" };
|
|
110
|
+
}
|
|
111
|
+
if (result.status === "error") {
|
|
112
|
+
throw new Error("Voice transcription failed (AssemblyAI processing error)");
|
|
113
|
+
}
|
|
114
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
115
|
+
}
|
|
116
|
+
throw new Error("AssemblyAI transcription timed out (60s)");
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=stt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stt.js","sourceRoot":"","sources":["../../src/utils/stt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function getSystemPrompt(): string;
|
|
2
|
+
export declare function loadSystemPrompt(): string;
|
|
3
|
+
export declare function reloadSystemPrompt(): string;
|
|
4
|
+
export declare function isUsingCustomPrompt(): boolean;
|
|
5
|
+
export declare function unwatchSystemPrompt(): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, existsSync, watchFile, unwatchFile } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
const DEFAULT_SYSTEM_PROMPT = `You are a coding assistant accessed through a Telegram bot. Your responses are delivered as Telegram messages, so keep them concise and under 4000 characters when possible — use Markdown formatting (bold, inline code, code blocks) for readability. Focus on actionable, practical answers: provide code, commands, or direct solutions rather than lengthy explanations. Messages may originate from voice transcriptions, so interpret the user's intent generously even if the wording is imprecise or contains transcription artifacts.`;
|
|
4
|
+
let cachedPrompt = null;
|
|
5
|
+
let watchedPath = null;
|
|
6
|
+
export function getSystemPrompt() {
|
|
7
|
+
if (cachedPrompt !== null)
|
|
8
|
+
return cachedPrompt;
|
|
9
|
+
return loadSystemPrompt();
|
|
10
|
+
}
|
|
11
|
+
export function loadSystemPrompt() {
|
|
12
|
+
const filePath = resolvePromptPath();
|
|
13
|
+
if (filePath && existsSync(filePath)) {
|
|
14
|
+
try {
|
|
15
|
+
const fileContent = readFileSync(filePath, "utf-8").trim();
|
|
16
|
+
if (fileContent) {
|
|
17
|
+
cachedPrompt = fileContent;
|
|
18
|
+
if (watchedPath !== filePath) {
|
|
19
|
+
if (watchedPath)
|
|
20
|
+
unwatchFile(watchedPath);
|
|
21
|
+
watchFile(filePath, { interval: 5000 }, () => {
|
|
22
|
+
cachedPrompt = null;
|
|
23
|
+
});
|
|
24
|
+
watchedPath = filePath;
|
|
25
|
+
}
|
|
26
|
+
return cachedPrompt;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Fall through to default
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
cachedPrompt = DEFAULT_SYSTEM_PROMPT;
|
|
34
|
+
return cachedPrompt;
|
|
35
|
+
}
|
|
36
|
+
export function reloadSystemPrompt() {
|
|
37
|
+
cachedPrompt = null;
|
|
38
|
+
return loadSystemPrompt();
|
|
39
|
+
}
|
|
40
|
+
export function isUsingCustomPrompt() {
|
|
41
|
+
const filePath = resolvePromptPath();
|
|
42
|
+
if (!filePath || !existsSync(filePath))
|
|
43
|
+
return false;
|
|
44
|
+
try {
|
|
45
|
+
const content = readFileSync(filePath, "utf-8").trim();
|
|
46
|
+
return content.length > 0;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function unwatchSystemPrompt() {
|
|
53
|
+
if (watchedPath) {
|
|
54
|
+
unwatchFile(watchedPath);
|
|
55
|
+
watchedPath = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function resolvePromptPath() {
|
|
59
|
+
const envPath = process.env.SYSTEM_PROMPT_FILE;
|
|
60
|
+
if (envPath)
|
|
61
|
+
return resolve(envPath);
|
|
62
|
+
return resolve("skill.md");
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=system-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/utils/system-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,MAAM,qBAAqB,GAAG,ihBAAihB,CAAC;AAEhjB,IAAI,YAAY,GAAkB,IAAI,CAAC;AACvC,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,YAAY,CAAC;IAC/C,OAAO,gBAAgB,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IACrC,IAAI,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3D,IAAI,WAAW,EAAE,CAAC;gBAChB,YAAY,GAAG,WAAW,CAAC;gBAC3B,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;oBAC7B,IAAI,WAAW;wBAAE,WAAW,CAAC,WAAW,CAAC,CAAC;oBAC1C,SAAS,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE;wBAC3C,YAAY,GAAG,IAAI,CAAC;oBACtB,CAAC,CAAC,CAAC;oBACH,WAAW,GAAG,QAAQ,CAAC;gBACzB,CAAC;gBACD,OAAO,YAAY,CAAC;YACtB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,0BAA0B;QAC5B,CAAC;IACH,CAAC;IACD,YAAY,GAAG,qBAAqB,CAAC;IACrC,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,YAAY,GAAG,IAAI,CAAC;IACpB,OAAO,gBAAgB,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IACrC,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,IAAI,WAAW,EAAE,CAAC;QAChB,WAAW,CAAC,WAAW,CAAC,CAAC;QACzB,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC/C,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const DEFAULT_PROMPT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
2
|
+
export function getPromptTimeout() {
|
|
3
|
+
const env = process.env.PROMPT_TIMEOUT_MS;
|
|
4
|
+
return env ? Number(env) : DEFAULT_PROMPT_TIMEOUT;
|
|
5
|
+
}
|
|
6
|
+
export async function withTimeout(promise, ms, label = "Operation") {
|
|
7
|
+
let timer;
|
|
8
|
+
const timeout = new Promise((_, reject) => {
|
|
9
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${Math.round(ms / 1000)}s`)), ms);
|
|
10
|
+
});
|
|
11
|
+
try {
|
|
12
|
+
return await Promise.race([promise, timeout]);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/utils/timeout.ts"],"names":[],"mappings":"AAAA,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AAE1D,MAAM,UAAU,gBAAgB;IAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC1C,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAmB,EACnB,EAAU,EACV,QAAgB,WAAW;IAE3B,IAAI,KAAoC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QAC/C,KAAK,GAAG,UAAU,CAChB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAC7E,EAAE,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAChD,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAM,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|