@cephalization/phoenix-insight 1.0.0 → 1.0.3

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.
@@ -1,212 +0,0 @@
1
- /**
2
- * WebSocket server for Phoenix Insight CLI.
3
- * Provides bidirectional communication between the CLI agent and the web UI.
4
- *
5
- * Binds to localhost only for security (no external network access).
6
- * Handles HTTP upgrade requests, manages client connections, and broadcasts messages.
7
- */
8
- import { WebSocketServer, WebSocket } from "ws";
9
- // ============================================================================
10
- // Phoenix WebSocket Server
11
- // ============================================================================
12
- /**
13
- * Phoenix WebSocket server wrapper providing typed message handling
14
- * and connection management.
15
- */
16
- export class PhoenixWebSocketServer {
17
- wss = null;
18
- clients = new Set();
19
- options;
20
- constructor(options = {}) {
21
- this.options = {
22
- path: "/ws",
23
- onMessage: () => { },
24
- onConnection: () => { },
25
- onDisconnection: () => { },
26
- onError: () => { },
27
- ...options,
28
- };
29
- }
30
- /**
31
- * Attach the WebSocket server to an existing HTTP server.
32
- * Uses the upgrade event to handle WebSocket handshakes.
33
- */
34
- attach(httpServer) {
35
- if (this.wss) {
36
- throw new Error("WebSocket server is already attached");
37
- }
38
- // Create WebSocket server with noServer mode to handle upgrade ourselves
39
- this.wss = new WebSocketServer({ noServer: true });
40
- // Handle HTTP upgrade requests
41
- httpServer.on("upgrade", (request, socket, head) => {
42
- this.handleUpgrade(request, socket, head);
43
- });
44
- // Set up WebSocket server event handlers
45
- this.setupEventHandlers();
46
- }
47
- /**
48
- * Handle HTTP upgrade request for WebSocket connection.
49
- * Only accepts connections on the configured path and from localhost.
50
- */
51
- handleUpgrade(request, socket, head) {
52
- if (!this.wss) {
53
- socket.destroy();
54
- return;
55
- }
56
- // Parse the request URL
57
- const url = new URL(request.url ?? "/", `http://${request.headers.host}`);
58
- // Only accept connections on the configured path
59
- if (url.pathname !== this.options.path) {
60
- socket.destroy();
61
- return;
62
- }
63
- // Complete the WebSocket handshake
64
- this.wss.handleUpgrade(request, socket, head, (ws) => {
65
- this.wss?.emit("connection", ws, request);
66
- });
67
- }
68
- /**
69
- * Set up event handlers for the WebSocket server.
70
- */
71
- setupEventHandlers() {
72
- if (!this.wss)
73
- return;
74
- this.wss.on("connection", (ws) => {
75
- this.clients.add(ws);
76
- this.options.onConnection(ws);
77
- ws.on("message", (data) => {
78
- this.handleMessage(data, ws);
79
- });
80
- ws.on("close", (code, reason) => {
81
- this.clients.delete(ws);
82
- this.options.onDisconnection(ws, code, reason.toString());
83
- });
84
- ws.on("error", (error) => {
85
- this.options.onError(error, ws);
86
- });
87
- });
88
- this.wss.on("error", (error) => {
89
- this.options.onError(error);
90
- });
91
- }
92
- /**
93
- * Handle incoming message from a client.
94
- * Parses JSON and validates message structure before calling handler.
95
- */
96
- handleMessage(data, client) {
97
- try {
98
- const rawMessage = data.toString();
99
- const parsed = JSON.parse(rawMessage);
100
- // Basic validation of message structure
101
- if (!parsed || typeof parsed !== "object") {
102
- throw new Error("Invalid message structure: expected object");
103
- }
104
- const obj = parsed;
105
- if (!("type" in obj) || typeof obj.type !== "string") {
106
- throw new Error("Invalid message structure: missing type field");
107
- }
108
- const messageType = obj.type;
109
- if (messageType !== "query" && messageType !== "cancel") {
110
- throw new Error(`Unknown message type: ${messageType}`);
111
- }
112
- // Now we know this is a valid ClientMessage
113
- const message = parsed;
114
- this.options.onMessage(message, client);
115
- }
116
- catch (error) {
117
- // Send error message back to the client
118
- const errorMessage = {
119
- type: "error",
120
- payload: {
121
- message: error instanceof Error
122
- ? error.message
123
- : "Failed to parse message",
124
- },
125
- };
126
- this.sendToClient(client, errorMessage);
127
- }
128
- }
129
- /**
130
- * Send a message to a specific client.
131
- */
132
- sendToClient(client, message) {
133
- if (client.readyState === WebSocket.OPEN) {
134
- client.send(JSON.stringify(message));
135
- }
136
- }
137
- /**
138
- * Broadcast a message to all connected clients.
139
- */
140
- broadcast(message) {
141
- const data = JSON.stringify(message);
142
- for (const client of this.clients) {
143
- if (client.readyState === WebSocket.OPEN) {
144
- client.send(data);
145
- }
146
- }
147
- }
148
- /**
149
- * Broadcast a message to all clients with a specific session ID.
150
- * This requires tracking session-to-client mapping externally.
151
- * For now, it broadcasts to all clients (to be refined in cli-agent-session).
152
- */
153
- broadcastToSession(sessionId, message) {
154
- // For now, broadcast to all clients.
155
- // Session-to-client mapping will be implemented in cli-agent-session task.
156
- this.broadcast(message);
157
- }
158
- /**
159
- * Get the number of connected clients.
160
- */
161
- get clientCount() {
162
- return this.clients.size;
163
- }
164
- /**
165
- * Get all connected clients.
166
- */
167
- getClients() {
168
- return new Set(this.clients);
169
- }
170
- /**
171
- * Close the WebSocket server and disconnect all clients.
172
- */
173
- close() {
174
- return new Promise((resolve, reject) => {
175
- if (!this.wss) {
176
- resolve();
177
- return;
178
- }
179
- // Close all client connections
180
- for (const client of this.clients) {
181
- client.close(1000, "Server shutting down");
182
- }
183
- this.clients.clear();
184
- // Close the WebSocket server
185
- this.wss.close((err) => {
186
- this.wss = null;
187
- if (err) {
188
- reject(err);
189
- }
190
- else {
191
- resolve();
192
- }
193
- });
194
- });
195
- }
196
- }
197
- // ============================================================================
198
- // Factory Function
199
- // ============================================================================
200
- /**
201
- * Create and attach a WebSocket server to an HTTP server.
202
- * The WebSocket server binds to localhost only (through the HTTP server).
203
- *
204
- * @param httpServer - HTTP server to attach WebSocket handling to
205
- * @param options - WebSocket server options
206
- * @returns PhoenixWebSocketServer instance
207
- */
208
- export function createWebSocketServer(httpServer, options) {
209
- const server = new PhoenixWebSocketServer(options);
210
- server.attach(httpServer);
211
- return server;
212
- }
@@ -1,74 +0,0 @@
1
- import { createClient } from "@arizeai/phoenix-client";
2
- export class PhoenixClientError extends Error {
3
- code;
4
- originalError;
5
- constructor(message, code, originalError) {
6
- super(message);
7
- this.name = "PhoenixClientError";
8
- this.code = code;
9
- this.originalError = originalError;
10
- }
11
- }
12
- /**
13
- * Creates a wrapped Phoenix client with error handling
14
- */
15
- export function createPhoenixClient(config = {}) {
16
- const headers = {};
17
- if (config.apiKey) {
18
- headers["api_key"] = config.apiKey;
19
- }
20
- const clientOptions = {
21
- options: {
22
- baseUrl: config.baseURL,
23
- headers: Object.keys(headers).length > 0 ? headers : undefined,
24
- },
25
- };
26
- return createClient(clientOptions);
27
- }
28
- /**
29
- * Wraps an async operation with standardized error handling
30
- */
31
- export async function withErrorHandling(operation, context) {
32
- try {
33
- return await operation();
34
- }
35
- catch (error) {
36
- // Network errors
37
- if (error instanceof TypeError && error.message.includes("fetch")) {
38
- throw new PhoenixClientError(`Network error during ${context}: Unable to connect to Phoenix server`, "NETWORK_ERROR", error);
39
- }
40
- // HTTP errors from the middleware
41
- if (error instanceof Error && error.message.includes(": ")) {
42
- const parts = error.message.split(": ", 2);
43
- if (parts.length === 2 && parts[1]) {
44
- const [url, statusInfo] = parts;
45
- const statusParts = statusInfo.split(" ");
46
- const statusCode = statusParts[0];
47
- const statusText = statusParts.slice(1).join(" ");
48
- if (statusCode === "401" || statusCode === "403") {
49
- throw new PhoenixClientError(`Authentication error during ${context}: ${statusText}`, "AUTH_ERROR", error);
50
- }
51
- if (statusCode && statusCode.startsWith("4")) {
52
- throw new PhoenixClientError(`Client error during ${context}: ${statusCode} ${statusText}`, "INVALID_RESPONSE", error);
53
- }
54
- if (statusCode && statusCode.startsWith("5")) {
55
- throw new PhoenixClientError(`Server error during ${context}: ${statusCode} ${statusText}`, "NETWORK_ERROR", error);
56
- }
57
- }
58
- }
59
- // Unknown errors
60
- throw new PhoenixClientError(`Unexpected error during ${context}: ${error instanceof Error ? error.message : String(error)}`, "UNKNOWN_ERROR", error);
61
- }
62
- }
63
- /**
64
- * Helper to safely extract data from API responses
65
- */
66
- export function extractData(response) {
67
- if (response.error) {
68
- throw response.error;
69
- }
70
- if (!response.data) {
71
- throw new PhoenixClientError("Invalid API response: missing data", "INVALID_RESPONSE");
72
- }
73
- return response.data;
74
- }