@ebowwa/channel-types 0.1.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/src/index.ts ADDED
@@ -0,0 +1,367 @@
1
+ /**
2
+ * @ebowwa/channel-types
3
+ *
4
+ * Composable types for channel-LLM communication.
5
+ *
6
+ * Architecture:
7
+ * Channel (Discord/Telegram/WhatsApp)
8
+ * ↓ ChannelMessage (normalized)
9
+ * LLMHandler.process()
10
+ * ↓ ChannelResponse (normalized)
11
+ * Channel delivers response
12
+ */
13
+
14
+ // ============================================================
15
+ // CHANNEL IDENTITY
16
+ // ============================================================
17
+
18
+ /** Supported channel platforms */
19
+ export type ChannelPlatform =
20
+ | "discord"
21
+ | "telegram"
22
+ | "whatsapp"
23
+ | "imessage"
24
+ | "slack"
25
+ | "signal"
26
+ | "web"
27
+ | "cli"
28
+ | "custom";
29
+
30
+ /** Unique channel identifier */
31
+ export interface ChannelId {
32
+ platform: ChannelPlatform;
33
+ accountId: string; // Bot/user account identifier
34
+ instanceId?: string; // Optional for multiple instances
35
+ }
36
+
37
+ // ============================================================
38
+ // NORMALIZED MESSAGE FORMAT
39
+ // ============================================================
40
+
41
+ /** Reference to a message (for replies) */
42
+ export interface MessageRef {
43
+ messageId: string;
44
+ channelId: ChannelId;
45
+ }
46
+
47
+ /** Sender information */
48
+ export interface MessageSender {
49
+ id: string;
50
+ username?: string;
51
+ displayName?: string;
52
+ isBot?: boolean;
53
+ }
54
+
55
+ /** Media attachment */
56
+ export interface MediaAttachment {
57
+ type: "image" | "video" | "audio" | "file";
58
+ url?: string;
59
+ data?: Buffer;
60
+ mimeType?: string;
61
+ filename?: string;
62
+ }
63
+
64
+ /** Message context (where it came from) */
65
+ export interface MessageContext {
66
+ /** Direct message vs group/channel */
67
+ isDM: boolean;
68
+ /** Group or channel name */
69
+ groupName?: string;
70
+ /** Thread ID if in a thread */
71
+ threadId?: string;
72
+ /** Additional platform-specific metadata */
73
+ metadata?: Record<string, unknown>;
74
+ }
75
+
76
+ /**
77
+ * Normalized incoming message from any channel.
78
+ * This is the universal format that LLM handlers receive.
79
+ */
80
+ export interface ChannelMessage {
81
+ // Identity
82
+ readonly messageId: string;
83
+ readonly channelId: ChannelId;
84
+ readonly timestamp: Date;
85
+
86
+ // Sender
87
+ readonly sender: MessageSender;
88
+
89
+ // Content
90
+ readonly text: string;
91
+ readonly media?: MediaAttachment[];
92
+
93
+ // Context
94
+ readonly context: MessageContext;
95
+
96
+ // Reply chain
97
+ readonly replyTo?: MessageRef;
98
+ readonly quotedText?: string;
99
+ }
100
+
101
+ // ============================================================
102
+ // NORMALIZED RESPONSE FORMAT
103
+ // ============================================================
104
+
105
+ /** Response content options */
106
+ export interface ResponseContent {
107
+ /** Text response */
108
+ text: string;
109
+
110
+ /** Optional media attachments */
111
+ media?: MediaAttachment[];
112
+
113
+ /** Whether to reply to the original message */
114
+ replyToOriginal?: boolean;
115
+
116
+ /** Platform-specific options */
117
+ options?: {
118
+ /** Discord: ephemeral (only visible to sender) */
119
+ ephemeral?: boolean;
120
+ /** Telegram: parse mode */
121
+ parseMode?: "markdown" | "html";
122
+ /** Any platform-specific flags */
123
+ [key: string]: unknown;
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Normalized response from LLM handler.
129
+ * Channels deliver this back to the platform.
130
+ */
131
+ export interface ChannelResponse {
132
+ /** Response content */
133
+ readonly content: ResponseContent;
134
+
135
+ /** Reference to the original message (for reply) */
136
+ readonly replyTo: MessageRef;
137
+
138
+ /** Whether this response completes the interaction */
139
+ readonly isComplete?: boolean;
140
+
141
+ /** Optional metadata for logging/debugging */
142
+ readonly metadata?: {
143
+ model?: string;
144
+ tokensUsed?: number;
145
+ latency?: number;
146
+ [key: string]: unknown;
147
+ };
148
+ }
149
+
150
+ /** Streaming response chunk */
151
+ export interface StreamChunk {
152
+ /** Chunk of text */
153
+ text: string;
154
+ /** Whether this is the final chunk */
155
+ done: boolean;
156
+ /** Sequence number */
157
+ seq?: number;
158
+ }
159
+
160
+ // ============================================================
161
+ // CHANNEL CONNECTOR INTERFACE
162
+ // ============================================================
163
+
164
+ /**
165
+ * Interface for channel implementations.
166
+ * Channels normalize platform messages and deliver responses.
167
+ */
168
+ export interface ChannelConnector {
169
+ /** Channel identity */
170
+ readonly id: ChannelId;
171
+
172
+ /** Human-readable label */
173
+ readonly label: string;
174
+
175
+ /** Platform capabilities */
176
+ readonly capabilities: ChannelCapabilities;
177
+
178
+ /** Start listening for messages */
179
+ start(): Promise<void>;
180
+
181
+ /** Stop listening */
182
+ stop(): Promise<void>;
183
+
184
+ /** Register handler for incoming messages */
185
+ onMessage(handler: MessageHandler): void;
186
+
187
+ /** Send response to channel */
188
+ send(response: ChannelResponse): Promise<void>;
189
+
190
+ /** Send streaming response (if supported) */
191
+ stream?(response: ChannelResponse, chunks: AsyncIterable<StreamChunk>): Promise<void>;
192
+
193
+ /** Check if connected */
194
+ isConnected(): boolean;
195
+ }
196
+
197
+ /** Message handler function type */
198
+ export type MessageHandler = (message: ChannelMessage) => Promise<ChannelResponse | void>;
199
+
200
+ /** Channel capabilities */
201
+ export interface ChannelCapabilities {
202
+ /** Supported message types */
203
+ readonly supports: {
204
+ text: boolean;
205
+ media: boolean;
206
+ replies: boolean;
207
+ threads: boolean;
208
+ reactions: boolean;
209
+ editing: boolean;
210
+ streaming: boolean;
211
+ };
212
+
213
+ /** Media constraints */
214
+ readonly media?: {
215
+ maxFileSize?: number;
216
+ supportedMimeTypes?: string[];
217
+ };
218
+
219
+ /** Rate limits */
220
+ readonly rateLimits?: {
221
+ messagesPerMinute?: number;
222
+ charactersPerMessage?: number;
223
+ };
224
+ }
225
+
226
+ // ============================================================
227
+ // LLM HANDLER INTERFACE
228
+ // ============================================================
229
+
230
+ /**
231
+ * Interface for LLM implementations.
232
+ * LLM handlers receive normalized messages and return responses.
233
+ */
234
+ export interface LLMHandler {
235
+ /** Handler identity */
236
+ readonly id: string;
237
+
238
+ /** Model identifier */
239
+ readonly model: string;
240
+
241
+ /** Process incoming message */
242
+ process(message: ChannelMessage): Promise<ChannelResponse>;
243
+
244
+ /** Process with streaming (optional) */
245
+ stream?(message: ChannelMessage): AsyncIterable<StreamChunk>;
246
+
247
+ /** Check if ready */
248
+ isReady(): boolean;
249
+ }
250
+
251
+ // ============================================================
252
+ // CHANNEL-LLM BRIDGE
253
+ // ============================================================
254
+
255
+ /**
256
+ * Bridge that connects channels to LLM handlers.
257
+ * Routes messages from channels to appropriate handlers.
258
+ */
259
+ export interface ChannelBridge {
260
+ /** Register a channel */
261
+ registerChannel(channel: ChannelConnector): void;
262
+
263
+ /** Register an LLM handler */
264
+ registerHandler(handler: LLMHandler): void;
265
+
266
+ /** Set default handler for a channel */
267
+ setDefaultHandler(channelId: ChannelId, handlerId: string): void;
268
+
269
+ /** Start all channels */
270
+ start(): Promise<void>;
271
+
272
+ /** Stop all channels */
273
+ stop(): Promise<void>;
274
+
275
+ /** Get connected channels */
276
+ getChannels(): ChannelConnector[];
277
+
278
+ /** Get registered handlers */
279
+ getHandlers(): LLMHandler[];
280
+ }
281
+
282
+ // ============================================================
283
+ // UTILITY TYPES
284
+ // ============================================================
285
+
286
+ /** Create a simple channel ID */
287
+ export function createChannelId(
288
+ platform: ChannelPlatform,
289
+ accountId: string,
290
+ instanceId?: string
291
+ ): ChannelId {
292
+ return { platform, accountId, instanceId };
293
+ }
294
+
295
+ /** Create a simple message ref */
296
+ export function createMessageRef(messageId: string, channelId: ChannelId): MessageRef {
297
+ return { messageId, channelId };
298
+ }
299
+
300
+ /** Check if two channel IDs match */
301
+ export function channelIdsMatch(a: ChannelId, b: ChannelId): boolean {
302
+ return (
303
+ a.platform === b.platform &&
304
+ a.accountId === b.accountId &&
305
+ a.instanceId === b.instanceId
306
+ );
307
+ }
308
+
309
+ /** Format channel ID for logging */
310
+ export function formatChannelId(id: ChannelId): string {
311
+ const base = `${id.platform}:${id.accountId}`;
312
+ return id.instanceId ? `${base}:${id.instanceId}` : base;
313
+ }
314
+
315
+ /** Default capabilities for text-only channels */
316
+ export const DEFAULT_CAPABILITIES: ChannelCapabilities = {
317
+ supports: {
318
+ text: true,
319
+ media: false,
320
+ replies: false,
321
+ threads: false,
322
+ reactions: false,
323
+ editing: false,
324
+ streaming: false,
325
+ },
326
+ };
327
+
328
+ /** Full capabilities for rich channels (Discord, Slack) */
329
+ export const RICH_CAPABILITIES: ChannelCapabilities = {
330
+ supports: {
331
+ text: true,
332
+ media: true,
333
+ replies: true,
334
+ threads: true,
335
+ reactions: true,
336
+ editing: true,
337
+ streaming: true,
338
+ },
339
+ };
340
+
341
+ // Re-export config types and utilities
342
+ export type {
343
+ BaseChannelConfig,
344
+ TelegramChannelConfig,
345
+ DiscordChannelConfig,
346
+ WhatsAppChannelConfig,
347
+ IMessageChannelConfig,
348
+ SlackChannelConfig,
349
+ SignalChannelConfig,
350
+ CLIChannelConfig,
351
+ WebChannelConfig,
352
+ ChannelConfig,
353
+ ChannelConfigMap,
354
+ } from "./config.js";
355
+
356
+ export {
357
+ loadChannelConfigsFromEnv,
358
+ getChannelId,
359
+ validateChannelConfig,
360
+ getEnabledChannels,
361
+ getChannelsByPlatform,
362
+ createChannelConfig,
363
+ parseChannelConfig,
364
+ parseChannelConfigs,
365
+ serializeChannelConfig,
366
+ serializeChannelConfigs,
367
+ } from "./config.js";
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }