botinabox 2.7.11 → 2.8.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/dist/channel-DziSPayj.d.ts +73 -0
- package/dist/channels/discord/index.d.ts +1 -1
- package/dist/channels/slack/index.d.ts +75 -4
- package/dist/channels/slack/index.js +136 -4
- package/dist/channels/webhook/index.d.ts +1 -1
- package/dist/chat-pipeline-BGgmH_ap.d.ts +655 -0
- package/dist/chunk-OEMM2LEA.js +223 -0
- package/dist/inbound-5FKJBWPL.js +11 -0
- package/dist/index.d.ts +6 -6
- package/dist/provider-BHkqkSdq.d.ts +89 -0
- package/dist/providers/anthropic/index.d.ts +1 -1
- package/dist/providers/ollama/index.d.ts +1 -1
- package/dist/providers/openai/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Channel adapter types — Story 1.5 / 4.1 */
|
|
2
|
+
type ChatType = "direct" | "group" | "channel";
|
|
3
|
+
type FormattingMode = "markdown" | "mrkdwn" | "html" | "plain";
|
|
4
|
+
interface ChannelCapabilities {
|
|
5
|
+
chatTypes: ChatType[];
|
|
6
|
+
threads: boolean;
|
|
7
|
+
reactions: boolean;
|
|
8
|
+
editing: boolean;
|
|
9
|
+
media: boolean;
|
|
10
|
+
polls: boolean;
|
|
11
|
+
maxTextLength: number;
|
|
12
|
+
formattingMode: FormattingMode;
|
|
13
|
+
}
|
|
14
|
+
interface ChannelMeta {
|
|
15
|
+
displayName: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
homepage?: string;
|
|
18
|
+
}
|
|
19
|
+
interface InboundMessage {
|
|
20
|
+
id: string;
|
|
21
|
+
channel: string;
|
|
22
|
+
account?: string;
|
|
23
|
+
from: string;
|
|
24
|
+
userId?: string;
|
|
25
|
+
body: string;
|
|
26
|
+
threadId?: string;
|
|
27
|
+
replyToId?: string;
|
|
28
|
+
attachments?: Attachment[];
|
|
29
|
+
receivedAt: string;
|
|
30
|
+
raw?: unknown;
|
|
31
|
+
}
|
|
32
|
+
type AttachmentMediaType = "image" | "video" | "audio" | "pdf" | "doc" | "excel" | "presentation" | "html" | "link" | "misc";
|
|
33
|
+
interface Attachment {
|
|
34
|
+
type: AttachmentMediaType;
|
|
35
|
+
url?: string;
|
|
36
|
+
mimeType?: string;
|
|
37
|
+
filename?: string;
|
|
38
|
+
size?: number;
|
|
39
|
+
}
|
|
40
|
+
interface OutboundPayload {
|
|
41
|
+
text: string;
|
|
42
|
+
threadId?: string;
|
|
43
|
+
replyToId?: string;
|
|
44
|
+
attachments?: Attachment[];
|
|
45
|
+
}
|
|
46
|
+
interface SendResult {
|
|
47
|
+
success: boolean;
|
|
48
|
+
messageId?: string;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
interface HealthStatus {
|
|
52
|
+
ok: boolean;
|
|
53
|
+
latencyMs?: number;
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
type ChannelConfig = Record<string, unknown>;
|
|
57
|
+
interface ChannelAdapter {
|
|
58
|
+
/** Unique identifier for this adapter instance */
|
|
59
|
+
id: string;
|
|
60
|
+
meta: ChannelMeta;
|
|
61
|
+
capabilities: ChannelCapabilities;
|
|
62
|
+
connect(config: ChannelConfig): Promise<void>;
|
|
63
|
+
disconnect(): Promise<void>;
|
|
64
|
+
healthCheck(): Promise<HealthStatus>;
|
|
65
|
+
send(target: {
|
|
66
|
+
peerId: string;
|
|
67
|
+
threadId?: string;
|
|
68
|
+
}, payload: OutboundPayload): Promise<SendResult>;
|
|
69
|
+
/** Called when a message arrives — set by the framework */
|
|
70
|
+
onMessage?: (message: InboundMessage) => Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type { Attachment as A, ChannelAdapter as C, FormattingMode as F, HealthStatus as H, InboundMessage as I, OutboundPayload as O, SendResult as S, AttachmentMediaType as a, ChannelCapabilities as b, ChannelConfig as c, ChannelMeta as d, ChatType as e };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ChannelAdapter,
|
|
1
|
+
import { C as ChannelAdapter, d as ChannelMeta, b as ChannelCapabilities, I as InboundMessage, c as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-DziSPayj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* DiscordAdapter — ChannelAdapter implementation for Discord.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as ChannelAdapter,
|
|
2
|
-
import { H as HookBus, c as ChatPipeline } from '../../chat-pipeline-
|
|
1
|
+
import { C as ChannelAdapter, d as ChannelMeta, b as ChannelCapabilities, I as InboundMessage, c as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult, A as Attachment, a as AttachmentMediaType } from '../../channel-DziSPayj.js';
|
|
2
|
+
import { H as HookBus, c as ChatPipeline } from '../../chat-pipeline-BGgmH_ap.js';
|
|
3
3
|
import 'better-sqlite3';
|
|
4
|
-
import '../../provider-
|
|
4
|
+
import '../../provider-BHkqkSdq.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* SlackAdapter — ChannelAdapter implementation for Slack.
|
|
@@ -162,6 +162,27 @@ interface SlackConfig {
|
|
|
162
162
|
signingSecret?: string;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* An AttachmentEnricher downloads an attachment and extracts its textual content.
|
|
167
|
+
*
|
|
168
|
+
* Returns the extracted text on success, or `null` on failure. The framework
|
|
169
|
+
* surfaces the filename/URL as a fallback when the enricher returns null.
|
|
170
|
+
*
|
|
171
|
+
* @param attachment - The attachment metadata (type, url, filename, etc.)
|
|
172
|
+
* @param botToken - Slack bot token, required for authenticated downloads from url_private
|
|
173
|
+
*/
|
|
174
|
+
type AttachmentEnricher = (attachment: Attachment, botToken: string) => Promise<string | null>;
|
|
175
|
+
/**
|
|
176
|
+
* A map from attachment type to the enricher that handles it.
|
|
177
|
+
* Types without an entry fall through to the framework's default behavior
|
|
178
|
+
* (surfacing filename + URL in the message body).
|
|
179
|
+
*/
|
|
180
|
+
type AttachmentEnricherMap = Partial<Record<AttachmentMediaType, AttachmentEnricher>>;
|
|
181
|
+
/** Internal: the result of running enrichAttachments on a message. */
|
|
182
|
+
interface EnrichedMessage extends InboundMessage {
|
|
183
|
+
body: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
165
186
|
/**
|
|
166
187
|
* SlackBoltAdapter — real Bolt Socket Mode integration for botinabox.
|
|
167
188
|
*
|
|
@@ -181,6 +202,8 @@ interface SlackBoltAdapterConfig {
|
|
|
181
202
|
appToken: string;
|
|
182
203
|
hooks: HookBus;
|
|
183
204
|
pipeline: ChatPipeline;
|
|
205
|
+
/** Optional per-type attachment enrichers. */
|
|
206
|
+
attachmentEnrichers?: AttachmentEnricherMap;
|
|
184
207
|
}
|
|
185
208
|
declare class SlackBoltAdapter {
|
|
186
209
|
private app;
|
|
@@ -190,4 +213,52 @@ declare class SlackBoltAdapter {
|
|
|
190
213
|
stop(): Promise<void>;
|
|
191
214
|
}
|
|
192
215
|
|
|
193
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Run enrichers over each attachment on an InboundMessage and append extracted
|
|
218
|
+
* content to the message body.
|
|
219
|
+
*
|
|
220
|
+
* Enrichers are run sequentially. If an enricher throws or returns null, the
|
|
221
|
+
* attachment is surfaced as `[Attached: <filename>]` with no content — the
|
|
222
|
+
* LLM still sees that a file was present.
|
|
223
|
+
*
|
|
224
|
+
* Extracted text is appended to the body in this format:
|
|
225
|
+
*
|
|
226
|
+
* <original body>
|
|
227
|
+
*
|
|
228
|
+
* [Attached: invoice.pdf]
|
|
229
|
+
* <extracted content>
|
|
230
|
+
*/
|
|
231
|
+
declare function enrichAttachments(msg: InboundMessage, botToken: string, enrichers: AttachmentEnricherMap): Promise<InboundMessage>;
|
|
232
|
+
|
|
233
|
+
interface ImageEnricherConfig {
|
|
234
|
+
/** Anthropic API key for vision calls. */
|
|
235
|
+
apiKey: string;
|
|
236
|
+
/** Model to use. Defaults to claude-sonnet-4-6. */
|
|
237
|
+
model?: string;
|
|
238
|
+
/** Max tokens for the description. Defaults to 1024. */
|
|
239
|
+
maxTokens?: number;
|
|
240
|
+
/** Prompt used for image description. */
|
|
241
|
+
prompt?: string;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Build an enricher that sends Slack-hosted images to Claude's vision API
|
|
245
|
+
* and returns a text description.
|
|
246
|
+
*
|
|
247
|
+
* Consumer must provide an Anthropic API key. The Anthropic SDK is a peer
|
|
248
|
+
* dependency of botinabox — the consumer must have it installed.
|
|
249
|
+
*/
|
|
250
|
+
declare function createImageEnricher(config: ImageEnricherConfig): AttachmentEnricher;
|
|
251
|
+
|
|
252
|
+
interface PdfEnricherConfig {
|
|
253
|
+
apiKey: string;
|
|
254
|
+
model?: string;
|
|
255
|
+
maxTokens?: number;
|
|
256
|
+
prompt?: string;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Build an enricher that sends Slack-hosted PDFs to Claude's document API
|
|
260
|
+
* and returns a text summary. Requires Claude 3.5+.
|
|
261
|
+
*/
|
|
262
|
+
declare function createPdfEnricher(config: PdfEnricherConfig): AttachmentEnricher;
|
|
263
|
+
|
|
264
|
+
export { type AttachmentEnricher, type AttachmentEnricherMap, type BoltClient, type EnrichedMessage, type ImageEnricherConfig, type PdfEnricherConfig, SlackAdapter, SlackBoltAdapter, type SlackBoltAdapterConfig, type SlackConfig, type SlackEvent, type SlackFile, type TranscribeOptions, type TranscribeResult, createImageEnricher, createPdfEnricher, createSlackAdapter as default, downloadAudio, enrichAttachments, enrichVoiceMessage, extractVoiceTranscript, formatForSlack, parseSlackEvent, transcribeAudio };
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
extractVoiceTranscript,
|
|
5
5
|
parseSlackEvent,
|
|
6
6
|
transcribeAudio
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-OEMM2LEA.js";
|
|
8
8
|
import {
|
|
9
9
|
chunkText
|
|
10
10
|
} from "../../chunk-ZTZFPTOQ.js";
|
|
@@ -71,8 +71,8 @@ var SlackAdapter = class {
|
|
|
71
71
|
/** Simulate receiving an inbound message (for testing/webhooks). */
|
|
72
72
|
async receive(event) {
|
|
73
73
|
if (this.onMessage) {
|
|
74
|
-
const { parseSlackEvent: parseSlackEvent2 } = await import("../../inbound-
|
|
75
|
-
const { enrichVoiceMessage: enrichVoiceMessage2 } = await import("../../inbound-
|
|
74
|
+
const { parseSlackEvent: parseSlackEvent2 } = await import("../../inbound-5FKJBWPL.js");
|
|
75
|
+
const { enrichVoiceMessage: enrichVoiceMessage2 } = await import("../../inbound-5FKJBWPL.js");
|
|
76
76
|
let msg = parseSlackEvent2(event);
|
|
77
77
|
if (msg.body.includes("[Voice message") && this.config?.botToken) {
|
|
78
78
|
msg = await enrichVoiceMessage2(msg, this.config.botToken);
|
|
@@ -85,6 +85,33 @@ function createSlackAdapter(client) {
|
|
|
85
85
|
return new SlackAdapter(client);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// src/channels/slack/enrichers/enrich.ts
|
|
89
|
+
async function enrichAttachments(msg, botToken, enrichers) {
|
|
90
|
+
if (!msg.attachments?.length) return msg;
|
|
91
|
+
const parts = msg.body ? [msg.body] : [];
|
|
92
|
+
for (const att of msg.attachments) {
|
|
93
|
+
const enricher = enrichers[att.type];
|
|
94
|
+
const label = att.filename ?? att.url ?? att.type;
|
|
95
|
+
if (!enricher) {
|
|
96
|
+
parts.push(`[Attached: ${label}]`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let extracted = null;
|
|
100
|
+
try {
|
|
101
|
+
extracted = await enricher(att, botToken);
|
|
102
|
+
} catch {
|
|
103
|
+
extracted = null;
|
|
104
|
+
}
|
|
105
|
+
if (extracted) {
|
|
106
|
+
parts.push(`[Attached: ${label}]
|
|
107
|
+
${extracted}`);
|
|
108
|
+
} else {
|
|
109
|
+
parts.push(`[Attached: ${label}]`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { ...msg, body: parts.join("\n\n") };
|
|
113
|
+
}
|
|
114
|
+
|
|
88
115
|
// src/channels/slack/bolt-adapter.ts
|
|
89
116
|
var SlackBoltAdapter = class {
|
|
90
117
|
app = null;
|
|
@@ -95,7 +122,7 @@ var SlackBoltAdapter = class {
|
|
|
95
122
|
async start() {
|
|
96
123
|
const boltModule = "@slack/bolt";
|
|
97
124
|
const bolt = await import(boltModule);
|
|
98
|
-
const { enrichVoiceMessage: enrichVoiceMessage2 } = await import("../../inbound-
|
|
125
|
+
const { enrichVoiceMessage: enrichVoiceMessage2 } = await import("../../inbound-5FKJBWPL.js");
|
|
99
126
|
const boltApp = new bolt.App({
|
|
100
127
|
token: this.config.botToken,
|
|
101
128
|
appToken: this.config.appToken,
|
|
@@ -113,6 +140,9 @@ var SlackBoltAdapter = class {
|
|
|
113
140
|
if (inbound.body.includes("[Voice message \u2014 no transcript available]")) {
|
|
114
141
|
inbound = await enrichVoiceMessage2(inbound, botToken);
|
|
115
142
|
}
|
|
143
|
+
if (inbound.attachments?.length && this.config.attachmentEnrichers) {
|
|
144
|
+
inbound = await enrichAttachments(inbound, botToken, this.config.attachmentEnrichers);
|
|
145
|
+
}
|
|
116
146
|
await hooks.emit("message.inbound", inbound);
|
|
117
147
|
});
|
|
118
148
|
hooks.register("response.ready", async (ctx) => {
|
|
@@ -164,11 +194,113 @@ var SlackBoltAdapter = class {
|
|
|
164
194
|
}
|
|
165
195
|
}
|
|
166
196
|
};
|
|
197
|
+
|
|
198
|
+
// src/channels/slack/enrichers/image-enricher.ts
|
|
199
|
+
var DEFAULT_PROMPT = "Describe this image in detail. Include any visible text (OCR), objects, people, layout, and context. Be thorough \u2014 the description will be used as input to another agent that cannot see the image.";
|
|
200
|
+
async function downloadAsBase64(urlPrivate, botToken) {
|
|
201
|
+
try {
|
|
202
|
+
const response = await fetch(urlPrivate, {
|
|
203
|
+
headers: { Authorization: `Bearer ${botToken}` },
|
|
204
|
+
signal: AbortSignal.timeout(3e4)
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) return null;
|
|
207
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
208
|
+
return buffer.toString("base64");
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function createImageEnricher(config) {
|
|
214
|
+
const { apiKey, model = "claude-sonnet-4-6", maxTokens = 1024, prompt = DEFAULT_PROMPT } = config;
|
|
215
|
+
return async (att, botToken) => {
|
|
216
|
+
if (!att.url) return null;
|
|
217
|
+
const mediaType = att.mimeType ?? "image/jpeg";
|
|
218
|
+
const base64 = await downloadAsBase64(att.url, botToken);
|
|
219
|
+
if (!base64) return null;
|
|
220
|
+
const anthropicModule = "@anthropic-ai/sdk";
|
|
221
|
+
const sdk = await import(anthropicModule);
|
|
222
|
+
const client = new sdk.default({ apiKey });
|
|
223
|
+
try {
|
|
224
|
+
const message = await client.messages.create({
|
|
225
|
+
model,
|
|
226
|
+
max_tokens: maxTokens,
|
|
227
|
+
messages: [
|
|
228
|
+
{
|
|
229
|
+
role: "user",
|
|
230
|
+
content: [
|
|
231
|
+
{
|
|
232
|
+
type: "image",
|
|
233
|
+
source: { type: "base64", media_type: mediaType, data: base64 }
|
|
234
|
+
},
|
|
235
|
+
{ type: "text", text: prompt }
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
});
|
|
240
|
+
const textBlock = message.content.find((c) => c.type === "text");
|
|
241
|
+
return textBlock?.text ?? null;
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/channels/slack/enrichers/pdf-enricher.ts
|
|
249
|
+
var DEFAULT_PROMPT2 = "Summarize this PDF in detail. Include all key facts, numbers, dates, names, and any structured content (tables, lists). The summary will be used as input to another agent that cannot see the PDF.";
|
|
250
|
+
async function downloadAsBase642(urlPrivate, botToken) {
|
|
251
|
+
try {
|
|
252
|
+
const response = await fetch(urlPrivate, {
|
|
253
|
+
headers: { Authorization: `Bearer ${botToken}` },
|
|
254
|
+
signal: AbortSignal.timeout(6e4)
|
|
255
|
+
});
|
|
256
|
+
if (!response.ok) return null;
|
|
257
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
258
|
+
return buffer.toString("base64");
|
|
259
|
+
} catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function createPdfEnricher(config) {
|
|
264
|
+
const { apiKey, model = "claude-sonnet-4-6", maxTokens = 4096, prompt = DEFAULT_PROMPT2 } = config;
|
|
265
|
+
return async (att, botToken) => {
|
|
266
|
+
if (!att.url) return null;
|
|
267
|
+
const base64 = await downloadAsBase642(att.url, botToken);
|
|
268
|
+
if (!base64) return null;
|
|
269
|
+
const anthropicModule = "@anthropic-ai/sdk";
|
|
270
|
+
const sdk = await import(anthropicModule);
|
|
271
|
+
const client = new sdk.default({ apiKey });
|
|
272
|
+
try {
|
|
273
|
+
const message = await client.messages.create({
|
|
274
|
+
model,
|
|
275
|
+
max_tokens: maxTokens,
|
|
276
|
+
messages: [
|
|
277
|
+
{
|
|
278
|
+
role: "user",
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: "document",
|
|
282
|
+
source: { type: "base64", media_type: "application/pdf", data: base64 }
|
|
283
|
+
},
|
|
284
|
+
{ type: "text", text: prompt }
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
});
|
|
289
|
+
const textBlock = message.content.find((c) => c.type === "text");
|
|
290
|
+
return textBlock?.text ?? null;
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
167
296
|
export {
|
|
168
297
|
SlackAdapter,
|
|
169
298
|
SlackBoltAdapter,
|
|
299
|
+
createImageEnricher,
|
|
300
|
+
createPdfEnricher,
|
|
170
301
|
createSlackAdapter as default,
|
|
171
302
|
downloadAudio,
|
|
303
|
+
enrichAttachments,
|
|
172
304
|
enrichVoiceMessage,
|
|
173
305
|
extractVoiceTranscript,
|
|
174
306
|
formatForSlack,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ChannelAdapter,
|
|
1
|
+
import { C as ChannelAdapter, d as ChannelMeta, b as ChannelCapabilities, I as InboundMessage, c as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-DziSPayj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* WebhookAdapter — ChannelAdapter implementation for webhook-based channels.
|