@framers/agentos 0.1.26 → 0.1.28
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/api/AgentOSOrchestrator.js +1 -1
- package/dist/api/AgentOSOrchestrator.js.map +1 -1
- package/dist/channels/ChannelRouter.d.ts.map +1 -1
- package/dist/channels/ChannelRouter.js +1 -1
- package/dist/channels/ChannelRouter.js.map +1 -1
- package/dist/channels/IChannelAdapter.d.ts.map +1 -1
- package/dist/channels/adapters/BaseChannelAdapter.d.ts +130 -0
- package/dist/channels/adapters/BaseChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/BaseChannelAdapter.js +257 -0
- package/dist/channels/adapters/BaseChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/DiscordChannelAdapter.d.ts +86 -0
- package/dist/channels/adapters/DiscordChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/DiscordChannelAdapter.js +550 -0
- package/dist/channels/adapters/DiscordChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/GoogleChatChannelAdapter.d.ts +117 -0
- package/dist/channels/adapters/GoogleChatChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/GoogleChatChannelAdapter.js +632 -0
- package/dist/channels/adapters/GoogleChatChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/IRCChannelAdapter.d.ts +86 -0
- package/dist/channels/adapters/IRCChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/IRCChannelAdapter.js +253 -0
- package/dist/channels/adapters/IRCChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/RedditChannelAdapter.d.ts +123 -0
- package/dist/channels/adapters/RedditChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/RedditChannelAdapter.js +516 -0
- package/dist/channels/adapters/RedditChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/SignalChannelAdapter.d.ts +120 -0
- package/dist/channels/adapters/SignalChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/SignalChannelAdapter.js +521 -0
- package/dist/channels/adapters/SignalChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/SlackChannelAdapter.d.ts +96 -0
- package/dist/channels/adapters/SlackChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/SlackChannelAdapter.js +535 -0
- package/dist/channels/adapters/SlackChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/TeamsChannelAdapter.d.ts +138 -0
- package/dist/channels/adapters/TeamsChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/TeamsChannelAdapter.js +639 -0
- package/dist/channels/adapters/TeamsChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/TelegramChannelAdapter.d.ts +83 -0
- package/dist/channels/adapters/TelegramChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/TelegramChannelAdapter.js +463 -0
- package/dist/channels/adapters/TelegramChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/TwitterChannelAdapter.d.ts +117 -0
- package/dist/channels/adapters/TwitterChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/TwitterChannelAdapter.js +489 -0
- package/dist/channels/adapters/TwitterChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/WebChatChannelAdapter.d.ts +141 -0
- package/dist/channels/adapters/WebChatChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/WebChatChannelAdapter.js +539 -0
- package/dist/channels/adapters/WebChatChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/WhatsAppChannelAdapter.d.ts +122 -0
- package/dist/channels/adapters/WhatsAppChannelAdapter.d.ts.map +1 -0
- package/dist/channels/adapters/WhatsAppChannelAdapter.js +643 -0
- package/dist/channels/adapters/WhatsAppChannelAdapter.js.map +1 -0
- package/dist/channels/adapters/index.d.ts +34 -0
- package/dist/channels/adapters/index.d.ts.map +1 -0
- package/dist/channels/adapters/index.js +25 -0
- package/dist/channels/adapters/index.js.map +1 -0
- package/dist/channels/index.d.ts +24 -0
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +16 -0
- package/dist/channels/index.js.map +1 -1
- package/dist/cognitive_substrate/personas/metaprompt_presets.d.ts.map +1 -1
- package/dist/cognitive_substrate/personas/metaprompt_presets.js +0 -2
- package/dist/cognitive_substrate/personas/metaprompt_presets.js.map +1 -1
- package/dist/core/provenance/crypto/HashChain.d.ts.map +1 -1
- package/dist/core/provenance/enforcement/AutonomyGuard.d.ts.map +1 -1
- package/dist/core/provenance/enforcement/ProvenanceStorageHooks.d.ts.map +1 -1
- package/dist/core/provenance/enforcement/ProvenanceStorageHooks.js +2 -1
- package/dist/core/provenance/enforcement/ProvenanceStorageHooks.js.map +1 -1
- package/dist/core/provenance/ledger/EventTypes.d.ts.map +1 -1
- package/dist/core/safety/ToolExecutionGuard.d.ts.map +1 -1
- package/dist/core/safety/ToolExecutionGuard.js +0 -1
- package/dist/core/safety/ToolExecutionGuard.js.map +1 -1
- package/dist/extensions/ExtensionManager.d.ts.map +1 -1
- package/dist/extensions/ExtensionManager.js +3 -9
- package/dist/extensions/ExtensionManager.js.map +1 -1
- package/dist/rag/RetrievalAugmentor.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview WebChat Channel Adapter for AgentOS.
|
|
3
|
+
*
|
|
4
|
+
* Provides a built-in HTTP/WebSocket server for embedding a chat widget
|
|
5
|
+
* directly in web applications. No external SDK is required — this adapter
|
|
6
|
+
* uses Node.js built-in `http` and the `ws` package for WebSocket support.
|
|
7
|
+
*
|
|
8
|
+
* **Modes of operation**:
|
|
9
|
+
* - **Standalone**: Creates its own HTTP server on a configurable port.
|
|
10
|
+
* - **Attached**: Attaches to an existing HTTP server (e.g., Express, Fastify).
|
|
11
|
+
*
|
|
12
|
+
* **Dependencies**: Requires the `ws` npm package for WebSocket support.
|
|
13
|
+
* Uses a dynamic import so the package is only loaded at connection time.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Standalone mode
|
|
18
|
+
* const webchat = new WebChatChannelAdapter();
|
|
19
|
+
* await webchat.initialize({
|
|
20
|
+
* platform: 'webchat',
|
|
21
|
+
* credential: 'optional-api-key',
|
|
22
|
+
* params: {
|
|
23
|
+
* port: '8080',
|
|
24
|
+
* corsOrigins: '*',
|
|
25
|
+
* },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Attached mode (with existing Express server)
|
|
29
|
+
* const webchat = new WebChatChannelAdapter();
|
|
30
|
+
* webchat.attachToServer(existingHttpServer);
|
|
31
|
+
* await webchat.initialize({
|
|
32
|
+
* platform: 'webchat',
|
|
33
|
+
* credential: 'optional-api-key',
|
|
34
|
+
* params: { corsOrigins: 'https://myapp.com' },
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @module @framers/agentos/channels/adapters/WebChatChannelAdapter
|
|
39
|
+
*/
|
|
40
|
+
import type { ChannelAuthConfig, ChannelCapability, ChannelPlatform, ChannelSendResult, MessageContent } from '../types.js';
|
|
41
|
+
import { BaseChannelAdapter } from './BaseChannelAdapter.js';
|
|
42
|
+
/** Platform-specific parameters for WebChat connections. */
|
|
43
|
+
export interface WebChatAuthParams extends Record<string, string | undefined> {
|
|
44
|
+
/** API key for authenticating WebSocket clients. Optional. */
|
|
45
|
+
apiKey?: string;
|
|
46
|
+
/** Comma-separated CORS origins (default: '*'). */
|
|
47
|
+
corsOrigins?: string;
|
|
48
|
+
/** Port for standalone HTTP server (default: '8080'). Ignored in attached mode. */
|
|
49
|
+
port?: string;
|
|
50
|
+
/** Path prefix for the WebSocket endpoint (default: '/ws'). */
|
|
51
|
+
wsPath?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Channel adapter for web-based chat using HTTP/WebSocket.
|
|
55
|
+
*
|
|
56
|
+
* Uses dynamic import for the `ws` package so it is only required
|
|
57
|
+
* at runtime when the adapter is actually initialized.
|
|
58
|
+
*
|
|
59
|
+
* Capabilities: text, rich_text, images, buttons, typing_indicator,
|
|
60
|
+
* read_receipts.
|
|
61
|
+
*/
|
|
62
|
+
export declare class WebChatChannelAdapter extends BaseChannelAdapter<WebChatAuthParams> {
|
|
63
|
+
readonly platform: ChannelPlatform;
|
|
64
|
+
readonly displayName = "WebChat";
|
|
65
|
+
readonly capabilities: readonly ChannelCapability[];
|
|
66
|
+
/** Node.js HTTP server (standalone mode). */
|
|
67
|
+
private httpServer;
|
|
68
|
+
/** WebSocket server (ws package). */
|
|
69
|
+
private wss;
|
|
70
|
+
/** Connected clients, keyed by client ID. */
|
|
71
|
+
private clients;
|
|
72
|
+
/** External HTTP server (attached mode). */
|
|
73
|
+
private externalServer;
|
|
74
|
+
/** Whether we created the HTTP server ourselves (vs attached). */
|
|
75
|
+
private ownServer;
|
|
76
|
+
/** API key for client authentication. Empty string means no auth required. */
|
|
77
|
+
private apiKey;
|
|
78
|
+
/** Allowed CORS origins. */
|
|
79
|
+
private corsOrigins;
|
|
80
|
+
/** Counter for generating client IDs. */
|
|
81
|
+
private clientIdCounter;
|
|
82
|
+
/**
|
|
83
|
+
* Attach to an existing HTTP server instead of creating a standalone one.
|
|
84
|
+
* Must be called before {@link initialize}.
|
|
85
|
+
*
|
|
86
|
+
* @param server - Node.js http.Server instance (e.g., from Express).
|
|
87
|
+
*/
|
|
88
|
+
attachToServer(server: any): void;
|
|
89
|
+
protected doConnect(auth: ChannelAuthConfig & {
|
|
90
|
+
params?: WebChatAuthParams;
|
|
91
|
+
}): Promise<void>;
|
|
92
|
+
protected doSendMessage(conversationId: string, content: MessageContent): Promise<ChannelSendResult>;
|
|
93
|
+
protected doShutdown(): Promise<void>;
|
|
94
|
+
sendTypingIndicator(conversationId: string, isTyping: boolean): Promise<void>;
|
|
95
|
+
getConversationInfo(conversationId: string): Promise<{
|
|
96
|
+
name?: string;
|
|
97
|
+
memberCount?: number;
|
|
98
|
+
isGroup: boolean;
|
|
99
|
+
metadata?: Record<string, unknown>;
|
|
100
|
+
}>;
|
|
101
|
+
/** Get the number of currently connected clients. */
|
|
102
|
+
getConnectedClientCount(): number;
|
|
103
|
+
/** Get all connected client IDs. */
|
|
104
|
+
getConnectedClientIds(): string[];
|
|
105
|
+
/**
|
|
106
|
+
* Broadcast a message to ALL connected and authenticated clients.
|
|
107
|
+
*/
|
|
108
|
+
broadcast(content: MessageContent): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Handle plain HTTP requests (standalone mode only).
|
|
111
|
+
* Provides a simple health-check endpoint and CORS headers.
|
|
112
|
+
*/
|
|
113
|
+
private handleHttpRequest;
|
|
114
|
+
/**
|
|
115
|
+
* Handle a new WebSocket connection.
|
|
116
|
+
*/
|
|
117
|
+
private handleNewConnection;
|
|
118
|
+
/**
|
|
119
|
+
* Handle a message from a connected WebSocket client.
|
|
120
|
+
*/
|
|
121
|
+
private handleClientMessage;
|
|
122
|
+
/**
|
|
123
|
+
* Handle client authentication.
|
|
124
|
+
*/
|
|
125
|
+
private handleAuth;
|
|
126
|
+
/**
|
|
127
|
+
* Handle an inbound chat message from a WebSocket client.
|
|
128
|
+
*/
|
|
129
|
+
private handleInboundChatMessage;
|
|
130
|
+
/**
|
|
131
|
+
* Handle a client disconnection.
|
|
132
|
+
*/
|
|
133
|
+
private handleClientDisconnect;
|
|
134
|
+
/**
|
|
135
|
+
* Safely send data over a WebSocket, catching errors.
|
|
136
|
+
*/
|
|
137
|
+
private safeSend;
|
|
138
|
+
private generateClientId;
|
|
139
|
+
private generateMessageId;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=WebChatChannelAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebChatChannelAdapter.d.ts","sourceRoot":"","sources":["../../../src/channels/adapters/WebChatChannelAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAEjB,eAAe,EACf,iBAAiB,EACjB,cAAc,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAM7D,4DAA4D;AAC5D,MAAM,WAAW,iBAAkB,SAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3E,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuED;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB,CAAC,iBAAiB,CAAC;IAC9E,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAa;IAC/C,QAAQ,CAAC,WAAW,aAAa;IACjC,QAAQ,CAAC,YAAY,EAAE,SAAS,iBAAiB,EAAE,CAOxC;IAEX,6CAA6C;IAE7C,OAAO,CAAC,UAAU,CAAkB;IAEpC,qCAAqC;IAErC,OAAO,CAAC,GAAG,CAAkB;IAE7B,6CAA6C;IAC7C,OAAO,CAAC,OAAO,CAAyC;IAExD,4CAA4C;IAE5C,OAAO,CAAC,cAAc,CAAkB;IAExC,kEAAkE;IAClE,OAAO,CAAC,SAAS,CAAS;IAE1B,8EAA8E;IAC9E,OAAO,CAAC,MAAM,CAAM;IAEpB,4BAA4B;IAC5B,OAAO,CAAC,WAAW,CAAO;IAE1B,yCAAyC;IACzC,OAAO,CAAC,eAAe,CAAK;IAI5B;;;;;OAKG;IAEH,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI;cAMjB,SAAS,CACvB,IAAI,EAAE,iBAAiB,GAAG;QAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;KAAE,GACvD,OAAO,CAAC,IAAI,CAAC;cA8EA,aAAa,CAC3B,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,iBAAiB,CAAC;cAsCb,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCrC,mBAAmB,CACvB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,IAAI,CAAC;IAeV,mBAAmB,CACvB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAkBzG,qDAAqD;IACrD,uBAAuB,IAAI,MAAM;IAIjC,oCAAoC;IACpC,qBAAqB,IAAI,MAAM,EAAE;IAIjC;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBvD;;;OAGG;IAEH,OAAO,CAAC,iBAAiB;IAiCzB;;OAEG;IAEH,OAAO,CAAC,mBAAmB;IAuD3B;;OAEG;IAEH,OAAO,CAAC,mBAAmB;IAuE3B;;OAEG;IACH,OAAO,CAAC,UAAU;IA6ClB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA+ChC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;OAEG;IAEH,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,iBAAiB;CAG1B"}
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview WebChat Channel Adapter for AgentOS.
|
|
3
|
+
*
|
|
4
|
+
* Provides a built-in HTTP/WebSocket server for embedding a chat widget
|
|
5
|
+
* directly in web applications. No external SDK is required — this adapter
|
|
6
|
+
* uses Node.js built-in `http` and the `ws` package for WebSocket support.
|
|
7
|
+
*
|
|
8
|
+
* **Modes of operation**:
|
|
9
|
+
* - **Standalone**: Creates its own HTTP server on a configurable port.
|
|
10
|
+
* - **Attached**: Attaches to an existing HTTP server (e.g., Express, Fastify).
|
|
11
|
+
*
|
|
12
|
+
* **Dependencies**: Requires the `ws` npm package for WebSocket support.
|
|
13
|
+
* Uses a dynamic import so the package is only loaded at connection time.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Standalone mode
|
|
18
|
+
* const webchat = new WebChatChannelAdapter();
|
|
19
|
+
* await webchat.initialize({
|
|
20
|
+
* platform: 'webchat',
|
|
21
|
+
* credential: 'optional-api-key',
|
|
22
|
+
* params: {
|
|
23
|
+
* port: '8080',
|
|
24
|
+
* corsOrigins: '*',
|
|
25
|
+
* },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Attached mode (with existing Express server)
|
|
29
|
+
* const webchat = new WebChatChannelAdapter();
|
|
30
|
+
* webchat.attachToServer(existingHttpServer);
|
|
31
|
+
* await webchat.initialize({
|
|
32
|
+
* platform: 'webchat',
|
|
33
|
+
* credential: 'optional-api-key',
|
|
34
|
+
* params: { corsOrigins: 'https://myapp.com' },
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @module @framers/agentos/channels/adapters/WebChatChannelAdapter
|
|
39
|
+
*/
|
|
40
|
+
import { BaseChannelAdapter } from './BaseChannelAdapter.js';
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// WebChatChannelAdapter
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Channel adapter for web-based chat using HTTP/WebSocket.
|
|
46
|
+
*
|
|
47
|
+
* Uses dynamic import for the `ws` package so it is only required
|
|
48
|
+
* at runtime when the adapter is actually initialized.
|
|
49
|
+
*
|
|
50
|
+
* Capabilities: text, rich_text, images, buttons, typing_indicator,
|
|
51
|
+
* read_receipts.
|
|
52
|
+
*/
|
|
53
|
+
export class WebChatChannelAdapter extends BaseChannelAdapter {
|
|
54
|
+
constructor() {
|
|
55
|
+
super(...arguments);
|
|
56
|
+
this.platform = 'webchat';
|
|
57
|
+
this.displayName = 'WebChat';
|
|
58
|
+
this.capabilities = [
|
|
59
|
+
'text',
|
|
60
|
+
'rich_text',
|
|
61
|
+
'images',
|
|
62
|
+
'buttons',
|
|
63
|
+
'typing_indicator',
|
|
64
|
+
'read_receipts',
|
|
65
|
+
];
|
|
66
|
+
/** Connected clients, keyed by client ID. */
|
|
67
|
+
this.clients = new Map();
|
|
68
|
+
/** Whether we created the HTTP server ourselves (vs attached). */
|
|
69
|
+
this.ownServer = false;
|
|
70
|
+
/** API key for client authentication. Empty string means no auth required. */
|
|
71
|
+
this.apiKey = '';
|
|
72
|
+
/** Allowed CORS origins. */
|
|
73
|
+
this.corsOrigins = '*';
|
|
74
|
+
/** Counter for generating client IDs. */
|
|
75
|
+
this.clientIdCounter = 0;
|
|
76
|
+
}
|
|
77
|
+
// ── Public: Server attachment ──
|
|
78
|
+
/**
|
|
79
|
+
* Attach to an existing HTTP server instead of creating a standalone one.
|
|
80
|
+
* Must be called before {@link initialize}.
|
|
81
|
+
*
|
|
82
|
+
* @param server - Node.js http.Server instance (e.g., from Express).
|
|
83
|
+
*/
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
attachToServer(server) {
|
|
86
|
+
this.externalServer = server;
|
|
87
|
+
}
|
|
88
|
+
// ── Abstract hook implementations ──
|
|
89
|
+
async doConnect(auth) {
|
|
90
|
+
const params = auth.params ?? {};
|
|
91
|
+
this.apiKey = params.apiKey ?? auth.credential ?? '';
|
|
92
|
+
this.corsOrigins = params.corsOrigins ?? '*';
|
|
93
|
+
const port = parseInt(params.port ?? '8080', 10);
|
|
94
|
+
const wsPath = params.wsPath ?? '/ws';
|
|
95
|
+
// Dynamic import for ws package
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
let WsModule;
|
|
98
|
+
try {
|
|
99
|
+
WsModule = await import('ws');
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
throw new Error('The "ws" package is required for the WebChat adapter. ' +
|
|
103
|
+
'Install it with: npm install ws');
|
|
104
|
+
}
|
|
105
|
+
const WebSocketServer = WsModule.WebSocketServer ?? WsModule.Server ?? WsModule.default?.WebSocketServer ?? WsModule.default?.Server;
|
|
106
|
+
// Create or reuse HTTP server
|
|
107
|
+
if (this.externalServer) {
|
|
108
|
+
// Attached mode — use provided server
|
|
109
|
+
this.httpServer = this.externalServer;
|
|
110
|
+
this.ownServer = false;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Standalone mode — create our own HTTP server
|
|
114
|
+
const http = await import('http');
|
|
115
|
+
this.httpServer = http.createServer((req, res) => {
|
|
116
|
+
this.handleHttpRequest(req, res);
|
|
117
|
+
});
|
|
118
|
+
this.ownServer = true;
|
|
119
|
+
}
|
|
120
|
+
// Create WebSocket server attached to HTTP server
|
|
121
|
+
this.wss = new WebSocketServer({
|
|
122
|
+
server: this.httpServer,
|
|
123
|
+
path: wsPath,
|
|
124
|
+
});
|
|
125
|
+
// Wire up WebSocket events
|
|
126
|
+
this.wss.on('connection', (ws, req) => {
|
|
127
|
+
this.handleNewConnection(ws, req);
|
|
128
|
+
});
|
|
129
|
+
// Start HTTP server if we own it
|
|
130
|
+
if (this.ownServer) {
|
|
131
|
+
await new Promise((resolve, reject) => {
|
|
132
|
+
const listenTimeout = setTimeout(() => {
|
|
133
|
+
reject(new Error(`WebChat HTTP server failed to start on port ${port} within 10s`));
|
|
134
|
+
}, 10000);
|
|
135
|
+
this.httpServer.listen(port, () => {
|
|
136
|
+
clearTimeout(listenTimeout);
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
this.httpServer.on('error', (err) => {
|
|
140
|
+
clearTimeout(listenTimeout);
|
|
141
|
+
reject(err);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
this.platformInfo = {
|
|
146
|
+
port: this.ownServer ? port : 'attached',
|
|
147
|
+
wsPath,
|
|
148
|
+
corsOrigins: this.corsOrigins,
|
|
149
|
+
requiresAuth: !!this.apiKey,
|
|
150
|
+
};
|
|
151
|
+
console.log(`[WebChat] ${this.ownServer ? `Server started on port ${port}` : 'Attached to existing server'} (ws: ${wsPath}, auth: ${this.apiKey ? 'required' : 'none'})`);
|
|
152
|
+
}
|
|
153
|
+
async doSendMessage(conversationId, content) {
|
|
154
|
+
// Find all clients in this conversation
|
|
155
|
+
const targets = [];
|
|
156
|
+
for (const client of this.clients.values()) {
|
|
157
|
+
if (client.conversationId === conversationId && client.authenticated) {
|
|
158
|
+
targets.push(client);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (targets.length === 0) {
|
|
162
|
+
throw new Error(`[WebChat] No connected clients for conversation ${conversationId}.`);
|
|
163
|
+
}
|
|
164
|
+
const messageId = this.generateMessageId();
|
|
165
|
+
const timestamp = new Date().toISOString();
|
|
166
|
+
const outbound = {
|
|
167
|
+
type: 'message',
|
|
168
|
+
messageId,
|
|
169
|
+
blocks: content.blocks,
|
|
170
|
+
text: content.blocks
|
|
171
|
+
.filter((b) => b.type === 'text')
|
|
172
|
+
.map((b) => b.text)
|
|
173
|
+
.join('\n'),
|
|
174
|
+
timestamp,
|
|
175
|
+
replyToMessageId: content.replyToMessageId,
|
|
176
|
+
};
|
|
177
|
+
const payload = JSON.stringify(outbound);
|
|
178
|
+
for (const client of targets) {
|
|
179
|
+
this.safeSend(client.ws, payload);
|
|
180
|
+
}
|
|
181
|
+
return { messageId, timestamp };
|
|
182
|
+
}
|
|
183
|
+
async doShutdown() {
|
|
184
|
+
// Close all client connections
|
|
185
|
+
for (const client of this.clients.values()) {
|
|
186
|
+
try {
|
|
187
|
+
client.ws.close(1001, 'Server shutting down');
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Best effort
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this.clients.clear();
|
|
194
|
+
// Close WebSocket server
|
|
195
|
+
if (this.wss) {
|
|
196
|
+
try {
|
|
197
|
+
this.wss.close();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Best effort
|
|
201
|
+
}
|
|
202
|
+
this.wss = undefined;
|
|
203
|
+
}
|
|
204
|
+
// Close HTTP server only if we own it
|
|
205
|
+
if (this.httpServer && this.ownServer) {
|
|
206
|
+
await new Promise((resolve) => {
|
|
207
|
+
this.httpServer.close(() => resolve());
|
|
208
|
+
// Force resolve after 5s if server doesn't close cleanly
|
|
209
|
+
setTimeout(resolve, 5000);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
this.httpServer = undefined;
|
|
213
|
+
this.externalServer = undefined;
|
|
214
|
+
this.ownServer = false;
|
|
215
|
+
console.log('[WebChat] Adapter shut down.');
|
|
216
|
+
}
|
|
217
|
+
// ── IChannelAdapter optional methods ──
|
|
218
|
+
async sendTypingIndicator(conversationId, isTyping) {
|
|
219
|
+
const outbound = {
|
|
220
|
+
type: 'typing',
|
|
221
|
+
timestamp: new Date().toISOString(),
|
|
222
|
+
};
|
|
223
|
+
// Add isTyping to the payload (not in the strict type but useful)
|
|
224
|
+
const payload = JSON.stringify({ ...outbound, isTyping });
|
|
225
|
+
for (const client of this.clients.values()) {
|
|
226
|
+
if (client.conversationId === conversationId && client.authenticated) {
|
|
227
|
+
this.safeSend(client.ws, payload);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async getConversationInfo(conversationId) {
|
|
232
|
+
let memberCount = 0;
|
|
233
|
+
for (const client of this.clients.values()) {
|
|
234
|
+
if (client.conversationId === conversationId) {
|
|
235
|
+
memberCount++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
name: `WebChat ${conversationId}`,
|
|
240
|
+
memberCount,
|
|
241
|
+
isGroup: memberCount > 1,
|
|
242
|
+
metadata: { platform: 'webchat' },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// ── Public: Accessors ──
|
|
246
|
+
/** Get the number of currently connected clients. */
|
|
247
|
+
getConnectedClientCount() {
|
|
248
|
+
return this.clients.size;
|
|
249
|
+
}
|
|
250
|
+
/** Get all connected client IDs. */
|
|
251
|
+
getConnectedClientIds() {
|
|
252
|
+
return [...this.clients.keys()];
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Broadcast a message to ALL connected and authenticated clients.
|
|
256
|
+
*/
|
|
257
|
+
async broadcast(content) {
|
|
258
|
+
const messageId = this.generateMessageId();
|
|
259
|
+
const timestamp = new Date().toISOString();
|
|
260
|
+
const outbound = {
|
|
261
|
+
type: 'message',
|
|
262
|
+
messageId,
|
|
263
|
+
blocks: content.blocks,
|
|
264
|
+
text: content.blocks
|
|
265
|
+
.filter((b) => b.type === 'text')
|
|
266
|
+
.map((b) => b.text)
|
|
267
|
+
.join('\n'),
|
|
268
|
+
timestamp,
|
|
269
|
+
};
|
|
270
|
+
const payload = JSON.stringify(outbound);
|
|
271
|
+
for (const client of this.clients.values()) {
|
|
272
|
+
if (client.authenticated) {
|
|
273
|
+
this.safeSend(client.ws, payload);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// ── Private: HTTP ──
|
|
278
|
+
/**
|
|
279
|
+
* Handle plain HTTP requests (standalone mode only).
|
|
280
|
+
* Provides a simple health-check endpoint and CORS headers.
|
|
281
|
+
*/
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
283
|
+
handleHttpRequest(req, res) {
|
|
284
|
+
// CORS headers
|
|
285
|
+
res.setHeader('Access-Control-Allow-Origin', this.corsOrigins);
|
|
286
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
287
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
288
|
+
if (req.method === 'OPTIONS') {
|
|
289
|
+
res.writeHead(204);
|
|
290
|
+
res.end();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (req.url === '/health' || req.url === '/') {
|
|
294
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
295
|
+
res.end(JSON.stringify({
|
|
296
|
+
status: 'ok',
|
|
297
|
+
platform: 'webchat',
|
|
298
|
+
clients: this.clients.size,
|
|
299
|
+
uptime: this.connectedSince
|
|
300
|
+
? Date.now() - new Date(this.connectedSince).getTime()
|
|
301
|
+
: 0,
|
|
302
|
+
}));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
306
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
307
|
+
}
|
|
308
|
+
// ── Private: WebSocket ──
|
|
309
|
+
/**
|
|
310
|
+
* Handle a new WebSocket connection.
|
|
311
|
+
*/
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
313
|
+
handleNewConnection(ws, req) {
|
|
314
|
+
const clientId = this.generateClientId();
|
|
315
|
+
const client = {
|
|
316
|
+
id: clientId,
|
|
317
|
+
ws,
|
|
318
|
+
conversationId: clientId, // Default conversation is 1:1 with this client
|
|
319
|
+
connectedAt: new Date().toISOString(),
|
|
320
|
+
authenticated: !this.apiKey, // Auto-authenticated if no API key required
|
|
321
|
+
};
|
|
322
|
+
this.clients.set(clientId, client);
|
|
323
|
+
console.log(`[WebChat] Client ${clientId} connected (${this.clients.size} total, auth: ${client.authenticated ? 'yes' : 'pending'})`);
|
|
324
|
+
// If no auth required, emit connection event immediately
|
|
325
|
+
if (client.authenticated) {
|
|
326
|
+
this.emit({
|
|
327
|
+
type: 'member_joined',
|
|
328
|
+
platform: 'webchat',
|
|
329
|
+
conversationId: client.conversationId,
|
|
330
|
+
timestamp: client.connectedAt,
|
|
331
|
+
data: { clientId, user: client.user },
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// Wire up message handler
|
|
335
|
+
ws.on('message', (data) => {
|
|
336
|
+
this.handleClientMessage(client, data);
|
|
337
|
+
});
|
|
338
|
+
ws.on('close', () => {
|
|
339
|
+
this.handleClientDisconnect(client);
|
|
340
|
+
});
|
|
341
|
+
ws.on('error', (err) => {
|
|
342
|
+
console.error(`[WebChat] Client ${clientId} error:`, err.message);
|
|
343
|
+
this.handleClientDisconnect(client);
|
|
344
|
+
});
|
|
345
|
+
// Send welcome/auth-required message
|
|
346
|
+
if (!client.authenticated) {
|
|
347
|
+
this.safeSend(ws, JSON.stringify({
|
|
348
|
+
type: 'auth_result',
|
|
349
|
+
authenticated: false,
|
|
350
|
+
error: 'Authentication required. Send { type: "auth", apiKey: "..." }.',
|
|
351
|
+
timestamp: new Date().toISOString(),
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Handle a message from a connected WebSocket client.
|
|
357
|
+
*/
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
359
|
+
handleClientMessage(client, rawData) {
|
|
360
|
+
let msg;
|
|
361
|
+
try {
|
|
362
|
+
const text = typeof rawData === 'string' ? rawData : rawData.toString();
|
|
363
|
+
msg = JSON.parse(text);
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
this.safeSend(client.ws, JSON.stringify({
|
|
367
|
+
type: 'error',
|
|
368
|
+
error: 'Invalid JSON',
|
|
369
|
+
timestamp: new Date().toISOString(),
|
|
370
|
+
}));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
switch (msg.type) {
|
|
374
|
+
case 'auth': {
|
|
375
|
+
this.handleAuth(client, msg);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case 'message': {
|
|
379
|
+
if (!client.authenticated) {
|
|
380
|
+
this.safeSend(client.ws, JSON.stringify({
|
|
381
|
+
type: 'error',
|
|
382
|
+
error: 'Not authenticated',
|
|
383
|
+
timestamp: new Date().toISOString(),
|
|
384
|
+
}));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
this.handleInboundChatMessage(client, msg);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case 'typing': {
|
|
391
|
+
if (!client.authenticated)
|
|
392
|
+
return;
|
|
393
|
+
this.emit({
|
|
394
|
+
type: 'typing',
|
|
395
|
+
platform: 'webchat',
|
|
396
|
+
conversationId: msg.conversationId ?? client.conversationId,
|
|
397
|
+
timestamp: new Date().toISOString(),
|
|
398
|
+
data: {
|
|
399
|
+
userId: client.user?.id ?? client.id,
|
|
400
|
+
isTyping: msg.isTyping ?? true,
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
case 'ping': {
|
|
406
|
+
this.safeSend(client.ws, JSON.stringify({
|
|
407
|
+
type: 'pong',
|
|
408
|
+
timestamp: new Date().toISOString(),
|
|
409
|
+
}));
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
default:
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Handle client authentication.
|
|
418
|
+
*/
|
|
419
|
+
handleAuth(client, msg) {
|
|
420
|
+
if (!this.apiKey || msg.apiKey === this.apiKey) {
|
|
421
|
+
client.authenticated = true;
|
|
422
|
+
// Update user info if provided
|
|
423
|
+
if (msg.user) {
|
|
424
|
+
client.user = msg.user;
|
|
425
|
+
}
|
|
426
|
+
// Update conversation ID if provided
|
|
427
|
+
if (msg.conversationId) {
|
|
428
|
+
client.conversationId = msg.conversationId;
|
|
429
|
+
}
|
|
430
|
+
this.safeSend(client.ws, JSON.stringify({
|
|
431
|
+
type: 'auth_result',
|
|
432
|
+
authenticated: true,
|
|
433
|
+
timestamp: new Date().toISOString(),
|
|
434
|
+
}));
|
|
435
|
+
this.emit({
|
|
436
|
+
type: 'member_joined',
|
|
437
|
+
platform: 'webchat',
|
|
438
|
+
conversationId: client.conversationId,
|
|
439
|
+
timestamp: new Date().toISOString(),
|
|
440
|
+
data: { clientId: client.id, user: client.user },
|
|
441
|
+
});
|
|
442
|
+
console.log(`[WebChat] Client ${client.id} authenticated`);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
this.safeSend(client.ws, JSON.stringify({
|
|
446
|
+
type: 'auth_result',
|
|
447
|
+
authenticated: false,
|
|
448
|
+
error: 'Invalid API key',
|
|
449
|
+
timestamp: new Date().toISOString(),
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Handle an inbound chat message from a WebSocket client.
|
|
455
|
+
*/
|
|
456
|
+
handleInboundChatMessage(client, msg) {
|
|
457
|
+
const conversationId = msg.conversationId ?? client.conversationId;
|
|
458
|
+
const timestamp = new Date().toISOString();
|
|
459
|
+
// Build content blocks
|
|
460
|
+
let contentBlocks;
|
|
461
|
+
if (msg.blocks && msg.blocks.length > 0) {
|
|
462
|
+
contentBlocks = msg.blocks;
|
|
463
|
+
}
|
|
464
|
+
else if (msg.text) {
|
|
465
|
+
contentBlocks = [{ type: 'text', text: msg.text }];
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
contentBlocks = [{ type: 'text', text: '' }];
|
|
469
|
+
}
|
|
470
|
+
const messageId = this.generateMessageId();
|
|
471
|
+
const channelMessage = {
|
|
472
|
+
messageId,
|
|
473
|
+
platform: 'webchat',
|
|
474
|
+
conversationId,
|
|
475
|
+
conversationType: 'direct',
|
|
476
|
+
sender: client.user ?? {
|
|
477
|
+
id: client.id,
|
|
478
|
+
displayName: `User ${client.id}`,
|
|
479
|
+
},
|
|
480
|
+
content: contentBlocks,
|
|
481
|
+
text: msg.text ??
|
|
482
|
+
contentBlocks
|
|
483
|
+
.filter((b) => b.type === 'text')
|
|
484
|
+
.map((b) => b.text)
|
|
485
|
+
.join('\n'),
|
|
486
|
+
timestamp,
|
|
487
|
+
replyToMessageId: msg.replyToMessageId,
|
|
488
|
+
};
|
|
489
|
+
this.emit({
|
|
490
|
+
type: 'message',
|
|
491
|
+
platform: 'webchat',
|
|
492
|
+
conversationId,
|
|
493
|
+
timestamp,
|
|
494
|
+
data: channelMessage,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Handle a client disconnection.
|
|
499
|
+
*/
|
|
500
|
+
handleClientDisconnect(client) {
|
|
501
|
+
if (!this.clients.has(client.id))
|
|
502
|
+
return;
|
|
503
|
+
this.clients.delete(client.id);
|
|
504
|
+
if (client.authenticated) {
|
|
505
|
+
this.emit({
|
|
506
|
+
type: 'member_left',
|
|
507
|
+
platform: 'webchat',
|
|
508
|
+
conversationId: client.conversationId,
|
|
509
|
+
timestamp: new Date().toISOString(),
|
|
510
|
+
data: { clientId: client.id, user: client.user },
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
console.log(`[WebChat] Client ${client.id} disconnected (${this.clients.size} remaining)`);
|
|
514
|
+
}
|
|
515
|
+
// ── Private: Utilities ──
|
|
516
|
+
/**
|
|
517
|
+
* Safely send data over a WebSocket, catching errors.
|
|
518
|
+
*/
|
|
519
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
520
|
+
safeSend(ws, data) {
|
|
521
|
+
try {
|
|
522
|
+
if (ws.readyState === 1) {
|
|
523
|
+
// OPEN
|
|
524
|
+
ws.send(data);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
console.error('[WebChat] Failed to send message:', err);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
generateClientId() {
|
|
532
|
+
this.clientIdCounter++;
|
|
533
|
+
return `wc-${Date.now()}-${this.clientIdCounter}`;
|
|
534
|
+
}
|
|
535
|
+
generateMessageId() {
|
|
536
|
+
return `wcm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
//# sourceMappingURL=WebChatChannelAdapter.js.map
|