botinabox 2.11.0 → 2.12.1
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/bin/botinabox.mjs +1 -1
- package/dist/channels/slack/index.js +4 -4
- package/dist/chunk-2PIU3ERC.js +224 -0
- package/dist/connectors/google/calendar-connector.d.ts +13 -1
- package/dist/connectors/google/index.js +16 -9
- package/dist/inbound-L7XRZWA3.js +11 -0
- package/package.json +1 -1
- package/dist/channel-CVm1AWUF.d.ts +0 -82
- package/dist/channel-DziSPayj.d.ts +0 -73
- package/dist/chat-pipeline-BGgmH_ap.d.ts +0 -655
- package/dist/chat-pipeline-BWrtVqEP.d.ts +0 -652
- package/dist/chat-pipeline-aBSj7a4E.d.ts +0 -655
- package/dist/provider-BHkqkSdq.d.ts +0 -89
package/bin/botinabox.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import('../dist/cli.js').then(m => m.main(process.argv.slice(2)));
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
extractVoiceTranscript,
|
|
5
5
|
parseSlackEvent,
|
|
6
6
|
transcribeAudio
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-2PIU3ERC.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-L7XRZWA3.js");
|
|
75
|
+
const { enrichVoiceMessage: enrichVoiceMessage2 } = await import("../../inbound-L7XRZWA3.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);
|
|
@@ -139,7 +139,7 @@ var SlackBoltAdapter = class {
|
|
|
139
139
|
async start() {
|
|
140
140
|
const boltModule = "@slack/bolt";
|
|
141
141
|
const bolt = await import(boltModule);
|
|
142
|
-
const { enrichVoiceMessage: enrichVoiceMessage2 } = await import("../../inbound-
|
|
142
|
+
const { enrichVoiceMessage: enrichVoiceMessage2 } = await import("../../inbound-L7XRZWA3.js");
|
|
143
143
|
const boltApp = new bolt.App({
|
|
144
144
|
token: this.config.botToken,
|
|
145
145
|
appToken: this.config.appToken,
|
|
@@ -0,0 +1,224 @@
|
|
|
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/media-type.ts
|
|
75
|
+
var FILETYPE_MAP = {
|
|
76
|
+
// image
|
|
77
|
+
jpg: "image",
|
|
78
|
+
jpeg: "image",
|
|
79
|
+
png: "image",
|
|
80
|
+
gif: "image",
|
|
81
|
+
webp: "image",
|
|
82
|
+
heic: "image",
|
|
83
|
+
svg: "image",
|
|
84
|
+
bmp: "image",
|
|
85
|
+
// video
|
|
86
|
+
mp4: "video",
|
|
87
|
+
mov: "video",
|
|
88
|
+
webm: "video",
|
|
89
|
+
avi: "video",
|
|
90
|
+
mkv: "video",
|
|
91
|
+
// audio (also handled by voice-message path — included for completeness)
|
|
92
|
+
aac: "audio",
|
|
93
|
+
m4a: "audio",
|
|
94
|
+
mp3: "audio",
|
|
95
|
+
wav: "audio",
|
|
96
|
+
ogg: "audio",
|
|
97
|
+
flac: "audio",
|
|
98
|
+
// pdf
|
|
99
|
+
pdf: "pdf",
|
|
100
|
+
// doc
|
|
101
|
+
gdoc: "doc",
|
|
102
|
+
docx: "doc",
|
|
103
|
+
doc: "doc",
|
|
104
|
+
md: "doc",
|
|
105
|
+
txt: "doc",
|
|
106
|
+
rtf: "doc",
|
|
107
|
+
// excel
|
|
108
|
+
gsheet: "excel",
|
|
109
|
+
xlsx: "excel",
|
|
110
|
+
xls: "excel",
|
|
111
|
+
csv: "excel",
|
|
112
|
+
tsv: "excel",
|
|
113
|
+
// presentation
|
|
114
|
+
gslide: "presentation",
|
|
115
|
+
pptx: "presentation",
|
|
116
|
+
ppt: "presentation",
|
|
117
|
+
key: "presentation",
|
|
118
|
+
// html
|
|
119
|
+
html: "html",
|
|
120
|
+
htm: "html"
|
|
121
|
+
};
|
|
122
|
+
function slackFiletypeToMediaType(filetype) {
|
|
123
|
+
if (!filetype) return "misc";
|
|
124
|
+
return FILETYPE_MAP[filetype.toLowerCase()] ?? "misc";
|
|
125
|
+
}
|
|
126
|
+
var URL_REGEX = /https?:\/\/[^\s<>"')]+/g;
|
|
127
|
+
function extractUrls(text) {
|
|
128
|
+
if (!text) return [];
|
|
129
|
+
const matches = text.match(URL_REGEX);
|
|
130
|
+
if (!matches) return [];
|
|
131
|
+
return Array.from(new Set(matches.map((u) => u.replace(/[.,;:!?)]+$/, ""))));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/channels/slack/inbound.ts
|
|
135
|
+
var AUDIO_TYPES = /* @__PURE__ */ new Set(["aac", "mp4", "m4a", "ogg", "webm", "mp3", "wav"]);
|
|
136
|
+
function extractVoiceTranscript(file) {
|
|
137
|
+
const isAudio = file.subtype === "slack_audio" || AUDIO_TYPES.has(file.filetype ?? "");
|
|
138
|
+
if (!isAudio) return null;
|
|
139
|
+
const transcript = file.transcription?.preview?.content ?? (typeof file.preview === "string" ? file.preview : null);
|
|
140
|
+
return transcript ?? null;
|
|
141
|
+
}
|
|
142
|
+
function parseSlackEvent(event) {
|
|
143
|
+
const id = event.ts ?? event.event_ts ?? event.client_msg_id ?? `slack-${Date.now()}`;
|
|
144
|
+
const channel = event.channel ?? "unknown";
|
|
145
|
+
const from = event.user ?? "unknown";
|
|
146
|
+
const isChannel = channel.startsWith("C") || channel.startsWith("G");
|
|
147
|
+
const threadId = event.thread_ts ?? (isChannel ? event.ts : void 0);
|
|
148
|
+
const receivedAt = event.ts ? new Date(parseFloat(event.ts) * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
149
|
+
let body = event.text ?? "";
|
|
150
|
+
if (event.subtype === "file_share" && event.files?.length) {
|
|
151
|
+
for (const file of event.files) {
|
|
152
|
+
const transcript = extractVoiceTranscript(file);
|
|
153
|
+
if (transcript) {
|
|
154
|
+
body = body ? `${body}
|
|
155
|
+
|
|
156
|
+
[Voice message] ${transcript}` : `[Voice message] ${transcript}`;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (event.subtype === "file_share" && event.files?.length && !body) {
|
|
162
|
+
const hasAudio = event.files.some(
|
|
163
|
+
(f) => f.subtype === "slack_audio" || AUDIO_TYPES.has(f.filetype ?? "")
|
|
164
|
+
);
|
|
165
|
+
if (hasAudio) {
|
|
166
|
+
body = "[Voice message \u2014 no transcript available]";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const attachments = [];
|
|
170
|
+
if (event.subtype === "file_share" && event.files?.length) {
|
|
171
|
+
for (const file of event.files) {
|
|
172
|
+
const isAudio = file.subtype === "slack_audio" || AUDIO_TYPES.has(file.filetype ?? "");
|
|
173
|
+
if (isAudio) continue;
|
|
174
|
+
attachments.push({
|
|
175
|
+
type: slackFiletypeToMediaType(file.filetype),
|
|
176
|
+
url: file.url_private,
|
|
177
|
+
mimeType: file.mimetype,
|
|
178
|
+
filename: file.name ?? file.title,
|
|
179
|
+
size: file.size
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const urls = extractUrls(body);
|
|
184
|
+
for (const url of urls) {
|
|
185
|
+
attachments.push({ type: "link", url });
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
channel,
|
|
190
|
+
from,
|
|
191
|
+
body,
|
|
192
|
+
threadId,
|
|
193
|
+
attachments: attachments.length > 0 ? attachments : void 0,
|
|
194
|
+
receivedAt,
|
|
195
|
+
raw: event
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async function enrichVoiceMessage(msg, botToken) {
|
|
199
|
+
if (!msg.body.includes("[Voice message \u2014 no transcript available]")) return msg;
|
|
200
|
+
const raw = msg.raw;
|
|
201
|
+
const files = raw?.files;
|
|
202
|
+
if (!files?.length) return msg;
|
|
203
|
+
const audioFile = files.find(
|
|
204
|
+
(f) => f.subtype === "slack_audio" || AUDIO_TYPES.has(f.filetype ?? "")
|
|
205
|
+
);
|
|
206
|
+
if (!audioFile?.url_private) return msg;
|
|
207
|
+
const buffer = await downloadAudio(audioFile.url_private, botToken);
|
|
208
|
+
if (!buffer) return msg;
|
|
209
|
+
const filename = audioFile.name ?? `voice.${audioFile.filetype ?? "aac"}`;
|
|
210
|
+
const transcript = await transcribeAudio(buffer, filename);
|
|
211
|
+
if (!transcript) return msg;
|
|
212
|
+
return {
|
|
213
|
+
...msg,
|
|
214
|
+
body: `[Voice message] ${transcript}`
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export {
|
|
219
|
+
transcribeAudio,
|
|
220
|
+
downloadAudio,
|
|
221
|
+
extractVoiceTranscript,
|
|
222
|
+
parseSlackEvent,
|
|
223
|
+
enrichVoiceMessage
|
|
224
|
+
};
|
|
@@ -33,7 +33,19 @@ export declare class GoogleCalendarConnector implements Connector<CalendarEventR
|
|
|
33
33
|
sync(options?: SyncOptions): Promise<SyncResult<CalendarEventRecord>>;
|
|
34
34
|
/** Incremental sync using Calendar syncToken. */
|
|
35
35
|
private syncIncremental;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Full sync — paginates through every event for the calendar to obtain a
|
|
38
|
+
* `nextSyncToken`. Google only emits `nextSyncToken` on the final page of a
|
|
39
|
+
* query that contains none of `timeMin`, `timeMax`, `orderBy`, `q`,
|
|
40
|
+
* `updatedMin`, `eventTypes`, `iCalUID`, `showDeleted`, or
|
|
41
|
+
* `showHiddenInvitations`. This implementation therefore omits all of those
|
|
42
|
+
* parameters and drains pagination to completion regardless of `options.limit`
|
|
43
|
+
* or `options.since` — a partial first sync would never mint a usable cursor,
|
|
44
|
+
* which would break every subsequent `syncIncremental` call.
|
|
45
|
+
*
|
|
46
|
+
* `options.limit` and `options.since` are intentionally ignored on full sync.
|
|
47
|
+
* Use `syncIncremental` (via `options.cursor`) for bounded follow-up syncs.
|
|
48
|
+
*/
|
|
37
49
|
private syncFull;
|
|
38
50
|
private ensureConnected;
|
|
39
51
|
private mapEvent;
|
|
@@ -156,22 +156,30 @@ var GoogleCalendarConnector = class {
|
|
|
156
156
|
errors
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Full sync — paginates through every event for the calendar to obtain a
|
|
161
|
+
* `nextSyncToken`. Google only emits `nextSyncToken` on the final page of a
|
|
162
|
+
* query that contains none of `timeMin`, `timeMax`, `orderBy`, `q`,
|
|
163
|
+
* `updatedMin`, `eventTypes`, `iCalUID`, `showDeleted`, or
|
|
164
|
+
* `showHiddenInvitations`. This implementation therefore omits all of those
|
|
165
|
+
* parameters and drains pagination to completion regardless of `options.limit`
|
|
166
|
+
* or `options.since` — a partial first sync would never mint a usable cursor,
|
|
167
|
+
* which would break every subsequent `syncIncremental` call.
|
|
168
|
+
*
|
|
169
|
+
* `options.limit` and `options.since` are intentionally ignored on full sync.
|
|
170
|
+
* Use `syncIncremental` (via `options.cursor`) for bounded follow-up syncs.
|
|
171
|
+
*/
|
|
160
172
|
async syncFull(options) {
|
|
161
173
|
const calendarId = options?.filters?.calendarId ?? "primary";
|
|
162
174
|
const records = [];
|
|
163
175
|
const errors = [];
|
|
164
|
-
const maxResults = options?.limit ?? 250;
|
|
165
|
-
const timeMin = options?.since ? new Date(options.since).toISOString() : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString();
|
|
166
176
|
let pageToken;
|
|
167
177
|
let nextSyncToken;
|
|
168
178
|
do {
|
|
169
179
|
const res = await this.calendar.events.list({
|
|
170
180
|
calendarId,
|
|
171
|
-
timeMin,
|
|
172
181
|
singleEvents: true,
|
|
173
|
-
|
|
174
|
-
maxResults: Math.min(maxResults - records.length, 250),
|
|
182
|
+
maxResults: 250,
|
|
175
183
|
...pageToken ? { pageToken } : {}
|
|
176
184
|
});
|
|
177
185
|
for (const event of res.data.items ?? []) {
|
|
@@ -180,15 +188,14 @@ var GoogleCalendarConnector = class {
|
|
|
180
188
|
} catch (err) {
|
|
181
189
|
errors.push({ id: event.id, error: errorMessage(err) });
|
|
182
190
|
}
|
|
183
|
-
if (records.length >= maxResults) break;
|
|
184
191
|
}
|
|
185
192
|
pageToken = res.data.nextPageToken ?? void 0;
|
|
186
193
|
nextSyncToken = res.data.nextSyncToken ?? void 0;
|
|
187
|
-
} while (pageToken
|
|
194
|
+
} while (pageToken);
|
|
188
195
|
return {
|
|
189
196
|
records,
|
|
190
197
|
cursor: nextSyncToken,
|
|
191
|
-
hasMore:
|
|
198
|
+
hasMore: false,
|
|
192
199
|
errors
|
|
193
200
|
};
|
|
194
201
|
}
|
package/package.json
CHANGED
|
@@ -1,82 +0,0 @@
|
|
|
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,73 +0,0 @@
|
|
|
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 };
|