botinabox 2.16.0 → 2.16.2

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.
@@ -0,0 +1,144 @@
1
+ // src/channels/slack/transcribe.ts
2
+ import { execFileSync } from "child_process";
3
+ import { writeFileSync, unlinkSync, mkdirSync } from "fs";
4
+ import { join } from "path";
5
+ import { randomUUID } from "crypto";
6
+ import os from "os";
7
+ import { createRequire } from "module";
8
+ var TEMP_DIR = join(os.tmpdir(), "botinabox-audio");
9
+ async function transcribeAudio(audioBuffer, filename, opts) {
10
+ let whisper;
11
+ try {
12
+ const require2 = createRequire(import.meta.url);
13
+ const mod = require2("whisper-node");
14
+ whisper = mod.whisper ?? mod.default ?? mod;
15
+ } catch {
16
+ console.warn("[botinabox] whisper-node not installed \u2014 voice transcription unavailable. Run: npm install whisper-node && npx whisper-node download");
17
+ return null;
18
+ }
19
+ try {
20
+ execFileSync("ffmpeg", ["-version"], { stdio: "ignore" });
21
+ } catch {
22
+ console.warn("[botinabox] ffmpeg not found \u2014 required for audio conversion. Install: brew install ffmpeg");
23
+ return null;
24
+ }
25
+ const id = randomUUID().slice(0, 8);
26
+ const ext = filename.split(".").pop() ?? "aac";
27
+ mkdirSync(TEMP_DIR, { recursive: true });
28
+ const inputPath = join(TEMP_DIR, `${id}.${ext}`);
29
+ const wavPath = join(TEMP_DIR, `${id}.wav`);
30
+ try {
31
+ writeFileSync(inputPath, audioBuffer);
32
+ execFileSync("ffmpeg", ["-y", "-i", inputPath, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", wavPath], {
33
+ stdio: "ignore",
34
+ timeout: 3e4
35
+ });
36
+ const segments = await whisper(wavPath, {
37
+ modelName: opts?.modelName ?? "base.en",
38
+ whisperOptions: {
39
+ language: opts?.language ?? "auto"
40
+ }
41
+ });
42
+ if (!segments || segments.length === 0) return null;
43
+ return segments.map((s) => s.speech).join(" ").trim();
44
+ } catch (err) {
45
+ console.error("[botinabox] Transcription failed:", err);
46
+ return null;
47
+ } finally {
48
+ try {
49
+ unlinkSync(inputPath);
50
+ } catch {
51
+ }
52
+ try {
53
+ unlinkSync(wavPath);
54
+ } catch {
55
+ }
56
+ }
57
+ }
58
+ async function downloadAudio(url, token) {
59
+ try {
60
+ const resp = await fetch(url, {
61
+ headers: { Authorization: `Bearer ${token}` }
62
+ });
63
+ if (!resp.ok) {
64
+ console.error(`[botinabox] Audio download failed: ${resp.status} ${resp.statusText}`);
65
+ return null;
66
+ }
67
+ return Buffer.from(await resp.arrayBuffer());
68
+ } catch (err) {
69
+ console.error("[botinabox] Audio download error:", err);
70
+ return null;
71
+ }
72
+ }
73
+
74
+ // src/channels/slack/inbound.ts
75
+ var AUDIO_TYPES = /* @__PURE__ */ new Set(["aac", "mp4", "m4a", "ogg", "webm", "mp3", "wav"]);
76
+ function extractVoiceTranscript(file) {
77
+ const isAudio = file.subtype === "slack_audio" || AUDIO_TYPES.has(file.filetype ?? "");
78
+ if (!isAudio) return null;
79
+ const transcript = file.transcription?.preview?.content ?? (typeof file.preview === "string" ? file.preview : null);
80
+ return transcript ?? null;
81
+ }
82
+ function parseSlackEvent(event) {
83
+ const id = event.client_msg_id ?? event.ts ?? event.event_ts ?? `slack-${Date.now()}`;
84
+ const channel = event.channel ?? "unknown";
85
+ const from = event.user ?? "unknown";
86
+ const threadId = event.thread_ts !== void 0 ? event.thread_ts : void 0;
87
+ const receivedAt = event.ts ? new Date(parseFloat(event.ts) * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
88
+ let body = event.text ?? "";
89
+ if (event.subtype === "file_share" && event.files?.length) {
90
+ for (const file of event.files) {
91
+ const transcript = extractVoiceTranscript(file);
92
+ if (transcript) {
93
+ body = body ? `${body}
94
+
95
+ [Voice message] ${transcript}` : `[Voice message] ${transcript}`;
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ if (event.subtype === "file_share" && event.files?.length && !body) {
101
+ const hasAudio = event.files.some(
102
+ (f) => f.subtype === "slack_audio" || AUDIO_TYPES.has(f.filetype ?? "")
103
+ );
104
+ if (hasAudio) {
105
+ body = "[Voice message \u2014 no transcript available]";
106
+ }
107
+ }
108
+ return {
109
+ id,
110
+ channel,
111
+ from,
112
+ body,
113
+ threadId,
114
+ receivedAt,
115
+ raw: event
116
+ };
117
+ }
118
+ async function enrichVoiceMessage(msg, botToken) {
119
+ if (!msg.body.includes("[Voice message \u2014 no transcript available]")) return msg;
120
+ const raw = msg.raw;
121
+ const files = raw?.files;
122
+ if (!files?.length) return msg;
123
+ const audioFile = files.find(
124
+ (f) => f.subtype === "slack_audio" || AUDIO_TYPES.has(f.filetype ?? "")
125
+ );
126
+ if (!audioFile?.url_private) return msg;
127
+ const buffer = await downloadAudio(audioFile.url_private, botToken);
128
+ if (!buffer) return msg;
129
+ const filename = audioFile.name ?? `voice.${audioFile.filetype ?? "aac"}`;
130
+ const transcript = await transcribeAudio(buffer, filename);
131
+ if (!transcript) return msg;
132
+ return {
133
+ ...msg,
134
+ body: `[Voice message] ${transcript}`
135
+ };
136
+ }
137
+
138
+ export {
139
+ transcribeAudio,
140
+ downloadAudio,
141
+ extractVoiceTranscript,
142
+ parseSlackEvent,
143
+ enrichVoiceMessage
144
+ };
package/dist/cli.js CHANGED
File without changes
@@ -0,0 +1,79 @@
1
+ /** Connector types — generic external service integrations. */
2
+ interface ConnectorMeta {
3
+ displayName: string;
4
+ /** Provider identifier, e.g. "google", "trello", "jira", "salesforce" */
5
+ provider: string;
6
+ /** Data type this connector handles, e.g. "email", "calendar", "board", "crm" */
7
+ dataType: string;
8
+ }
9
+ interface SyncOptions {
10
+ /** Only sync records after this ISO 8601 timestamp */
11
+ since?: string;
12
+ /** Provider-specific incremental sync token */
13
+ cursor?: string;
14
+ /** Maximum number of records to fetch */
15
+ limit?: number;
16
+ /** Provider-specific query filters */
17
+ filters?: Record<string, unknown>;
18
+ }
19
+ interface SyncResult<T = Record<string, unknown>> {
20
+ /** Typed records produced by the connector — consumer decides where to store */
21
+ records: T[];
22
+ /** Next incremental sync token (persist for future calls) */
23
+ cursor?: string;
24
+ /** Whether more records are available (pagination) */
25
+ hasMore: boolean;
26
+ /** Errors encountered during sync (non-fatal per-record failures) */
27
+ errors: Array<{
28
+ id?: string;
29
+ error: string;
30
+ }>;
31
+ }
32
+ interface PushResult {
33
+ success: boolean;
34
+ externalId?: string;
35
+ error?: string;
36
+ }
37
+ interface AuthResult {
38
+ success: boolean;
39
+ account?: string;
40
+ /** URL the user must visit to authorize (for OAuth flows) */
41
+ authUrl?: string;
42
+ error?: string;
43
+ }
44
+ type ConnectorConfig = Record<string, unknown>;
45
+ /**
46
+ * Generic connector interface for external service integrations.
47
+ *
48
+ * Connectors pull and optionally push data to/from external services
49
+ * (Gmail, Calendar, Trello, Jira, Salesforce, etc.). They produce
50
+ * typed records — the consuming application decides where to store them.
51
+ *
52
+ * @typeParam T - The record type this connector produces/consumes.
53
+ */
54
+ interface Connector<T = Record<string, unknown>> {
55
+ readonly id: string;
56
+ readonly meta: ConnectorMeta;
57
+ connect(config: ConnectorConfig): Promise<void>;
58
+ disconnect(): Promise<void>;
59
+ healthCheck(): Promise<{
60
+ ok: boolean;
61
+ account?: string;
62
+ error?: string;
63
+ }>;
64
+ /** Pull records from external source */
65
+ sync(options?: SyncOptions): Promise<SyncResult<T>>;
66
+ /** Push a record to external source (optional) */
67
+ push?(payload: T): Promise<PushResult>;
68
+ /**
69
+ * Run the authentication/authorization flow for this connector.
70
+ * For OAuth connectors, this generates the auth URL and exchanges the code for tokens.
71
+ *
72
+ * @param codeProvider - called with the auth URL; must return the authorization code.
73
+ * For CLI flows, this prints the URL and reads from stdin.
74
+ * For programmatic flows, the caller handles the redirect.
75
+ */
76
+ authenticate?(codeProvider: (authUrl: string) => Promise<string>): Promise<AuthResult>;
77
+ }
78
+
79
+ export type { AuthResult as A, ConnectorConfig as C, PushResult as P, SyncOptions as S, Connector as a, ConnectorMeta as b, SyncResult as c };
@@ -0,0 +1,11 @@
1
+ import {
2
+ enrichVoiceMessage,
3
+ extractVoiceTranscript,
4
+ parseSlackEvent
5
+ } from "./chunk-NNPCKR6G.js";
6
+ import "./chunk-3RG5ZIWI.js";
7
+ export {
8
+ enrichVoiceMessage,
9
+ extractVoiceTranscript,
10
+ parseSlackEvent
11
+ };
@@ -0,0 +1,75 @@
1
+ /** LLM provider types — Story 1.5 / 2.1 */
2
+ interface ToolDefinition {
3
+ name: string;
4
+ description: string;
5
+ parameters: Record<string, unknown>;
6
+ }
7
+ interface ChatMessage {
8
+ role: "user" | "assistant" | "system";
9
+ content: string | ContentBlock[];
10
+ }
11
+ type ContentBlock = {
12
+ type: "text";
13
+ text: string;
14
+ } | {
15
+ type: "tool_use";
16
+ id: string;
17
+ name: string;
18
+ input: unknown;
19
+ } | {
20
+ type: "tool_result";
21
+ tool_use_id: string;
22
+ content: string;
23
+ };
24
+ interface ChatParams {
25
+ messages: ChatMessage[];
26
+ system?: string;
27
+ tools?: ToolDefinition[];
28
+ maxTokens?: number;
29
+ temperature?: number;
30
+ model: string;
31
+ abortSignal?: AbortSignal;
32
+ }
33
+ interface TokenUsage {
34
+ inputTokens: number;
35
+ outputTokens: number;
36
+ cacheReadTokens?: number;
37
+ cacheWriteTokens?: number;
38
+ }
39
+ interface ChatResult {
40
+ content: string;
41
+ toolUses?: ToolUse[];
42
+ usage: TokenUsage;
43
+ model: string;
44
+ stopReason: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence";
45
+ }
46
+ interface ToolUse {
47
+ id: string;
48
+ name: string;
49
+ input: unknown;
50
+ }
51
+ interface ModelInfo {
52
+ id: string;
53
+ displayName: string;
54
+ contextWindow: number;
55
+ maxOutputTokens: number;
56
+ capabilities: Array<"chat" | "tools" | "vision" | "streaming">;
57
+ /** Cost in micro-cents per 1M tokens */
58
+ inputCostPerMToken?: number;
59
+ outputCostPerMToken?: number;
60
+ }
61
+ interface ResolvedModel {
62
+ provider: string;
63
+ model: string;
64
+ }
65
+ interface LLMProvider {
66
+ id: string;
67
+ displayName: string;
68
+ models: ModelInfo[];
69
+ chat(params: ChatParams): Promise<ChatResult>;
70
+ chatStream(params: ChatParams): AsyncGenerator<string, ChatResult, unknown>;
71
+ /** Convert ToolDefinition[] to provider-native format */
72
+ serializeTools(tools: ToolDefinition[]): unknown;
73
+ }
74
+
75
+ export type { ChatMessage as C, LLMProvider as L, ModelInfo as M, ResolvedModel as R, TokenUsage as T, ChatParams as a, ChatResult as b, ContentBlock as c, ToolUse as d, ToolDefinition as e };
package/package.json CHANGED
@@ -1,100 +1,100 @@
1
- {
2
- "name": "botinabox",
3
- "version": "2.16.0",
4
- "description": "Bot in a Box — framework for building multi-agent bots",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
- },
13
- "./anthropic": {
14
- "import": "./dist/providers/anthropic/index.js",
15
- "types": "./dist/providers/anthropic/index.d.ts"
16
- },
17
- "./openai": {
18
- "import": "./dist/providers/openai/index.js",
19
- "types": "./dist/providers/openai/index.d.ts"
20
- },
21
- "./ollama": {
22
- "import": "./dist/providers/ollama/index.js",
23
- "types": "./dist/providers/ollama/index.d.ts"
24
- },
25
- "./slack": {
26
- "import": "./dist/channels/slack/index.js",
27
- "types": "./dist/channels/slack/index.d.ts"
28
- },
29
- "./discord": {
30
- "import": "./dist/channels/discord/index.js",
31
- "types": "./dist/channels/discord/index.d.ts"
32
- },
33
- "./webhook": {
34
- "import": "./dist/channels/webhook/index.js",
35
- "types": "./dist/channels/webhook/index.d.ts"
36
- },
37
- "./google": {
38
- "import": "./dist/connectors/google/index.js",
39
- "types": "./dist/connectors/google/index.d.ts"
40
- }
41
- },
42
- "bin": {
43
- "botinabox": "./bin/botinabox.mjs"
44
- },
45
- "files": [
46
- "dist",
47
- "bin"
48
- ],
49
- "engines": {
50
- "node": ">=18"
51
- },
52
- "scripts": {
53
- "build": "tsup && tsc --emitDeclarationOnly",
54
- "test": "vitest run",
55
- "typecheck": "tsc --noEmit",
56
- "check-docs": "echo 'Documentation check passed'",
57
- "prepublishOnly": "npm run build && npm run typecheck && npm test"
58
- },
59
- "dependencies": {
60
- "@types/uuid": "^10.0.0",
61
- "ajv": "^8.17.1",
62
- "cron-parser": "^4.9.0",
63
- "latticesql": "^1.13.4",
64
- "uuid": "^13.0.0",
65
- "yaml": "^2.7.0"
66
- },
67
- "optionalDependencies": {
68
- "whisper-node": "^1.1.1"
69
- },
70
- "peerDependencies": {
71
- "@anthropic-ai/sdk": "^0.52.0",
72
- "googleapis": ">=140.0.0 <200.0.0",
73
- "openai": "^4.104.0"
74
- },
75
- "peerDependenciesMeta": {
76
- "@anthropic-ai/sdk": {
77
- "optional": true
78
- },
79
- "openai": {
80
- "optional": true
81
- },
82
- "googleapis": {
83
- "optional": true
84
- }
85
- },
86
- "repository": {
87
- "type": "git",
88
- "url": "https://github.com/automated-industries/botinabox.git"
89
- },
90
- "devDependencies": {
91
- "@anthropic-ai/sdk": "^0.52.0",
92
- "@types/better-sqlite3": "^7.6.12",
93
- "@types/node": "^22.10.0",
94
- "googleapis": "^171.4.0",
95
- "openai": "^4.104.0",
96
- "tsup": "^8.3.5",
97
- "typescript": "^5.7.2",
98
- "vitest": "^3.0.0"
99
- }
100
- }
1
+ {
2
+ "name": "botinabox",
3
+ "version": "2.16.2",
4
+ "description": "Bot in a Box — framework for building multi-agent bots",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./anthropic": {
14
+ "import": "./dist/providers/anthropic/index.js",
15
+ "types": "./dist/providers/anthropic/index.d.ts"
16
+ },
17
+ "./openai": {
18
+ "import": "./dist/providers/openai/index.js",
19
+ "types": "./dist/providers/openai/index.d.ts"
20
+ },
21
+ "./ollama": {
22
+ "import": "./dist/providers/ollama/index.js",
23
+ "types": "./dist/providers/ollama/index.d.ts"
24
+ },
25
+ "./slack": {
26
+ "import": "./dist/channels/slack/index.js",
27
+ "types": "./dist/channels/slack/index.d.ts"
28
+ },
29
+ "./discord": {
30
+ "import": "./dist/channels/discord/index.js",
31
+ "types": "./dist/channels/discord/index.d.ts"
32
+ },
33
+ "./webhook": {
34
+ "import": "./dist/channels/webhook/index.js",
35
+ "types": "./dist/channels/webhook/index.d.ts"
36
+ },
37
+ "./google": {
38
+ "import": "./dist/connectors/google/index.js",
39
+ "types": "./dist/connectors/google/index.d.ts"
40
+ }
41
+ },
42
+ "bin": {
43
+ "botinabox": "./bin/botinabox.mjs"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "bin"
48
+ ],
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup && tsc --emitDeclarationOnly",
54
+ "test": "vitest run",
55
+ "typecheck": "tsc --noEmit",
56
+ "check-docs": "echo 'Documentation check passed'",
57
+ "prepublishOnly": "npm run build && npm run typecheck && npm test"
58
+ },
59
+ "dependencies": {
60
+ "@types/uuid": "^10.0.0",
61
+ "ajv": "^8.17.1",
62
+ "cron-parser": "^4.9.0",
63
+ "latticesql": "^1.13.6",
64
+ "uuid": "^13.0.0",
65
+ "yaml": "^2.7.0"
66
+ },
67
+ "optionalDependencies": {
68
+ "whisper-node": "^1.1.1"
69
+ },
70
+ "peerDependencies": {
71
+ "@anthropic-ai/sdk": "^0.52.0",
72
+ "googleapis": ">=140.0.0 <200.0.0",
73
+ "openai": "^4.104.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "@anthropic-ai/sdk": {
77
+ "optional": true
78
+ },
79
+ "openai": {
80
+ "optional": true
81
+ },
82
+ "googleapis": {
83
+ "optional": true
84
+ }
85
+ },
86
+ "repository": {
87
+ "type": "git",
88
+ "url": "https://github.com/automated-industries/botinabox.git"
89
+ },
90
+ "devDependencies": {
91
+ "@anthropic-ai/sdk": "^0.52.0",
92
+ "@types/better-sqlite3": "^7.6.12",
93
+ "@types/node": "^22.10.0",
94
+ "googleapis": "^171.4.0",
95
+ "openai": "^4.104.0",
96
+ "tsup": "^8.3.5",
97
+ "typescript": "^5.7.2",
98
+ "vitest": "^3.0.0"
99
+ }
100
+ }