@cmdop/react 0.1.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/README.md +271 -0
- package/dist/index.cjs +770 -0
- package/dist/index.d.cts +538 -0
- package/dist/index.d.ts +538 -0
- package/dist/index.js +732 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/index.tsx
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_CONFIG,
|
|
6
|
+
VERSION,
|
|
7
|
+
CMDOPError,
|
|
8
|
+
ConnectionError,
|
|
9
|
+
AuthenticationError,
|
|
10
|
+
SessionError,
|
|
11
|
+
TimeoutError,
|
|
12
|
+
NotFoundError,
|
|
13
|
+
PermissionError,
|
|
14
|
+
ResourceExhaustedError,
|
|
15
|
+
CancelledError,
|
|
16
|
+
UnavailableError,
|
|
17
|
+
api,
|
|
18
|
+
machines,
|
|
19
|
+
workspaces,
|
|
20
|
+
system,
|
|
21
|
+
API_BASE_URL,
|
|
22
|
+
MachinesModule,
|
|
23
|
+
WorkspacesModule,
|
|
24
|
+
SystemModule
|
|
25
|
+
} from "@cmdop/core";
|
|
26
|
+
|
|
27
|
+
// src/centrifugo/client.ts
|
|
28
|
+
import { Centrifuge } from "centrifuge";
|
|
29
|
+
var CMDOPWebSocketClient = class {
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
centrifuge = null;
|
|
34
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
35
|
+
stateListeners = /* @__PURE__ */ new Set();
|
|
36
|
+
state = {
|
|
37
|
+
isConnected: false,
|
|
38
|
+
isConnecting: false,
|
|
39
|
+
error: null
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Connect to Centrifugo server
|
|
43
|
+
*/
|
|
44
|
+
async connect() {
|
|
45
|
+
if (this.centrifuge) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.updateState({ isConnecting: true, error: null });
|
|
49
|
+
try {
|
|
50
|
+
const token = await this.config.getToken();
|
|
51
|
+
this.centrifuge = new Centrifuge(this.config.url, {
|
|
52
|
+
token,
|
|
53
|
+
getToken: async () => {
|
|
54
|
+
return this.config.getToken();
|
|
55
|
+
},
|
|
56
|
+
debug: this.config.debug ?? false
|
|
57
|
+
});
|
|
58
|
+
this.centrifuge.on("connected", () => {
|
|
59
|
+
this.updateState({ isConnected: true, isConnecting: false, error: null });
|
|
60
|
+
});
|
|
61
|
+
this.centrifuge.on("disconnected", () => {
|
|
62
|
+
this.updateState({ isConnected: false, isConnecting: false });
|
|
63
|
+
});
|
|
64
|
+
this.centrifuge.on("error", (ctx) => {
|
|
65
|
+
this.updateState({ error: new Error(ctx.error?.message ?? "Connection error") });
|
|
66
|
+
});
|
|
67
|
+
this.centrifuge.connect();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
this.updateState({
|
|
70
|
+
isConnecting: false,
|
|
71
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
72
|
+
});
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Disconnect from server
|
|
78
|
+
*/
|
|
79
|
+
disconnect() {
|
|
80
|
+
if (this.centrifuge) {
|
|
81
|
+
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
|
82
|
+
this.subscriptions.clear();
|
|
83
|
+
this.centrifuge.disconnect();
|
|
84
|
+
this.centrifuge = null;
|
|
85
|
+
this.updateState({ isConnected: false, isConnecting: false });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to a channel
|
|
90
|
+
*/
|
|
91
|
+
subscribe(channel, onPublication, onError) {
|
|
92
|
+
if (!this.centrifuge) {
|
|
93
|
+
throw new Error("Not connected");
|
|
94
|
+
}
|
|
95
|
+
if (this.subscriptions.has(channel)) {
|
|
96
|
+
const existing = this.subscriptions.get(channel);
|
|
97
|
+
existing.on("publication", (ctx) => {
|
|
98
|
+
onPublication(ctx.data);
|
|
99
|
+
});
|
|
100
|
+
return () => this.unsubscribe(channel);
|
|
101
|
+
}
|
|
102
|
+
const subscription = this.centrifuge.newSubscription(channel);
|
|
103
|
+
subscription.on("publication", (ctx) => {
|
|
104
|
+
onPublication(ctx.data);
|
|
105
|
+
});
|
|
106
|
+
subscription.on("error", (ctx) => {
|
|
107
|
+
onError?.(new Error(ctx.error?.message ?? "Subscription error"));
|
|
108
|
+
});
|
|
109
|
+
subscription.subscribe();
|
|
110
|
+
this.subscriptions.set(channel, subscription);
|
|
111
|
+
return () => this.unsubscribe(channel);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Unsubscribe from a channel
|
|
115
|
+
*/
|
|
116
|
+
unsubscribe(channel) {
|
|
117
|
+
const subscription = this.subscriptions.get(channel);
|
|
118
|
+
if (subscription) {
|
|
119
|
+
subscription.unsubscribe();
|
|
120
|
+
this.subscriptions.delete(channel);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Make an RPC call via Centrifugo
|
|
125
|
+
*/
|
|
126
|
+
async rpc(method, data) {
|
|
127
|
+
if (!this.centrifuge) {
|
|
128
|
+
throw new Error("Not connected");
|
|
129
|
+
}
|
|
130
|
+
const result = await this.centrifuge.rpc(method, data);
|
|
131
|
+
return result.data;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Publish to a channel (fire-and-forget)
|
|
135
|
+
*/
|
|
136
|
+
async publish(channel, data) {
|
|
137
|
+
if (!this.centrifuge) {
|
|
138
|
+
throw new Error("Not connected");
|
|
139
|
+
}
|
|
140
|
+
const subscription = this.subscriptions.get(channel);
|
|
141
|
+
if (subscription) {
|
|
142
|
+
await subscription.publish(data);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get current connection state
|
|
147
|
+
*/
|
|
148
|
+
getState() {
|
|
149
|
+
return { ...this.state };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Add state change listener
|
|
153
|
+
*/
|
|
154
|
+
onStateChange(listener) {
|
|
155
|
+
this.stateListeners.add(listener);
|
|
156
|
+
return () => this.stateListeners.delete(listener);
|
|
157
|
+
}
|
|
158
|
+
updateState(partial) {
|
|
159
|
+
this.state = { ...this.state, ...partial };
|
|
160
|
+
this.stateListeners.forEach((listener) => listener(this.state));
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/centrifugo/provider.tsx
|
|
165
|
+
import {
|
|
166
|
+
createContext,
|
|
167
|
+
useContext,
|
|
168
|
+
useEffect,
|
|
169
|
+
useState,
|
|
170
|
+
useCallback,
|
|
171
|
+
useRef
|
|
172
|
+
} from "react";
|
|
173
|
+
import { jsx } from "react/jsx-runtime";
|
|
174
|
+
var WebSocketContext = createContext(null);
|
|
175
|
+
function WebSocketProvider({
|
|
176
|
+
children,
|
|
177
|
+
url,
|
|
178
|
+
getToken,
|
|
179
|
+
autoConnect = true,
|
|
180
|
+
debug = false
|
|
181
|
+
}) {
|
|
182
|
+
const clientRef = useRef(null);
|
|
183
|
+
const [state, setState] = useState({
|
|
184
|
+
isConnected: false,
|
|
185
|
+
isConnecting: false,
|
|
186
|
+
error: null
|
|
187
|
+
});
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
const client = new CMDOPWebSocketClient({
|
|
190
|
+
url,
|
|
191
|
+
getToken,
|
|
192
|
+
debug
|
|
193
|
+
});
|
|
194
|
+
clientRef.current = client;
|
|
195
|
+
const unsubscribe = client.onStateChange(setState);
|
|
196
|
+
if (autoConnect) {
|
|
197
|
+
client.connect().catch((error) => {
|
|
198
|
+
console.error("[CMDOP WebSocket] Auto-connect failed:", error);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return () => {
|
|
202
|
+
unsubscribe();
|
|
203
|
+
client.disconnect();
|
|
204
|
+
clientRef.current = null;
|
|
205
|
+
};
|
|
206
|
+
}, [url, getToken, autoConnect, debug]);
|
|
207
|
+
const connect = useCallback(async () => {
|
|
208
|
+
if (clientRef.current) {
|
|
209
|
+
await clientRef.current.connect();
|
|
210
|
+
}
|
|
211
|
+
}, []);
|
|
212
|
+
const disconnect = useCallback(() => {
|
|
213
|
+
if (clientRef.current) {
|
|
214
|
+
clientRef.current.disconnect();
|
|
215
|
+
}
|
|
216
|
+
}, []);
|
|
217
|
+
const value = {
|
|
218
|
+
client: clientRef.current,
|
|
219
|
+
isConnected: state.isConnected,
|
|
220
|
+
isConnecting: state.isConnecting,
|
|
221
|
+
error: state.error,
|
|
222
|
+
connect,
|
|
223
|
+
disconnect
|
|
224
|
+
};
|
|
225
|
+
return /* @__PURE__ */ jsx(WebSocketContext.Provider, { value, children });
|
|
226
|
+
}
|
|
227
|
+
function useWebSocket() {
|
|
228
|
+
const context = useContext(WebSocketContext);
|
|
229
|
+
if (!context) {
|
|
230
|
+
throw new Error("useWebSocket must be used within a WebSocketProvider");
|
|
231
|
+
}
|
|
232
|
+
return context;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/centrifugo/hooks.ts
|
|
236
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
237
|
+
function useSubscription(options) {
|
|
238
|
+
const { channel, enabled = true, onData, onError } = options;
|
|
239
|
+
const { client, isConnected } = useWebSocket();
|
|
240
|
+
const [data, setData] = useState2(null);
|
|
241
|
+
const [error, setError] = useState2(null);
|
|
242
|
+
const [isSubscribed, setIsSubscribed] = useState2(false);
|
|
243
|
+
const onDataRef = useRef2(onData);
|
|
244
|
+
const onErrorRef = useRef2(onError);
|
|
245
|
+
onDataRef.current = onData;
|
|
246
|
+
onErrorRef.current = onError;
|
|
247
|
+
useEffect2(() => {
|
|
248
|
+
if (!client || !isConnected || !enabled || !channel) {
|
|
249
|
+
setIsSubscribed(false);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const handleData = (received) => {
|
|
253
|
+
setData(received);
|
|
254
|
+
onDataRef.current?.(received);
|
|
255
|
+
};
|
|
256
|
+
const handleError = (err) => {
|
|
257
|
+
setError(err);
|
|
258
|
+
onErrorRef.current?.(err);
|
|
259
|
+
};
|
|
260
|
+
try {
|
|
261
|
+
const unsubscribe = client.subscribe(channel, handleData, handleError);
|
|
262
|
+
setIsSubscribed(true);
|
|
263
|
+
setError(null);
|
|
264
|
+
return () => {
|
|
265
|
+
unsubscribe();
|
|
266
|
+
setIsSubscribed(false);
|
|
267
|
+
};
|
|
268
|
+
} catch (err) {
|
|
269
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
270
|
+
setError(error2);
|
|
271
|
+
onErrorRef.current?.(error2);
|
|
272
|
+
}
|
|
273
|
+
}, [client, isConnected, enabled, channel]);
|
|
274
|
+
return { data, error, isSubscribed };
|
|
275
|
+
}
|
|
276
|
+
function useRPC(options = {}) {
|
|
277
|
+
const { onError } = options;
|
|
278
|
+
const { client, isConnected } = useWebSocket();
|
|
279
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
280
|
+
const [error, setError] = useState2(null);
|
|
281
|
+
const onErrorRef = useRef2(onError);
|
|
282
|
+
onErrorRef.current = onError;
|
|
283
|
+
const call = useCallback2(
|
|
284
|
+
async (method, data) => {
|
|
285
|
+
if (!client || !isConnected) {
|
|
286
|
+
const err = new Error("WebSocket not connected");
|
|
287
|
+
setError(err);
|
|
288
|
+
onErrorRef.current?.(err);
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
291
|
+
setIsLoading(true);
|
|
292
|
+
setError(null);
|
|
293
|
+
try {
|
|
294
|
+
const result = await client.rpc(method, data);
|
|
295
|
+
return result;
|
|
296
|
+
} catch (err) {
|
|
297
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
298
|
+
setError(error2);
|
|
299
|
+
onErrorRef.current?.(error2);
|
|
300
|
+
throw error2;
|
|
301
|
+
} finally {
|
|
302
|
+
setIsLoading(false);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
[client, isConnected]
|
|
306
|
+
);
|
|
307
|
+
const reset = useCallback2(() => {
|
|
308
|
+
setError(null);
|
|
309
|
+
}, []);
|
|
310
|
+
return { call, isLoading, error, reset };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/hooks/useTerminal.ts
|
|
314
|
+
import { useState as useState3, useCallback as useCallback3, useRef as useRef3 } from "react";
|
|
315
|
+
function useTerminal(options) {
|
|
316
|
+
const { sessionId, enabled = true, onOutput, onStatus, onError } = options;
|
|
317
|
+
const [output, setOutput] = useState3("");
|
|
318
|
+
const [status, setStatus] = useState3(null);
|
|
319
|
+
const [error, setError] = useState3(null);
|
|
320
|
+
const [isConnecting, setIsConnecting] = useState3(false);
|
|
321
|
+
const onOutputRef = useRef3(onOutput);
|
|
322
|
+
const onStatusRef = useRef3(onStatus);
|
|
323
|
+
const onErrorRef = useRef3(onError);
|
|
324
|
+
onOutputRef.current = onOutput;
|
|
325
|
+
onStatusRef.current = onStatus;
|
|
326
|
+
onErrorRef.current = onError;
|
|
327
|
+
const { call, isLoading: isRpcLoading } = useRPC({
|
|
328
|
+
onError: (err) => {
|
|
329
|
+
setError(err);
|
|
330
|
+
onErrorRef.current?.(err);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
const { isSubscribed: isOutputSubscribed } = useSubscription({
|
|
334
|
+
channel: `terminal#${sessionId}#output`,
|
|
335
|
+
enabled: enabled && !!sessionId,
|
|
336
|
+
onData: (data) => {
|
|
337
|
+
const text = data.data;
|
|
338
|
+
setOutput((prev) => prev + text);
|
|
339
|
+
onOutputRef.current?.(text);
|
|
340
|
+
},
|
|
341
|
+
onError: (err) => {
|
|
342
|
+
setError(err);
|
|
343
|
+
onErrorRef.current?.(err);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
const { isSubscribed: isStatusSubscribed } = useSubscription({
|
|
347
|
+
channel: `terminal#${sessionId}#status`,
|
|
348
|
+
enabled: enabled && !!sessionId,
|
|
349
|
+
onData: (newStatus) => {
|
|
350
|
+
setStatus(newStatus);
|
|
351
|
+
onStatusRef.current?.(newStatus);
|
|
352
|
+
},
|
|
353
|
+
onError: (err) => {
|
|
354
|
+
setError(err);
|
|
355
|
+
onErrorRef.current?.(err);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
const isConnected = isOutputSubscribed && isStatusSubscribed;
|
|
359
|
+
const sendInput = useCallback3(
|
|
360
|
+
async (data) => {
|
|
361
|
+
if (!sessionId) return;
|
|
362
|
+
await call("terminal.input", {
|
|
363
|
+
session_id: sessionId,
|
|
364
|
+
data
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
[call, sessionId]
|
|
368
|
+
);
|
|
369
|
+
const resize = useCallback3(
|
|
370
|
+
async (cols, rows) => {
|
|
371
|
+
if (!sessionId) return;
|
|
372
|
+
await call("terminal.resize", {
|
|
373
|
+
session_id: sessionId,
|
|
374
|
+
cols,
|
|
375
|
+
rows
|
|
376
|
+
});
|
|
377
|
+
},
|
|
378
|
+
[call, sessionId]
|
|
379
|
+
);
|
|
380
|
+
const signal = useCallback3(
|
|
381
|
+
async (sig) => {
|
|
382
|
+
if (!sessionId) return;
|
|
383
|
+
const signalNum = typeof sig === "string" ? signalNameToNumber(sig) : sig;
|
|
384
|
+
await call("terminal.signal", {
|
|
385
|
+
session_id: sessionId,
|
|
386
|
+
signal: signalNum
|
|
387
|
+
});
|
|
388
|
+
},
|
|
389
|
+
[call, sessionId]
|
|
390
|
+
);
|
|
391
|
+
const clear = useCallback3(() => {
|
|
392
|
+
setOutput("");
|
|
393
|
+
}, []);
|
|
394
|
+
return {
|
|
395
|
+
isConnected,
|
|
396
|
+
isConnecting: isConnecting || isRpcLoading,
|
|
397
|
+
error,
|
|
398
|
+
output,
|
|
399
|
+
status,
|
|
400
|
+
sendInput,
|
|
401
|
+
resize,
|
|
402
|
+
signal,
|
|
403
|
+
clear
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
var SIGNAL_MAP = {
|
|
407
|
+
SIGHUP: 1,
|
|
408
|
+
SIGINT: 2,
|
|
409
|
+
SIGQUIT: 3,
|
|
410
|
+
SIGKILL: 9,
|
|
411
|
+
SIGTERM: 15,
|
|
412
|
+
SIGSTOP: 19,
|
|
413
|
+
SIGCONT: 18
|
|
414
|
+
};
|
|
415
|
+
function signalNameToNumber(name) {
|
|
416
|
+
const upper = name.toUpperCase();
|
|
417
|
+
return SIGNAL_MAP[upper] ?? parseInt(name, 10) ?? 15;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/hooks/useAgent.ts
|
|
421
|
+
import { useState as useState4, useCallback as useCallback4, useRef as useRef4 } from "react";
|
|
422
|
+
function useAgent(options) {
|
|
423
|
+
const { sessionId, enabled = true, onToken, onToolCall, onToolResult, onDone, onError } = options;
|
|
424
|
+
const [isRunning, setIsRunning] = useState4(false);
|
|
425
|
+
const [streamingText, setStreamingText] = useState4("");
|
|
426
|
+
const [result, setResult] = useState4(null);
|
|
427
|
+
const [toolCalls, setToolCalls] = useState4([]);
|
|
428
|
+
const [error, setError] = useState4(null);
|
|
429
|
+
const [requestId, setRequestId] = useState4(null);
|
|
430
|
+
const onTokenRef = useRef4(onToken);
|
|
431
|
+
const onToolCallRef = useRef4(onToolCall);
|
|
432
|
+
const onToolResultRef = useRef4(onToolResult);
|
|
433
|
+
const onDoneRef = useRef4(onDone);
|
|
434
|
+
const onErrorRef = useRef4(onError);
|
|
435
|
+
onTokenRef.current = onToken;
|
|
436
|
+
onToolCallRef.current = onToolCall;
|
|
437
|
+
onToolResultRef.current = onToolResult;
|
|
438
|
+
onDoneRef.current = onDone;
|
|
439
|
+
onErrorRef.current = onError;
|
|
440
|
+
const { call } = useRPC({
|
|
441
|
+
onError: (err) => {
|
|
442
|
+
setError(err);
|
|
443
|
+
setIsRunning(false);
|
|
444
|
+
onErrorRef.current?.(err);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
useSubscription({
|
|
448
|
+
channel: requestId ? `agent#${requestId}#events` : "",
|
|
449
|
+
enabled: enabled && !!requestId && isRunning,
|
|
450
|
+
onData: (event) => {
|
|
451
|
+
switch (event.type) {
|
|
452
|
+
case "token": {
|
|
453
|
+
const tokenData = event.data;
|
|
454
|
+
setStreamingText((prev) => prev + tokenData.text);
|
|
455
|
+
onTokenRef.current?.(tokenData.text);
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
case "tool_call": {
|
|
459
|
+
const toolCallData = event.data;
|
|
460
|
+
setToolCalls((prev) => [...prev, toolCallData]);
|
|
461
|
+
onToolCallRef.current?.(toolCallData);
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case "tool_result": {
|
|
465
|
+
const toolResultData = event.data;
|
|
466
|
+
setToolCalls((prev) => prev.filter((tc) => tc.id !== toolResultData.id));
|
|
467
|
+
onToolResultRef.current?.(toolResultData);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
case "done": {
|
|
471
|
+
const doneData = event.data;
|
|
472
|
+
setResult(doneData.text);
|
|
473
|
+
setIsRunning(false);
|
|
474
|
+
setRequestId(null);
|
|
475
|
+
onDoneRef.current?.(doneData);
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
case "error": {
|
|
479
|
+
const errorData = event.data;
|
|
480
|
+
const err = new Error(errorData.message);
|
|
481
|
+
setError(err);
|
|
482
|
+
setIsRunning(false);
|
|
483
|
+
setRequestId(null);
|
|
484
|
+
onErrorRef.current?.(err);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
onError: (err) => {
|
|
490
|
+
setError(err);
|
|
491
|
+
setIsRunning(false);
|
|
492
|
+
onErrorRef.current?.(err);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
const run = useCallback4(
|
|
496
|
+
async (prompt, runOptions) => {
|
|
497
|
+
if (!sessionId) {
|
|
498
|
+
throw new Error("Session ID required");
|
|
499
|
+
}
|
|
500
|
+
setStreamingText("");
|
|
501
|
+
setResult(null);
|
|
502
|
+
setToolCalls([]);
|
|
503
|
+
setError(null);
|
|
504
|
+
setIsRunning(true);
|
|
505
|
+
try {
|
|
506
|
+
const response = await call("agent.run", {
|
|
507
|
+
session_id: sessionId,
|
|
508
|
+
prompt,
|
|
509
|
+
mode: runOptions?.mode,
|
|
510
|
+
timeout_seconds: runOptions?.timeoutSeconds,
|
|
511
|
+
output_schema: runOptions?.outputSchema
|
|
512
|
+
});
|
|
513
|
+
setRequestId(response.request_id);
|
|
514
|
+
if (response.text) {
|
|
515
|
+
setResult(response.text);
|
|
516
|
+
setIsRunning(false);
|
|
517
|
+
return response.text;
|
|
518
|
+
}
|
|
519
|
+
return new Promise((resolve, reject) => {
|
|
520
|
+
const checkDone = setInterval(() => {
|
|
521
|
+
if (result) {
|
|
522
|
+
clearInterval(checkDone);
|
|
523
|
+
resolve(result);
|
|
524
|
+
}
|
|
525
|
+
if (error) {
|
|
526
|
+
clearInterval(checkDone);
|
|
527
|
+
reject(error);
|
|
528
|
+
}
|
|
529
|
+
}, 100);
|
|
530
|
+
setTimeout(() => {
|
|
531
|
+
clearInterval(checkDone);
|
|
532
|
+
if (!result && !error) {
|
|
533
|
+
const timeoutError = new Error("Agent timeout");
|
|
534
|
+
setError(timeoutError);
|
|
535
|
+
setIsRunning(false);
|
|
536
|
+
reject(timeoutError);
|
|
537
|
+
}
|
|
538
|
+
}, 3e5);
|
|
539
|
+
});
|
|
540
|
+
} catch (err) {
|
|
541
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
542
|
+
setError(error2);
|
|
543
|
+
setIsRunning(false);
|
|
544
|
+
throw error2;
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
[call, sessionId, result, error]
|
|
548
|
+
);
|
|
549
|
+
const cancel = useCallback4(async () => {
|
|
550
|
+
if (!requestId) return;
|
|
551
|
+
try {
|
|
552
|
+
await call("agent.cancel", { request_id: requestId });
|
|
553
|
+
} finally {
|
|
554
|
+
setIsRunning(false);
|
|
555
|
+
setRequestId(null);
|
|
556
|
+
}
|
|
557
|
+
}, [call, requestId]);
|
|
558
|
+
const reset = useCallback4(() => {
|
|
559
|
+
setStreamingText("");
|
|
560
|
+
setResult(null);
|
|
561
|
+
setToolCalls([]);
|
|
562
|
+
setError(null);
|
|
563
|
+
setIsRunning(false);
|
|
564
|
+
setRequestId(null);
|
|
565
|
+
}, []);
|
|
566
|
+
return {
|
|
567
|
+
run,
|
|
568
|
+
isRunning,
|
|
569
|
+
streamingText,
|
|
570
|
+
result,
|
|
571
|
+
toolCalls,
|
|
572
|
+
error,
|
|
573
|
+
reset,
|
|
574
|
+
cancel
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/index.tsx
|
|
579
|
+
import { createContext as createContext2, useContext as useContext2, useMemo } from "react";
|
|
580
|
+
import { useState as useState5, useCallback as useCallback5 } from "react";
|
|
581
|
+
import { machines as machines2, workspaces as workspaces2 } from "@cmdop/core";
|
|
582
|
+
import useSWR from "swr";
|
|
583
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
584
|
+
var CMDOPContext = createContext2(null);
|
|
585
|
+
function CMDOPProvider({ children, apiKey, token }) {
|
|
586
|
+
const [currentToken, setCurrentToken] = useState5(token);
|
|
587
|
+
const setToken = useCallback5((newToken) => {
|
|
588
|
+
setCurrentToken(newToken);
|
|
589
|
+
machines2.setToken(newToken);
|
|
590
|
+
workspaces2.setToken(newToken);
|
|
591
|
+
}, []);
|
|
592
|
+
useMemo(() => {
|
|
593
|
+
if (currentToken) {
|
|
594
|
+
machines2.setToken(currentToken);
|
|
595
|
+
workspaces2.setToken(currentToken);
|
|
596
|
+
}
|
|
597
|
+
}, [currentToken]);
|
|
598
|
+
const value = useMemo(
|
|
599
|
+
() => ({
|
|
600
|
+
config: { apiKey },
|
|
601
|
+
isAuthenticated: !!currentToken,
|
|
602
|
+
setToken
|
|
603
|
+
}),
|
|
604
|
+
[apiKey, currentToken, setToken]
|
|
605
|
+
);
|
|
606
|
+
return /* @__PURE__ */ jsx2(CMDOPContext.Provider, { value, children });
|
|
607
|
+
}
|
|
608
|
+
function useCMDOP() {
|
|
609
|
+
const context = useContext2(CMDOPContext);
|
|
610
|
+
if (!context) {
|
|
611
|
+
throw new Error("useCMDOP must be used within a CMDOPProvider");
|
|
612
|
+
}
|
|
613
|
+
return context;
|
|
614
|
+
}
|
|
615
|
+
function useMachines(options = {}) {
|
|
616
|
+
const { page, pageSize, ...swrConfig } = options;
|
|
617
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR(
|
|
618
|
+
["machines", page, pageSize],
|
|
619
|
+
async () => {
|
|
620
|
+
const response = await machines2.machines_machines.machinesList({
|
|
621
|
+
page,
|
|
622
|
+
page_size: pageSize
|
|
623
|
+
});
|
|
624
|
+
return response;
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
revalidateOnFocus: false,
|
|
628
|
+
...swrConfig
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
return {
|
|
632
|
+
machines: data?.results ?? [],
|
|
633
|
+
total: data?.count ?? 0,
|
|
634
|
+
isLoading,
|
|
635
|
+
isValidating,
|
|
636
|
+
error,
|
|
637
|
+
refetch: () => mutate()
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function useMachine(machineId, options = {}) {
|
|
641
|
+
const { data, error, isLoading, mutate } = useSWR(
|
|
642
|
+
machineId ? ["machine", machineId] : null,
|
|
643
|
+
async () => {
|
|
644
|
+
if (!machineId) return null;
|
|
645
|
+
return machines2.machines_machines.machinesRetrieve(machineId);
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
revalidateOnFocus: false,
|
|
649
|
+
...options
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
return {
|
|
653
|
+
machine: data ?? null,
|
|
654
|
+
isLoading,
|
|
655
|
+
error,
|
|
656
|
+
refetch: () => mutate()
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function useWorkspaces(options = {}) {
|
|
660
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR(
|
|
661
|
+
"workspaces",
|
|
662
|
+
async () => {
|
|
663
|
+
return workspaces2.workspaces_workspaces.workspacesList();
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
revalidateOnFocus: false,
|
|
667
|
+
...options
|
|
668
|
+
}
|
|
669
|
+
);
|
|
670
|
+
return {
|
|
671
|
+
workspaces: data?.results ?? [],
|
|
672
|
+
total: data?.count ?? 0,
|
|
673
|
+
isLoading,
|
|
674
|
+
isValidating,
|
|
675
|
+
error,
|
|
676
|
+
refetch: () => mutate()
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function useWorkspace(workspaceId, options = {}) {
|
|
680
|
+
const { data, error, isLoading, mutate } = useSWR(
|
|
681
|
+
workspaceId ? ["workspace", workspaceId] : null,
|
|
682
|
+
async () => {
|
|
683
|
+
if (!workspaceId) return null;
|
|
684
|
+
return workspaces2.workspaces_workspaces.workspacesRetrieve(workspaceId);
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
revalidateOnFocus: false,
|
|
688
|
+
...options
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
return {
|
|
692
|
+
workspace: data ?? null,
|
|
693
|
+
isLoading,
|
|
694
|
+
error,
|
|
695
|
+
refetch: () => mutate()
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
export {
|
|
699
|
+
API_BASE_URL,
|
|
700
|
+
AuthenticationError,
|
|
701
|
+
CMDOPError,
|
|
702
|
+
CMDOPProvider,
|
|
703
|
+
CMDOPWebSocketClient,
|
|
704
|
+
CancelledError,
|
|
705
|
+
ConnectionError,
|
|
706
|
+
DEFAULT_CONFIG,
|
|
707
|
+
MachinesModule,
|
|
708
|
+
NotFoundError,
|
|
709
|
+
PermissionError,
|
|
710
|
+
ResourceExhaustedError,
|
|
711
|
+
SessionError,
|
|
712
|
+
SystemModule,
|
|
713
|
+
TimeoutError,
|
|
714
|
+
UnavailableError,
|
|
715
|
+
VERSION,
|
|
716
|
+
WebSocketProvider,
|
|
717
|
+
WorkspacesModule,
|
|
718
|
+
api,
|
|
719
|
+
machines,
|
|
720
|
+
system,
|
|
721
|
+
useAgent,
|
|
722
|
+
useCMDOP,
|
|
723
|
+
useMachine,
|
|
724
|
+
useMachines,
|
|
725
|
+
useRPC,
|
|
726
|
+
useSubscription,
|
|
727
|
+
useTerminal,
|
|
728
|
+
useWebSocket,
|
|
729
|
+
useWorkspace,
|
|
730
|
+
useWorkspaces,
|
|
731
|
+
workspaces
|
|
732
|
+
};
|