ai.matey.http 0.2.2 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # ai.matey.http
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d3fd2e2: Production HTTP endpoints: built-in `/health` + `/health/ready` + `/health/live`, Prometheus
8
+ `/metrics`, OpenAI-compatible `/v1/embeddings`, per-route rate limits via `RouteConfig.rateLimit`,
9
+ and a zero-dependency WebSocket streaming subpath (`ai.matey.http/websocket`).
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [dae4d01]
14
+ - Updated dependencies [e7df1d0]
15
+ - Updated dependencies [d3fd2e2]
16
+ - Updated dependencies [f227db2]
17
+ - Updated dependencies [2912b7d]
18
+ - Updated dependencies [aef9f4a]
19
+ - Updated dependencies [78731bb]
20
+ - Updated dependencies [b7e2312]
21
+ - ai.matey.types@0.3.0
22
+ - ai.matey.core@0.3.0
23
+ - ai.matey.http.core@0.3.0
24
+
3
25
  ## 0.2.1
4
26
 
5
27
  ### Patch Changes
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ /**
3
+ * WebSocket Streaming
4
+ *
5
+ * Transport-agnostic WebSocket handler for real-time AI chat. Zero-dependency
6
+ * by design: instead of bundling a WebSocket implementation, it accepts any
7
+ * socket satisfying the structural {@link WebSocketLike} interface — Node
8
+ * `ws` sockets, `Deno.upgradeWebSocket` sockets, Bun sockets, or browser
9
+ * WebSockets all qualify.
10
+ *
11
+ * Wire protocol (JSON text frames):
12
+ * - client → server: `{ type: 'chat', id, request }`, `{ type: 'cancel', id }`
13
+ * - server → client: `{ type: 'start' | 'chunk' | 'done' | 'error', id, ... }`
14
+ * plus periodic `{ type: 'ping' }` heartbeats
15
+ *
16
+ * @module
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.createWebSocketHandler = createWebSocketHandler;
20
+ exports.attachWebSocketServer = attachWebSocketServer;
21
+ /**
22
+ * Create a per-connection handler streaming chat over a WebSocket.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { WebSocketServer } from 'ws';
27
+ * import { createWebSocketHandler } from 'ai.matey.http/websocket';
28
+ *
29
+ * const handleSocket = createWebSocketHandler(bridge);
30
+ * new WebSocketServer({ port: 8080 }).on('connection', handleSocket);
31
+ * ```
32
+ */
33
+ function createWebSocketHandler(bridge, options = {}) {
34
+ const maxConcurrentStreams = options.maxConcurrentStreams ?? 5;
35
+ const heartbeatMs = options.heartbeatMs ?? 30000;
36
+ return (socket) => {
37
+ const activeStreams = new Map();
38
+ const sendMessage = (message) => {
39
+ try {
40
+ socket.send(JSON.stringify(message));
41
+ }
42
+ catch (error) {
43
+ options.onError?.(error instanceof Error ? error : new Error(String(error)));
44
+ }
45
+ };
46
+ let heartbeat;
47
+ if (heartbeatMs > 0) {
48
+ heartbeat = setInterval(() => sendMessage({ type: 'ping' }), heartbeatMs);
49
+ if (typeof heartbeat === 'object' && 'unref' in heartbeat) {
50
+ heartbeat.unref();
51
+ }
52
+ }
53
+ const cleanup = () => {
54
+ if (heartbeat) {
55
+ clearInterval(heartbeat);
56
+ }
57
+ for (const controller of activeStreams.values()) {
58
+ controller.abort();
59
+ }
60
+ activeStreams.clear();
61
+ };
62
+ const runChat = async (id, request) => {
63
+ if (activeStreams.size >= maxConcurrentStreams) {
64
+ sendMessage({
65
+ type: 'error',
66
+ id,
67
+ error: { message: `Too many concurrent streams (max ${maxConcurrentStreams})` },
68
+ });
69
+ return;
70
+ }
71
+ const controller = new AbortController();
72
+ activeStreams.set(id, controller);
73
+ sendMessage({ type: 'start', id });
74
+ try {
75
+ const stream = bridge.chatStream(request, { signal: controller.signal });
76
+ for await (const chunk of stream) {
77
+ if (controller.signal.aborted) {
78
+ break;
79
+ }
80
+ sendMessage({ type: 'chunk', id, chunk });
81
+ }
82
+ if (!controller.signal.aborted) {
83
+ sendMessage({ type: 'done', id });
84
+ }
85
+ }
86
+ catch (error) {
87
+ if (!controller.signal.aborted) {
88
+ sendMessage({
89
+ type: 'error',
90
+ id,
91
+ error: { message: error instanceof Error ? error.message : String(error) },
92
+ });
93
+ }
94
+ }
95
+ finally {
96
+ activeStreams.delete(id);
97
+ }
98
+ };
99
+ const onMessage = (raw) => {
100
+ let parsed;
101
+ try {
102
+ const text = typeof raw === 'string'
103
+ ? raw
104
+ : raw instanceof Uint8Array
105
+ ? new TextDecoder().decode(raw)
106
+ : String(raw);
107
+ parsed = JSON.parse(text);
108
+ }
109
+ catch {
110
+ sendMessage({ type: 'error', id: null, error: { message: 'Malformed JSON message' } });
111
+ return;
112
+ }
113
+ switch (parsed.type) {
114
+ case 'chat':
115
+ if (!parsed.id || parsed.request === undefined) {
116
+ sendMessage({
117
+ type: 'error',
118
+ id: parsed.id ?? null,
119
+ error: { message: "'chat' requires 'id' and 'request'" },
120
+ });
121
+ return;
122
+ }
123
+ void runChat(parsed.id, parsed.request);
124
+ break;
125
+ case 'cancel':
126
+ activeStreams.get(parsed.id)?.abort();
127
+ activeStreams.delete(parsed.id);
128
+ break;
129
+ case 'pong':
130
+ break;
131
+ default:
132
+ sendMessage({
133
+ type: 'error',
134
+ id: null,
135
+ error: {
136
+ message: `Unknown message type: ${String(parsed.type)}`,
137
+ },
138
+ });
139
+ }
140
+ };
141
+ // Support both listener styles; extract data from DOM MessageEvent
142
+ if (socket.on) {
143
+ socket.on('message', (data) => onMessage(data));
144
+ socket.on('close', cleanup);
145
+ socket.on('error', cleanup);
146
+ }
147
+ else if (socket.addEventListener) {
148
+ socket.addEventListener('message', (event) => onMessage(event.data ?? event));
149
+ socket.addEventListener('close', cleanup);
150
+ socket.addEventListener('error', cleanup);
151
+ }
152
+ };
153
+ }
154
+ /**
155
+ * Attach the handler to a `ws`-style server's connection event.
156
+ */
157
+ function attachWebSocketServer(server, bridge, options) {
158
+ server.on('connection', createWebSocketHandler(bridge, options));
159
+ }
160
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/websocket/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;AA4DH,wDAsIC;AAKD,sDAMC;AA7JD;;;;;;;;;;;GAWG;AACH,SAAgB,sBAAsB,CACpC,MAAc,EACd,UAAmC,EAAE;IAErC,MAAM,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,IAAI,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,KAAK,CAAC;IAEjD,OAAO,CAAC,MAAqB,EAAQ,EAAE;QACrC,MAAM,aAAa,GAAG,IAAI,GAAG,EAA2B,CAAC;QAEzD,MAAM,WAAW,GAAG,CAAC,OAA+B,EAAQ,EAAE;YAC5D,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC,CAAC;QAEF,IAAI,SAAqD,CAAC;QAC1D,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACpB,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC;YAC1E,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;gBAC1D,SAAS,CAAC,KAAK,EAAE,CAAC;YACpB,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,IAAI,SAAS,EAAE,CAAC;gBACd,aAAa,CAAC,SAAS,CAAC,CAAC;YAC3B,CAAC;YACD,KAAK,MAAM,UAAU,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;gBAChD,UAAU,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;YACD,aAAa,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,KAAK,EAAE,EAAU,EAAE,OAAgB,EAAiB,EAAE;YACpE,IAAI,aAAa,CAAC,IAAI,IAAI,oBAAoB,EAAE,CAAC;gBAC/C,WAAW,CAAC;oBACV,IAAI,EAAE,OAAO;oBACb,EAAE;oBACF,KAAK,EAAE,EAAE,OAAO,EAAE,oCAAoC,oBAAoB,GAAG,EAAE;iBAChF,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;YAClC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;YAEnC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,OAAgB,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;gBAClF,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;oBACjC,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;wBAC9B,MAAM;oBACR,CAAC;oBACD,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC5C,CAAC;gBACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC/B,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC/B,WAAW,CAAC;wBACV,IAAI,EAAE,OAAO;wBACb,EAAE;wBACF,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;qBAC3E,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,SAAS,GAAG,CAAC,GAAY,EAAQ,EAAE;YACvC,IAAI,MAA8B,CAAC;YACnC,IAAI,CAAC;gBACH,MAAM,IAAI,GACR,OAAO,GAAG,KAAK,QAAQ;oBACrB,CAAC,CAAC,GAAG;oBACL,CAAC,CAAC,GAAG,YAAY,UAAU;wBACzB,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC;wBAC/B,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACpB,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA2B,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,wBAAwB,EAAE,EAAE,CAAC,CAAC;gBACvF,OAAO;YACT,CAAC;YAED,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;gBACpB,KAAK,MAAM;oBACT,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;wBAC/C,WAAW,CAAC;4BACV,IAAI,EAAE,OAAO;4BACb,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,IAAI;4BACrB,KAAK,EAAE,EAAE,OAAO,EAAE,oCAAoC,EAAE;yBACzD,CAAC,CAAC;wBACH,OAAO;oBACT,CAAC;oBACD,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;oBACxC,MAAM;gBAER,KAAK,QAAQ;oBACX,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;oBACtC,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBAChC,MAAM;gBAER,KAAK,MAAM;oBACT,MAAM;gBAER;oBACE,WAAW,CAAC;wBACV,IAAI,EAAE,OAAO;wBACb,EAAE,EAAE,IAAI;wBACR,KAAK,EAAE;4BACL,OAAO,EAAE,yBAAyB,MAAM,CAAE,MAA4B,CAAC,IAAI,CAAC,EAAE;yBAC/E;qBACF,CAAC,CAAC;YACP,CAAC;QACH,CAAC,CAAC;QAEF,mEAAmE;QACnE,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YAChD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9B,CAAC;aAAM,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACnC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAC3C,SAAS,CAAE,KAA4B,CAAC,IAAI,IAAI,KAAK,CAAC,CACvD,CAAC;YACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC1C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAgB,qBAAqB,CACnC,MAAoF,EACpF,MAAc,EACd,OAAiC;IAEjC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * WebSocket Streaming
3
+ *
4
+ * Transport-agnostic WebSocket handler for real-time AI chat. Zero-dependency
5
+ * by design: instead of bundling a WebSocket implementation, it accepts any
6
+ * socket satisfying the structural {@link WebSocketLike} interface — Node
7
+ * `ws` sockets, `Deno.upgradeWebSocket` sockets, Bun sockets, or browser
8
+ * WebSockets all qualify.
9
+ *
10
+ * Wire protocol (JSON text frames):
11
+ * - client → server: `{ type: 'chat', id, request }`, `{ type: 'cancel', id }`
12
+ * - server → client: `{ type: 'start' | 'chunk' | 'done' | 'error', id, ... }`
13
+ * plus periodic `{ type: 'ping' }` heartbeats
14
+ *
15
+ * @module
16
+ */
17
+ /**
18
+ * Create a per-connection handler streaming chat over a WebSocket.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { WebSocketServer } from 'ws';
23
+ * import { createWebSocketHandler } from 'ai.matey.http/websocket';
24
+ *
25
+ * const handleSocket = createWebSocketHandler(bridge);
26
+ * new WebSocketServer({ port: 8080 }).on('connection', handleSocket);
27
+ * ```
28
+ */
29
+ export function createWebSocketHandler(bridge, options = {}) {
30
+ const maxConcurrentStreams = options.maxConcurrentStreams ?? 5;
31
+ const heartbeatMs = options.heartbeatMs ?? 30000;
32
+ return (socket) => {
33
+ const activeStreams = new Map();
34
+ const sendMessage = (message) => {
35
+ try {
36
+ socket.send(JSON.stringify(message));
37
+ }
38
+ catch (error) {
39
+ options.onError?.(error instanceof Error ? error : new Error(String(error)));
40
+ }
41
+ };
42
+ let heartbeat;
43
+ if (heartbeatMs > 0) {
44
+ heartbeat = setInterval(() => sendMessage({ type: 'ping' }), heartbeatMs);
45
+ if (typeof heartbeat === 'object' && 'unref' in heartbeat) {
46
+ heartbeat.unref();
47
+ }
48
+ }
49
+ const cleanup = () => {
50
+ if (heartbeat) {
51
+ clearInterval(heartbeat);
52
+ }
53
+ for (const controller of activeStreams.values()) {
54
+ controller.abort();
55
+ }
56
+ activeStreams.clear();
57
+ };
58
+ const runChat = async (id, request) => {
59
+ if (activeStreams.size >= maxConcurrentStreams) {
60
+ sendMessage({
61
+ type: 'error',
62
+ id,
63
+ error: { message: `Too many concurrent streams (max ${maxConcurrentStreams})` },
64
+ });
65
+ return;
66
+ }
67
+ const controller = new AbortController();
68
+ activeStreams.set(id, controller);
69
+ sendMessage({ type: 'start', id });
70
+ try {
71
+ const stream = bridge.chatStream(request, { signal: controller.signal });
72
+ for await (const chunk of stream) {
73
+ if (controller.signal.aborted) {
74
+ break;
75
+ }
76
+ sendMessage({ type: 'chunk', id, chunk });
77
+ }
78
+ if (!controller.signal.aborted) {
79
+ sendMessage({ type: 'done', id });
80
+ }
81
+ }
82
+ catch (error) {
83
+ if (!controller.signal.aborted) {
84
+ sendMessage({
85
+ type: 'error',
86
+ id,
87
+ error: { message: error instanceof Error ? error.message : String(error) },
88
+ });
89
+ }
90
+ }
91
+ finally {
92
+ activeStreams.delete(id);
93
+ }
94
+ };
95
+ const onMessage = (raw) => {
96
+ let parsed;
97
+ try {
98
+ const text = typeof raw === 'string'
99
+ ? raw
100
+ : raw instanceof Uint8Array
101
+ ? new TextDecoder().decode(raw)
102
+ : String(raw);
103
+ parsed = JSON.parse(text);
104
+ }
105
+ catch {
106
+ sendMessage({ type: 'error', id: null, error: { message: 'Malformed JSON message' } });
107
+ return;
108
+ }
109
+ switch (parsed.type) {
110
+ case 'chat':
111
+ if (!parsed.id || parsed.request === undefined) {
112
+ sendMessage({
113
+ type: 'error',
114
+ id: parsed.id ?? null,
115
+ error: { message: "'chat' requires 'id' and 'request'" },
116
+ });
117
+ return;
118
+ }
119
+ void runChat(parsed.id, parsed.request);
120
+ break;
121
+ case 'cancel':
122
+ activeStreams.get(parsed.id)?.abort();
123
+ activeStreams.delete(parsed.id);
124
+ break;
125
+ case 'pong':
126
+ break;
127
+ default:
128
+ sendMessage({
129
+ type: 'error',
130
+ id: null,
131
+ error: {
132
+ message: `Unknown message type: ${String(parsed.type)}`,
133
+ },
134
+ });
135
+ }
136
+ };
137
+ // Support both listener styles; extract data from DOM MessageEvent
138
+ if (socket.on) {
139
+ socket.on('message', (data) => onMessage(data));
140
+ socket.on('close', cleanup);
141
+ socket.on('error', cleanup);
142
+ }
143
+ else if (socket.addEventListener) {
144
+ socket.addEventListener('message', (event) => onMessage(event.data ?? event));
145
+ socket.addEventListener('close', cleanup);
146
+ socket.addEventListener('error', cleanup);
147
+ }
148
+ };
149
+ }
150
+ /**
151
+ * Attach the handler to a `ws`-style server's connection event.
152
+ */
153
+ export function attachWebSocketServer(server, bridge, options) {
154
+ server.on('connection', createWebSocketHandler(bridge, options));
155
+ }
156
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/websocket/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * WebSocket Streaming
3
+ *
4
+ * Transport-agnostic WebSocket handler for real-time AI chat. Zero-dependency
5
+ * by design: instead of bundling a WebSocket implementation, it accepts any
6
+ * socket satisfying the structural {@link WebSocketLike} interface — Node
7
+ * `ws` sockets, `Deno.upgradeWebSocket` sockets, Bun sockets, or browser
8
+ * WebSockets all qualify.
9
+ *
10
+ * Wire protocol (JSON text frames):
11
+ * - client → server: `{ type: 'chat', id, request }`, `{ type: 'cancel', id }`
12
+ * - server → client: `{ type: 'start' | 'chunk' | 'done' | 'error', id, ... }`
13
+ * plus periodic `{ type: 'ping' }` heartbeats
14
+ *
15
+ * @module
16
+ */
17
+ import type { Bridge } from 'ai.matey.core';
18
+ /**
19
+ * Structural WebSocket interface (satisfied by ws, Deno, Bun, browsers).
20
+ */
21
+ export interface WebSocketLike {
22
+ send(data: string): void;
23
+ close(code?: number, reason?: string): void;
24
+ /** DOM-style listener registration. */
25
+ addEventListener?(type: 'message' | 'close' | 'error', listener: (event: unknown) => void): void;
26
+ /** Node ws-style listener registration. */
27
+ on?(type: 'message' | 'close' | 'error', listener: (...args: unknown[]) => void): void;
28
+ }
29
+ /**
30
+ * Client → server messages.
31
+ */
32
+ export type WebSocketClientMessage = {
33
+ type: 'chat';
34
+ id: string;
35
+ request: unknown;
36
+ } | {
37
+ type: 'cancel';
38
+ id: string;
39
+ } | {
40
+ type: 'pong';
41
+ };
42
+ /**
43
+ * Server → client messages.
44
+ */
45
+ export type WebSocketServerMessage = {
46
+ type: 'start';
47
+ id: string;
48
+ } | {
49
+ type: 'chunk';
50
+ id: string;
51
+ chunk: unknown;
52
+ } | {
53
+ type: 'done';
54
+ id: string;
55
+ } | {
56
+ type: 'error';
57
+ id: string | null;
58
+ error: {
59
+ message: string;
60
+ code?: string;
61
+ };
62
+ } | {
63
+ type: 'ping';
64
+ };
65
+ /**
66
+ * Options for the WebSocket handler.
67
+ */
68
+ export interface WebSocketHandlerOptions {
69
+ /** Maximum concurrent streams per connection. @default 5 */
70
+ maxConcurrentStreams?: number;
71
+ /** Heartbeat interval in milliseconds (0 disables). @default 30000 */
72
+ heartbeatMs?: number;
73
+ /** Error observability hook. */
74
+ onError?: (error: Error) => void;
75
+ }
76
+ /**
77
+ * Create a per-connection handler streaming chat over a WebSocket.
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * import { WebSocketServer } from 'ws';
82
+ * import { createWebSocketHandler } from 'ai.matey.http/websocket';
83
+ *
84
+ * const handleSocket = createWebSocketHandler(bridge);
85
+ * new WebSocketServer({ port: 8080 }).on('connection', handleSocket);
86
+ * ```
87
+ */
88
+ export declare function createWebSocketHandler(bridge: Bridge, options?: WebSocketHandlerOptions): (socket: WebSocketLike) => void;
89
+ /**
90
+ * Attach the handler to a `ws`-style server's connection event.
91
+ */
92
+ export declare function attachWebSocketServer(server: {
93
+ on(event: 'connection', listener: (socket: WebSocketLike) => void): void;
94
+ }, bridge: Bridge, options?: WebSocketHandlerOptions): void;
95
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/websocket/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,uCAAuC;IACvC,gBAAgB,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IACjG,2CAA2C;IAC3C,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CACxF;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC/E;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,gCAAgC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,uBAA4B,GACpC,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAmIjC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE;IAAE,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI,CAAA;CAAE,EACpF,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAEN"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai.matey.http",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "HTTP framework adapters for AI Matey - Universal AI Adapter System",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -76,6 +76,16 @@
76
76
  "types": "./dist/types/deno/index.d.ts",
77
77
  "default": "./dist/cjs/deno/index.js"
78
78
  }
79
+ },
80
+ "./websocket": {
81
+ "import": {
82
+ "types": "./dist/types/websocket/index.d.ts",
83
+ "default": "./dist/esm/websocket/index.js"
84
+ },
85
+ "require": {
86
+ "types": "./dist/types/websocket/index.d.ts",
87
+ "default": "./dist/cjs/websocket/index.js"
88
+ }
79
89
  }
80
90
  },
81
91
  "files": [