botinabox 2.8.0 → 2.9.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-CVm1AWUF.d.ts +82 -0
- package/dist/channels/discord/index.d.ts +2 -1
- package/dist/channels/slack/index.d.ts +43 -52
- package/dist/channels/slack/index.js +68 -102
- package/dist/channels/webhook/index.d.ts +2 -1
- package/dist/chat-pipeline-aBSj7a4E.d.ts +655 -0
- package/dist/connectors/google/index.d.ts +83 -1
- package/dist/connectors/google/index.js +100 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -3
- package/package.json +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { c as ContentBlock } from './provider-BHkqkSdq.js';
|
|
2
|
+
|
|
3
|
+
/** Channel adapter types — Story 1.5 / 4.1 */
|
|
4
|
+
|
|
5
|
+
type ChatType = "direct" | "group" | "channel";
|
|
6
|
+
type FormattingMode = "markdown" | "mrkdwn" | "html" | "plain";
|
|
7
|
+
interface ChannelCapabilities {
|
|
8
|
+
chatTypes: ChatType[];
|
|
9
|
+
threads: boolean;
|
|
10
|
+
reactions: boolean;
|
|
11
|
+
editing: boolean;
|
|
12
|
+
media: boolean;
|
|
13
|
+
polls: boolean;
|
|
14
|
+
maxTextLength: number;
|
|
15
|
+
formattingMode: FormattingMode;
|
|
16
|
+
}
|
|
17
|
+
interface ChannelMeta {
|
|
18
|
+
displayName: string;
|
|
19
|
+
icon?: string;
|
|
20
|
+
homepage?: string;
|
|
21
|
+
}
|
|
22
|
+
interface InboundMessage {
|
|
23
|
+
id: string;
|
|
24
|
+
channel: string;
|
|
25
|
+
account?: string;
|
|
26
|
+
from: string;
|
|
27
|
+
userId?: string;
|
|
28
|
+
body: string;
|
|
29
|
+
threadId?: string;
|
|
30
|
+
replyToId?: string;
|
|
31
|
+
attachments?: Attachment[];
|
|
32
|
+
/**
|
|
33
|
+
* Multimodal content blocks produced by attachment enrichers.
|
|
34
|
+
* When set, ChatPipeline builds a multimodal user message that passes
|
|
35
|
+
* these blocks through to the LLM provider alongside `body`.
|
|
36
|
+
*/
|
|
37
|
+
attachmentBlocks?: ContentBlock[];
|
|
38
|
+
receivedAt: string;
|
|
39
|
+
raw?: unknown;
|
|
40
|
+
}
|
|
41
|
+
type AttachmentMediaType = "image" | "video" | "audio" | "pdf" | "doc" | "excel" | "presentation" | "html" | "link" | "misc";
|
|
42
|
+
interface Attachment {
|
|
43
|
+
type: AttachmentMediaType;
|
|
44
|
+
url?: string;
|
|
45
|
+
mimeType?: string;
|
|
46
|
+
filename?: string;
|
|
47
|
+
size?: number;
|
|
48
|
+
}
|
|
49
|
+
interface OutboundPayload {
|
|
50
|
+
text: string;
|
|
51
|
+
threadId?: string;
|
|
52
|
+
replyToId?: string;
|
|
53
|
+
attachments?: Attachment[];
|
|
54
|
+
}
|
|
55
|
+
interface SendResult {
|
|
56
|
+
success: boolean;
|
|
57
|
+
messageId?: string;
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
interface HealthStatus {
|
|
61
|
+
ok: boolean;
|
|
62
|
+
latencyMs?: number;
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
type ChannelConfig = Record<string, unknown>;
|
|
66
|
+
interface ChannelAdapter {
|
|
67
|
+
/** Unique identifier for this adapter instance */
|
|
68
|
+
id: string;
|
|
69
|
+
meta: ChannelMeta;
|
|
70
|
+
capabilities: ChannelCapabilities;
|
|
71
|
+
connect(config: ChannelConfig): Promise<void>;
|
|
72
|
+
disconnect(): Promise<void>;
|
|
73
|
+
healthCheck(): Promise<HealthStatus>;
|
|
74
|
+
send(target: {
|
|
75
|
+
peerId: string;
|
|
76
|
+
threadId?: string;
|
|
77
|
+
}, payload: OutboundPayload): Promise<SendResult>;
|
|
78
|
+
/** Called when a message arrives — set by the framework */
|
|
79
|
+
onMessage?: (message: InboundMessage) => Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
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,5 @@
|
|
|
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-
|
|
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-CVm1AWUF.js';
|
|
2
|
+
import '../../provider-BHkqkSdq.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* DiscordAdapter — ChannelAdapter implementation for Discord.
|
|
@@ -1,7 +1,7 @@
|
|
|
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-
|
|
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-CVm1AWUF.js';
|
|
2
|
+
import { H as HookBus, c as ChatPipeline } from '../../chat-pipeline-aBSj7a4E.js';
|
|
3
|
+
import { c as ContentBlock } from '../../provider-BHkqkSdq.js';
|
|
3
4
|
import 'better-sqlite3';
|
|
4
|
-
import '../../provider-BHkqkSdq.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* SlackAdapter — ChannelAdapter implementation for Slack.
|
|
@@ -163,20 +163,30 @@ interface SlackConfig {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
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
|
|
166
|
+
* Transport-specific context passed to every enricher. Extensible — add a new
|
|
167
|
+
* optional sub-context when wiring a new source (gmail, drive, dropbox, etc.)
|
|
168
|
+
* and enrichers that need it can type-guard.
|
|
173
169
|
*/
|
|
174
|
-
|
|
170
|
+
interface EnrichmentContext {
|
|
171
|
+
slack?: {
|
|
172
|
+
botToken: string;
|
|
173
|
+
};
|
|
174
|
+
drive?: {
|
|
175
|
+
client: unknown;
|
|
176
|
+
};
|
|
177
|
+
gmail?: {
|
|
178
|
+
client: unknown;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
175
181
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
182
|
+
* An AttachmentEnricher downloads an attachment and returns Claude content
|
|
183
|
+
* blocks representing it. Text blocks become part of the message body; image
|
|
184
|
+
* and document blocks flow through to the Anthropic provider unchanged.
|
|
185
|
+
*
|
|
186
|
+
* Enrichers throw on failure. The framework catches and falls back to a
|
|
187
|
+
* plain `[Attached: <filename>]` breadcrumb in the message body.
|
|
179
188
|
*/
|
|
189
|
+
type AttachmentEnricher = (attachment: Attachment, ctx: EnrichmentContext) => Promise<ContentBlock[]>;
|
|
180
190
|
type AttachmentEnricherMap = Partial<Record<AttachmentMediaType, AttachmentEnricher>>;
|
|
181
191
|
/** Internal: the result of running enrichAttachments on a message. */
|
|
182
192
|
interface EnrichedMessage extends InboundMessage {
|
|
@@ -214,51 +224,32 @@ declare class SlackBoltAdapter {
|
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
/**
|
|
217
|
-
* Run enrichers over each attachment on an InboundMessage
|
|
218
|
-
*
|
|
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:
|
|
227
|
+
* Run enrichers over each attachment on an InboundMessage. Text blocks get
|
|
228
|
+
* appended to `body`; image/document blocks get stored on `attachmentBlocks`
|
|
229
|
+
* so ChatPipeline can assemble a multimodal user message.
|
|
225
230
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* <extracted content>
|
|
231
|
+
* Enrichers that throw or return empty arrays fall back to a plain
|
|
232
|
+
* `[Attached: <filename>]` breadcrumb — the LLM still sees the attachment
|
|
233
|
+
* was there, just without content.
|
|
230
234
|
*/
|
|
231
|
-
declare function enrichAttachments(msg: InboundMessage,
|
|
235
|
+
declare function enrichAttachments(msg: InboundMessage, ctx: EnrichmentContext, enrichers: AttachmentEnricherMap): Promise<InboundMessage>;
|
|
232
236
|
|
|
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
237
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
238
|
+
* Slack image enricher. Downloads the image via the Slack bot token and
|
|
239
|
+
* returns a single `image` ContentBlock containing the base64 data. No
|
|
240
|
+
* intermediate vision API call — the downstream Anthropic provider sees
|
|
241
|
+
* the raw image and processes it natively.
|
|
246
242
|
*
|
|
247
|
-
*
|
|
248
|
-
* dependency of botinabox — the consumer must have it installed.
|
|
243
|
+
* Consumers wire this as `attachmentEnrichers.image = createSlackImageEnricher()`.
|
|
249
244
|
*/
|
|
250
|
-
declare function
|
|
245
|
+
declare function createSlackImageEnricher(): AttachmentEnricher;
|
|
251
246
|
|
|
252
|
-
interface PdfEnricherConfig {
|
|
253
|
-
apiKey: string;
|
|
254
|
-
model?: string;
|
|
255
|
-
maxTokens?: number;
|
|
256
|
-
prompt?: string;
|
|
257
|
-
}
|
|
258
247
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
248
|
+
* Slack PDF enricher. Downloads the PDF via the Slack bot token and returns
|
|
249
|
+
* a single `document` ContentBlock containing the base64 data. No intermediate
|
|
250
|
+
* document-extraction API call — the downstream Anthropic provider ingests
|
|
251
|
+
* the PDF natively.
|
|
261
252
|
*/
|
|
262
|
-
declare function
|
|
253
|
+
declare function createSlackPdfEnricher(): AttachmentEnricher;
|
|
263
254
|
|
|
264
|
-
export { type AttachmentEnricher, type AttachmentEnricherMap, type BoltClient, type EnrichedMessage, type
|
|
255
|
+
export { type AttachmentEnricher, type AttachmentEnricherMap, type BoltClient, type EnrichedMessage, type EnrichmentContext, SlackAdapter, SlackBoltAdapter, type SlackBoltAdapterConfig, type SlackConfig, type SlackEvent, type SlackFile, type TranscribeOptions, type TranscribeResult, createSlackImageEnricher, createSlackPdfEnricher, createSlackAdapter as default, downloadAudio, enrichAttachments, enrichVoiceMessage, extractVoiceTranscript, formatForSlack, parseSlackEvent, transcribeAudio };
|
|
@@ -86,30 +86,43 @@ function createSlackAdapter(client) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// src/channels/slack/enrichers/enrich.ts
|
|
89
|
-
async function enrichAttachments(msg,
|
|
89
|
+
async function enrichAttachments(msg, ctx, enrichers) {
|
|
90
90
|
if (!msg.attachments?.length) return msg;
|
|
91
|
-
const
|
|
91
|
+
const textParts = msg.body ? [msg.body] : [];
|
|
92
|
+
const mediaBlocks = [];
|
|
92
93
|
for (const att of msg.attachments) {
|
|
93
94
|
const enricher = enrichers[att.type];
|
|
94
95
|
const label = att.filename ?? att.url ?? att.type;
|
|
95
96
|
if (!enricher) {
|
|
96
|
-
|
|
97
|
+
textParts.push(`[Attached: ${label}]`);
|
|
97
98
|
continue;
|
|
98
99
|
}
|
|
99
|
-
let
|
|
100
|
+
let blocks;
|
|
100
101
|
try {
|
|
101
|
-
|
|
102
|
+
blocks = await enricher(att, ctx);
|
|
102
103
|
} catch {
|
|
103
|
-
|
|
104
|
+
textParts.push(`[Attached: ${label}]`);
|
|
105
|
+
continue;
|
|
104
106
|
}
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
107
|
+
if (blocks.length === 0) {
|
|
108
|
+
textParts.push(`[Attached: ${label}]`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
for (const block of blocks) {
|
|
112
|
+
if (block.type === "text") {
|
|
113
|
+
textParts.push(`[Attached: ${label}]
|
|
114
|
+
${block.text}`);
|
|
115
|
+
} else if (block.type === "image" || block.type === "document") {
|
|
116
|
+
mediaBlocks.push(block);
|
|
117
|
+
textParts.push(`[Attached: ${label}]`);
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
...msg,
|
|
123
|
+
body: textParts.join("\n\n"),
|
|
124
|
+
attachmentBlocks: mediaBlocks.length > 0 ? mediaBlocks : void 0
|
|
125
|
+
};
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
// src/channels/slack/bolt-adapter.ts
|
|
@@ -141,7 +154,11 @@ var SlackBoltAdapter = class {
|
|
|
141
154
|
inbound = await enrichVoiceMessage2(inbound, botToken);
|
|
142
155
|
}
|
|
143
156
|
if (inbound.attachments?.length && this.config.attachmentEnrichers) {
|
|
144
|
-
inbound = await enrichAttachments(
|
|
157
|
+
inbound = await enrichAttachments(
|
|
158
|
+
inbound,
|
|
159
|
+
{ slack: { botToken } },
|
|
160
|
+
this.config.attachmentEnrichers
|
|
161
|
+
);
|
|
145
162
|
}
|
|
146
163
|
await hooks.emit("message.inbound", inbound);
|
|
147
164
|
});
|
|
@@ -196,108 +213,57 @@ var SlackBoltAdapter = class {
|
|
|
196
213
|
};
|
|
197
214
|
|
|
198
215
|
// src/channels/slack/enrichers/image-enricher.ts
|
|
199
|
-
|
|
200
|
-
async
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
216
|
+
function createSlackImageEnricher() {
|
|
217
|
+
return async (att, ctx) => {
|
|
218
|
+
if (!att.url) throw new Error("image enricher: attachment has no url");
|
|
219
|
+
if (!ctx.slack?.botToken) throw new Error("image enricher: ctx.slack.botToken required");
|
|
220
|
+
const response = await fetch(att.url, {
|
|
221
|
+
headers: { Authorization: `Bearer ${ctx.slack.botToken}` },
|
|
204
222
|
signal: AbortSignal.timeout(3e4)
|
|
205
223
|
});
|
|
206
|
-
if (!response.ok)
|
|
207
|
-
const
|
|
208
|
-
return
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
}
|
|
224
|
+
if (!response.ok) throw new Error(`slack download failed: ${response.status}`);
|
|
225
|
+
const base64 = Buffer.from(await response.arrayBuffer()).toString("base64");
|
|
226
|
+
return [
|
|
227
|
+
{
|
|
228
|
+
type: "image",
|
|
229
|
+
source: {
|
|
230
|
+
type: "base64",
|
|
231
|
+
media_type: att.mimeType ?? "image/jpeg",
|
|
232
|
+
data: base64
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
];
|
|
245
236
|
};
|
|
246
237
|
}
|
|
247
238
|
|
|
248
239
|
// src/channels/slack/enrichers/pdf-enricher.ts
|
|
249
|
-
|
|
250
|
-
async
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
240
|
+
function createSlackPdfEnricher() {
|
|
241
|
+
return async (att, ctx) => {
|
|
242
|
+
if (!att.url) throw new Error("pdf enricher: attachment has no url");
|
|
243
|
+
if (!ctx.slack?.botToken) throw new Error("pdf enricher: ctx.slack.botToken required");
|
|
244
|
+
const response = await fetch(att.url, {
|
|
245
|
+
headers: { Authorization: `Bearer ${ctx.slack.botToken}` },
|
|
254
246
|
signal: AbortSignal.timeout(6e4)
|
|
255
247
|
});
|
|
256
|
-
if (!response.ok)
|
|
257
|
-
const
|
|
258
|
-
return
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
}
|
|
248
|
+
if (!response.ok) throw new Error(`slack download failed: ${response.status}`);
|
|
249
|
+
const base64 = Buffer.from(await response.arrayBuffer()).toString("base64");
|
|
250
|
+
return [
|
|
251
|
+
{
|
|
252
|
+
type: "document",
|
|
253
|
+
source: {
|
|
254
|
+
type: "base64",
|
|
255
|
+
media_type: "application/pdf",
|
|
256
|
+
data: base64
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
];
|
|
294
260
|
};
|
|
295
261
|
}
|
|
296
262
|
export {
|
|
297
263
|
SlackAdapter,
|
|
298
264
|
SlackBoltAdapter,
|
|
299
|
-
|
|
300
|
-
|
|
265
|
+
createSlackImageEnricher,
|
|
266
|
+
createSlackPdfEnricher,
|
|
301
267
|
createSlackAdapter as default,
|
|
302
268
|
downloadAudio,
|
|
303
269
|
enrichAttachments,
|
|
@@ -1,4 +1,5 @@
|
|
|
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-
|
|
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-CVm1AWUF.js';
|
|
2
|
+
import '../../provider-BHkqkSdq.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* WebhookAdapter — ChannelAdapter implementation for webhook-based channels.
|