@opendray/sdk 2.6.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/LICENSE +201 -0
- package/README.md +62 -0
- package/dist/index.cjs +317 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +209 -0
- package/dist/index.d.ts +209 -0
- package/dist/index.js +287 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
type Iso8601 = string;
|
|
2
|
+
type IntegrationScope = string;
|
|
3
|
+
interface Integration {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
base_url?: string;
|
|
7
|
+
route_prefix: string;
|
|
8
|
+
scopes: IntegrationScope[];
|
|
9
|
+
version: string;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
created_at: Iso8601;
|
|
12
|
+
last_health_check?: Iso8601;
|
|
13
|
+
health_status?: "ok" | "degraded" | "down" | "unknown";
|
|
14
|
+
}
|
|
15
|
+
interface IntegrationRegistration {
|
|
16
|
+
name: string;
|
|
17
|
+
base_url?: string;
|
|
18
|
+
route_prefix: string;
|
|
19
|
+
scopes: IntegrationScope[];
|
|
20
|
+
version: string;
|
|
21
|
+
}
|
|
22
|
+
interface IntegrationCreated extends Integration {
|
|
23
|
+
/** Plaintext API key. Returned exactly once. */
|
|
24
|
+
api_key: string;
|
|
25
|
+
}
|
|
26
|
+
interface IntegrationUpdate {
|
|
27
|
+
base_url?: string;
|
|
28
|
+
scopes?: IntegrationScope[];
|
|
29
|
+
version?: string;
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
type SessionState = "starting" | "running" | "idle" | "ended" | "errored";
|
|
33
|
+
interface Session {
|
|
34
|
+
id: string;
|
|
35
|
+
provider: string;
|
|
36
|
+
state: SessionState;
|
|
37
|
+
cwd?: string;
|
|
38
|
+
cols?: number;
|
|
39
|
+
rows?: number;
|
|
40
|
+
created_at: Iso8601;
|
|
41
|
+
last_activity_at?: Iso8601;
|
|
42
|
+
ended_at?: Iso8601;
|
|
43
|
+
exit_code?: number;
|
|
44
|
+
}
|
|
45
|
+
interface SessionCreateRequest {
|
|
46
|
+
provider: string;
|
|
47
|
+
cwd?: string;
|
|
48
|
+
cols?: number;
|
|
49
|
+
rows?: number;
|
|
50
|
+
env?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
interface SessionInputRequest {
|
|
53
|
+
/** Raw bytes to write to the PTY's stdin. */
|
|
54
|
+
data: string;
|
|
55
|
+
}
|
|
56
|
+
interface SessionResizeRequest {
|
|
57
|
+
cols: number;
|
|
58
|
+
rows: number;
|
|
59
|
+
}
|
|
60
|
+
interface SessionBuffer {
|
|
61
|
+
/** Ring-buffer replay of recent terminal output. */
|
|
62
|
+
data: string;
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface Provider {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
command: string;
|
|
69
|
+
config?: Record<string, unknown>;
|
|
70
|
+
available: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface Channel {
|
|
73
|
+
id: string;
|
|
74
|
+
kind: string;
|
|
75
|
+
label: string;
|
|
76
|
+
enabled: boolean;
|
|
77
|
+
}
|
|
78
|
+
interface EventFrame<T = unknown> {
|
|
79
|
+
topic: string;
|
|
80
|
+
ts: Iso8601;
|
|
81
|
+
data: T;
|
|
82
|
+
}
|
|
83
|
+
interface SessionOutputData {
|
|
84
|
+
session_id: string;
|
|
85
|
+
data: string;
|
|
86
|
+
}
|
|
87
|
+
interface SessionEndedData {
|
|
88
|
+
session_id: string;
|
|
89
|
+
exit_code?: number;
|
|
90
|
+
reason?: string;
|
|
91
|
+
}
|
|
92
|
+
interface SessionIdleData {
|
|
93
|
+
session_id: string;
|
|
94
|
+
idle_for_ms: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ClientOptions {
|
|
98
|
+
/** Base URL of the gateway, e.g. `https://opendray.example.com`. */
|
|
99
|
+
baseUrl: string;
|
|
100
|
+
/** Admin bearer token or integration API key (`odk_live_…`). */
|
|
101
|
+
token: string;
|
|
102
|
+
/** Optional custom fetch (e.g. for proxying or instrumentation). */
|
|
103
|
+
fetch?: typeof fetch;
|
|
104
|
+
/** Default request timeout in ms. 30s if omitted. 0 disables. */
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
}
|
|
107
|
+
declare class OpendrayError extends Error {
|
|
108
|
+
readonly status: number;
|
|
109
|
+
readonly body: unknown;
|
|
110
|
+
constructor(status: number, body: unknown, message: string);
|
|
111
|
+
}
|
|
112
|
+
declare class Client {
|
|
113
|
+
readonly baseUrl: string;
|
|
114
|
+
readonly token: string;
|
|
115
|
+
private readonly fetchImpl;
|
|
116
|
+
private readonly timeoutMs;
|
|
117
|
+
constructor(opts: ClientOptions);
|
|
118
|
+
private request;
|
|
119
|
+
registerIntegration(input: IntegrationRegistration): Promise<IntegrationCreated>;
|
|
120
|
+
listIntegrations(): Promise<Integration[]>;
|
|
121
|
+
getIntegration(id: string): Promise<Integration>;
|
|
122
|
+
updateIntegration(id: string, patch: IntegrationUpdate): Promise<Integration>;
|
|
123
|
+
deleteIntegration(id: string): Promise<void>;
|
|
124
|
+
rotateIntegrationKey(id: string): Promise<IntegrationCreated>;
|
|
125
|
+
createSession(input: SessionCreateRequest): Promise<Session>;
|
|
126
|
+
listSessions(): Promise<Session[]>;
|
|
127
|
+
getSession(id: string): Promise<Session>;
|
|
128
|
+
deleteSession(id: string): Promise<void>;
|
|
129
|
+
sendInput(id: string, input: SessionInputRequest): Promise<void>;
|
|
130
|
+
resizeSession(id: string, size: SessionResizeRequest): Promise<void>;
|
|
131
|
+
getSessionBuffer(id: string): Promise<SessionBuffer>;
|
|
132
|
+
listProviders(): Promise<Provider[]>;
|
|
133
|
+
setProviderConfig(id: string, config: Record<string, unknown>): Promise<Provider>;
|
|
134
|
+
listChannels(): Promise<Channel[]>;
|
|
135
|
+
/**
|
|
136
|
+
* Compose the WS URL for the events stream. Used by `subscribeEvents`,
|
|
137
|
+
* exposed publicly for callers that want to manage the socket lifecycle
|
|
138
|
+
* themselves.
|
|
139
|
+
*/
|
|
140
|
+
eventsUrl(topics: string[]): string;
|
|
141
|
+
sessionStreamUrl(id: string): string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface SubscribeOptions {
|
|
145
|
+
/** Topic patterns. Use `session.*` for prefix wildcards. */
|
|
146
|
+
topics: string[];
|
|
147
|
+
/** AbortSignal closes the socket and ends the iterator. */
|
|
148
|
+
signal?: AbortSignal;
|
|
149
|
+
/**
|
|
150
|
+
* Custom WebSocket implementation. Defaults to globalThis.WebSocket.
|
|
151
|
+
* On Node <22 you'll need to pass `ws`'s default export.
|
|
152
|
+
*/
|
|
153
|
+
WebSocket?: typeof globalThis.WebSocket;
|
|
154
|
+
/** Reconnect on unexpected close. Defaults to true. */
|
|
155
|
+
reconnect?: boolean;
|
|
156
|
+
/** Initial backoff in ms (doubles up to 30s). Defaults to 1000. */
|
|
157
|
+
reconnectBaseMs?: number;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Returns an async iterable that yields one EventFrame per server message.
|
|
161
|
+
* Reconnects automatically on transient drops; an AbortSignal ends the
|
|
162
|
+
* stream cleanly.
|
|
163
|
+
*
|
|
164
|
+
* ```ts
|
|
165
|
+
* for await (const frame of subscribeEvents(client, { topics: ["session.*"] })) {
|
|
166
|
+
* console.log(frame.topic, frame.data);
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare function subscribeEvents<T = unknown>(client: Client, opts: SubscribeOptions): AsyncGenerator<EventFrame<T>, void, void>;
|
|
171
|
+
|
|
172
|
+
interface SessionStreamHandle {
|
|
173
|
+
/** Send raw bytes to the session's PTY stdin. */
|
|
174
|
+
send(data: string): void;
|
|
175
|
+
/** Close the stream. */
|
|
176
|
+
close(): void;
|
|
177
|
+
/** Resolves when the socket closes (cleanly or with an error). */
|
|
178
|
+
readonly closed: Promise<void>;
|
|
179
|
+
}
|
|
180
|
+
interface SessionStreamOptions {
|
|
181
|
+
/** Called with each chunk of terminal output. */
|
|
182
|
+
onOutput?: (data: string) => void;
|
|
183
|
+
/** Called when the server reports the session has ended. */
|
|
184
|
+
onEnded?: (info: {
|
|
185
|
+
exitCode?: number | undefined;
|
|
186
|
+
reason?: string | undefined;
|
|
187
|
+
}) => void;
|
|
188
|
+
/** AbortSignal closes the socket. */
|
|
189
|
+
signal?: AbortSignal;
|
|
190
|
+
/**
|
|
191
|
+
* Custom WebSocket implementation. Defaults to globalThis.WebSocket.
|
|
192
|
+
* On Node <22 pass `ws`'s default export.
|
|
193
|
+
*/
|
|
194
|
+
WebSocket?: typeof globalThis.WebSocket;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Open a bidirectional terminal stream against the given session.
|
|
198
|
+
*
|
|
199
|
+
* The wire format is gateway-specific JSON frames:
|
|
200
|
+
* { kind: "output", data: "<chunk>" } (server → client)
|
|
201
|
+
* { kind: "ended", exit_code?, reason? } (server → client)
|
|
202
|
+
* { kind: "input", data: "<chunk>" } (client → server)
|
|
203
|
+
*
|
|
204
|
+
* The returned handle exposes `send()` and `close()`, and a `closed`
|
|
205
|
+
* promise that resolves when the socket finishes.
|
|
206
|
+
*/
|
|
207
|
+
declare function streamSession(client: Client, sessionId: string, opts?: SessionStreamOptions): SessionStreamHandle;
|
|
208
|
+
|
|
209
|
+
export { type Channel, Client, type ClientOptions, type EventFrame, type Integration, type IntegrationCreated, type IntegrationRegistration, type IntegrationScope, type IntegrationUpdate, type Iso8601, OpendrayError, type Provider, type Session, type SessionBuffer, type SessionCreateRequest, type SessionEndedData, type SessionIdleData, type SessionInputRequest, type SessionOutputData, type SessionResizeRequest, type SessionState, type SessionStreamHandle, type SessionStreamOptions, type SubscribeOptions, streamSession, subscribeEvents };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
type Iso8601 = string;
|
|
2
|
+
type IntegrationScope = string;
|
|
3
|
+
interface Integration {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
base_url?: string;
|
|
7
|
+
route_prefix: string;
|
|
8
|
+
scopes: IntegrationScope[];
|
|
9
|
+
version: string;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
created_at: Iso8601;
|
|
12
|
+
last_health_check?: Iso8601;
|
|
13
|
+
health_status?: "ok" | "degraded" | "down" | "unknown";
|
|
14
|
+
}
|
|
15
|
+
interface IntegrationRegistration {
|
|
16
|
+
name: string;
|
|
17
|
+
base_url?: string;
|
|
18
|
+
route_prefix: string;
|
|
19
|
+
scopes: IntegrationScope[];
|
|
20
|
+
version: string;
|
|
21
|
+
}
|
|
22
|
+
interface IntegrationCreated extends Integration {
|
|
23
|
+
/** Plaintext API key. Returned exactly once. */
|
|
24
|
+
api_key: string;
|
|
25
|
+
}
|
|
26
|
+
interface IntegrationUpdate {
|
|
27
|
+
base_url?: string;
|
|
28
|
+
scopes?: IntegrationScope[];
|
|
29
|
+
version?: string;
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
type SessionState = "starting" | "running" | "idle" | "ended" | "errored";
|
|
33
|
+
interface Session {
|
|
34
|
+
id: string;
|
|
35
|
+
provider: string;
|
|
36
|
+
state: SessionState;
|
|
37
|
+
cwd?: string;
|
|
38
|
+
cols?: number;
|
|
39
|
+
rows?: number;
|
|
40
|
+
created_at: Iso8601;
|
|
41
|
+
last_activity_at?: Iso8601;
|
|
42
|
+
ended_at?: Iso8601;
|
|
43
|
+
exit_code?: number;
|
|
44
|
+
}
|
|
45
|
+
interface SessionCreateRequest {
|
|
46
|
+
provider: string;
|
|
47
|
+
cwd?: string;
|
|
48
|
+
cols?: number;
|
|
49
|
+
rows?: number;
|
|
50
|
+
env?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
interface SessionInputRequest {
|
|
53
|
+
/** Raw bytes to write to the PTY's stdin. */
|
|
54
|
+
data: string;
|
|
55
|
+
}
|
|
56
|
+
interface SessionResizeRequest {
|
|
57
|
+
cols: number;
|
|
58
|
+
rows: number;
|
|
59
|
+
}
|
|
60
|
+
interface SessionBuffer {
|
|
61
|
+
/** Ring-buffer replay of recent terminal output. */
|
|
62
|
+
data: string;
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface Provider {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
command: string;
|
|
69
|
+
config?: Record<string, unknown>;
|
|
70
|
+
available: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface Channel {
|
|
73
|
+
id: string;
|
|
74
|
+
kind: string;
|
|
75
|
+
label: string;
|
|
76
|
+
enabled: boolean;
|
|
77
|
+
}
|
|
78
|
+
interface EventFrame<T = unknown> {
|
|
79
|
+
topic: string;
|
|
80
|
+
ts: Iso8601;
|
|
81
|
+
data: T;
|
|
82
|
+
}
|
|
83
|
+
interface SessionOutputData {
|
|
84
|
+
session_id: string;
|
|
85
|
+
data: string;
|
|
86
|
+
}
|
|
87
|
+
interface SessionEndedData {
|
|
88
|
+
session_id: string;
|
|
89
|
+
exit_code?: number;
|
|
90
|
+
reason?: string;
|
|
91
|
+
}
|
|
92
|
+
interface SessionIdleData {
|
|
93
|
+
session_id: string;
|
|
94
|
+
idle_for_ms: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ClientOptions {
|
|
98
|
+
/** Base URL of the gateway, e.g. `https://opendray.example.com`. */
|
|
99
|
+
baseUrl: string;
|
|
100
|
+
/** Admin bearer token or integration API key (`odk_live_…`). */
|
|
101
|
+
token: string;
|
|
102
|
+
/** Optional custom fetch (e.g. for proxying or instrumentation). */
|
|
103
|
+
fetch?: typeof fetch;
|
|
104
|
+
/** Default request timeout in ms. 30s if omitted. 0 disables. */
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
}
|
|
107
|
+
declare class OpendrayError extends Error {
|
|
108
|
+
readonly status: number;
|
|
109
|
+
readonly body: unknown;
|
|
110
|
+
constructor(status: number, body: unknown, message: string);
|
|
111
|
+
}
|
|
112
|
+
declare class Client {
|
|
113
|
+
readonly baseUrl: string;
|
|
114
|
+
readonly token: string;
|
|
115
|
+
private readonly fetchImpl;
|
|
116
|
+
private readonly timeoutMs;
|
|
117
|
+
constructor(opts: ClientOptions);
|
|
118
|
+
private request;
|
|
119
|
+
registerIntegration(input: IntegrationRegistration): Promise<IntegrationCreated>;
|
|
120
|
+
listIntegrations(): Promise<Integration[]>;
|
|
121
|
+
getIntegration(id: string): Promise<Integration>;
|
|
122
|
+
updateIntegration(id: string, patch: IntegrationUpdate): Promise<Integration>;
|
|
123
|
+
deleteIntegration(id: string): Promise<void>;
|
|
124
|
+
rotateIntegrationKey(id: string): Promise<IntegrationCreated>;
|
|
125
|
+
createSession(input: SessionCreateRequest): Promise<Session>;
|
|
126
|
+
listSessions(): Promise<Session[]>;
|
|
127
|
+
getSession(id: string): Promise<Session>;
|
|
128
|
+
deleteSession(id: string): Promise<void>;
|
|
129
|
+
sendInput(id: string, input: SessionInputRequest): Promise<void>;
|
|
130
|
+
resizeSession(id: string, size: SessionResizeRequest): Promise<void>;
|
|
131
|
+
getSessionBuffer(id: string): Promise<SessionBuffer>;
|
|
132
|
+
listProviders(): Promise<Provider[]>;
|
|
133
|
+
setProviderConfig(id: string, config: Record<string, unknown>): Promise<Provider>;
|
|
134
|
+
listChannels(): Promise<Channel[]>;
|
|
135
|
+
/**
|
|
136
|
+
* Compose the WS URL for the events stream. Used by `subscribeEvents`,
|
|
137
|
+
* exposed publicly for callers that want to manage the socket lifecycle
|
|
138
|
+
* themselves.
|
|
139
|
+
*/
|
|
140
|
+
eventsUrl(topics: string[]): string;
|
|
141
|
+
sessionStreamUrl(id: string): string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface SubscribeOptions {
|
|
145
|
+
/** Topic patterns. Use `session.*` for prefix wildcards. */
|
|
146
|
+
topics: string[];
|
|
147
|
+
/** AbortSignal closes the socket and ends the iterator. */
|
|
148
|
+
signal?: AbortSignal;
|
|
149
|
+
/**
|
|
150
|
+
* Custom WebSocket implementation. Defaults to globalThis.WebSocket.
|
|
151
|
+
* On Node <22 you'll need to pass `ws`'s default export.
|
|
152
|
+
*/
|
|
153
|
+
WebSocket?: typeof globalThis.WebSocket;
|
|
154
|
+
/** Reconnect on unexpected close. Defaults to true. */
|
|
155
|
+
reconnect?: boolean;
|
|
156
|
+
/** Initial backoff in ms (doubles up to 30s). Defaults to 1000. */
|
|
157
|
+
reconnectBaseMs?: number;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Returns an async iterable that yields one EventFrame per server message.
|
|
161
|
+
* Reconnects automatically on transient drops; an AbortSignal ends the
|
|
162
|
+
* stream cleanly.
|
|
163
|
+
*
|
|
164
|
+
* ```ts
|
|
165
|
+
* for await (const frame of subscribeEvents(client, { topics: ["session.*"] })) {
|
|
166
|
+
* console.log(frame.topic, frame.data);
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare function subscribeEvents<T = unknown>(client: Client, opts: SubscribeOptions): AsyncGenerator<EventFrame<T>, void, void>;
|
|
171
|
+
|
|
172
|
+
interface SessionStreamHandle {
|
|
173
|
+
/** Send raw bytes to the session's PTY stdin. */
|
|
174
|
+
send(data: string): void;
|
|
175
|
+
/** Close the stream. */
|
|
176
|
+
close(): void;
|
|
177
|
+
/** Resolves when the socket closes (cleanly or with an error). */
|
|
178
|
+
readonly closed: Promise<void>;
|
|
179
|
+
}
|
|
180
|
+
interface SessionStreamOptions {
|
|
181
|
+
/** Called with each chunk of terminal output. */
|
|
182
|
+
onOutput?: (data: string) => void;
|
|
183
|
+
/** Called when the server reports the session has ended. */
|
|
184
|
+
onEnded?: (info: {
|
|
185
|
+
exitCode?: number | undefined;
|
|
186
|
+
reason?: string | undefined;
|
|
187
|
+
}) => void;
|
|
188
|
+
/** AbortSignal closes the socket. */
|
|
189
|
+
signal?: AbortSignal;
|
|
190
|
+
/**
|
|
191
|
+
* Custom WebSocket implementation. Defaults to globalThis.WebSocket.
|
|
192
|
+
* On Node <22 pass `ws`'s default export.
|
|
193
|
+
*/
|
|
194
|
+
WebSocket?: typeof globalThis.WebSocket;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Open a bidirectional terminal stream against the given session.
|
|
198
|
+
*
|
|
199
|
+
* The wire format is gateway-specific JSON frames:
|
|
200
|
+
* { kind: "output", data: "<chunk>" } (server → client)
|
|
201
|
+
* { kind: "ended", exit_code?, reason? } (server → client)
|
|
202
|
+
* { kind: "input", data: "<chunk>" } (client → server)
|
|
203
|
+
*
|
|
204
|
+
* The returned handle exposes `send()` and `close()`, and a `closed`
|
|
205
|
+
* promise that resolves when the socket finishes.
|
|
206
|
+
*/
|
|
207
|
+
declare function streamSession(client: Client, sessionId: string, opts?: SessionStreamOptions): SessionStreamHandle;
|
|
208
|
+
|
|
209
|
+
export { type Channel, Client, type ClientOptions, type EventFrame, type Integration, type IntegrationCreated, type IntegrationRegistration, type IntegrationScope, type IntegrationUpdate, type Iso8601, OpendrayError, type Provider, type Session, type SessionBuffer, type SessionCreateRequest, type SessionEndedData, type SessionIdleData, type SessionInputRequest, type SessionOutputData, type SessionResizeRequest, type SessionState, type SessionStreamHandle, type SessionStreamOptions, type SubscribeOptions, streamSession, subscribeEvents };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var OpendrayError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
constructor(status, body, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "OpendrayError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var Client = class {
|
|
13
|
+
baseUrl;
|
|
14
|
+
token;
|
|
15
|
+
fetchImpl;
|
|
16
|
+
timeoutMs;
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
if (!opts.baseUrl) throw new Error("Client: baseUrl is required");
|
|
19
|
+
if (!opts.token) throw new Error("Client: token is required");
|
|
20
|
+
let trimmed = opts.baseUrl;
|
|
21
|
+
while (trimmed.endsWith("/")) trimmed = trimmed.slice(0, -1);
|
|
22
|
+
this.baseUrl = trimmed;
|
|
23
|
+
this.token = opts.token;
|
|
24
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
25
|
+
if (!this.fetchImpl) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"Client: no fetch available \u2014 pass `fetch` in options on platforms older than Node 18."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
31
|
+
}
|
|
32
|
+
// ── low level ──────────────────────────────────────────────────────
|
|
33
|
+
async request(method, path, body) {
|
|
34
|
+
const url = `${this.baseUrl}/api/v1${path}`;
|
|
35
|
+
const headers = {
|
|
36
|
+
Authorization: `Bearer ${this.token}`,
|
|
37
|
+
Accept: "application/json"
|
|
38
|
+
};
|
|
39
|
+
let payload;
|
|
40
|
+
if (body !== void 0) {
|
|
41
|
+
headers["Content-Type"] = "application/json";
|
|
42
|
+
payload = JSON.stringify(body);
|
|
43
|
+
}
|
|
44
|
+
const ctrl = this.timeoutMs > 0 ? new AbortController() : null;
|
|
45
|
+
const timer = ctrl ? setTimeout(() => ctrl.abort(new Error("request timed out")), this.timeoutMs) : null;
|
|
46
|
+
const init = { method, headers };
|
|
47
|
+
if (payload !== void 0) init.body = payload;
|
|
48
|
+
if (ctrl) init.signal = ctrl.signal;
|
|
49
|
+
try {
|
|
50
|
+
const res = await this.fetchImpl(url, init);
|
|
51
|
+
const text = await res.text();
|
|
52
|
+
let parsed = void 0;
|
|
53
|
+
if (text.length > 0) {
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(text);
|
|
56
|
+
} catch {
|
|
57
|
+
parsed = text;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
const msg = (parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : void 0) ?? `${method} ${path} -> ${res.status}`;
|
|
62
|
+
throw new OpendrayError(res.status, parsed, msg);
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
} finally {
|
|
66
|
+
if (timer) clearTimeout(timer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ── integrations (admin-only) ──────────────────────────────────────
|
|
70
|
+
registerIntegration(input) {
|
|
71
|
+
return this.request("POST", "/integrations", input);
|
|
72
|
+
}
|
|
73
|
+
listIntegrations() {
|
|
74
|
+
return this.request("GET", "/integrations");
|
|
75
|
+
}
|
|
76
|
+
getIntegration(id) {
|
|
77
|
+
return this.request("GET", `/integrations/${encodeURIComponent(id)}`);
|
|
78
|
+
}
|
|
79
|
+
updateIntegration(id, patch) {
|
|
80
|
+
return this.request("PATCH", `/integrations/${encodeURIComponent(id)}`, patch);
|
|
81
|
+
}
|
|
82
|
+
deleteIntegration(id) {
|
|
83
|
+
return this.request("DELETE", `/integrations/${encodeURIComponent(id)}`);
|
|
84
|
+
}
|
|
85
|
+
rotateIntegrationKey(id) {
|
|
86
|
+
return this.request("POST", `/integrations/${encodeURIComponent(id)}/rotate-key`);
|
|
87
|
+
}
|
|
88
|
+
// ── sessions (dual-auth) ───────────────────────────────────────────
|
|
89
|
+
createSession(input) {
|
|
90
|
+
return this.request("POST", "/sessions", input);
|
|
91
|
+
}
|
|
92
|
+
listSessions() {
|
|
93
|
+
return this.request("GET", "/sessions");
|
|
94
|
+
}
|
|
95
|
+
getSession(id) {
|
|
96
|
+
return this.request("GET", `/sessions/${encodeURIComponent(id)}`);
|
|
97
|
+
}
|
|
98
|
+
deleteSession(id) {
|
|
99
|
+
return this.request("DELETE", `/sessions/${encodeURIComponent(id)}`);
|
|
100
|
+
}
|
|
101
|
+
sendInput(id, input) {
|
|
102
|
+
return this.request("POST", `/sessions/${encodeURIComponent(id)}/input`, input);
|
|
103
|
+
}
|
|
104
|
+
resizeSession(id, size) {
|
|
105
|
+
return this.request("POST", `/sessions/${encodeURIComponent(id)}/resize`, size);
|
|
106
|
+
}
|
|
107
|
+
getSessionBuffer(id) {
|
|
108
|
+
return this.request("GET", `/sessions/${encodeURIComponent(id)}/buffer`);
|
|
109
|
+
}
|
|
110
|
+
// ── providers / channels (dual-auth) ───────────────────────────────
|
|
111
|
+
listProviders() {
|
|
112
|
+
return this.request("GET", "/providers");
|
|
113
|
+
}
|
|
114
|
+
setProviderConfig(id, config) {
|
|
115
|
+
return this.request(
|
|
116
|
+
"PATCH",
|
|
117
|
+
`/providers/${encodeURIComponent(id)}/config`,
|
|
118
|
+
config
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
listChannels() {
|
|
122
|
+
return this.request("GET", "/channels");
|
|
123
|
+
}
|
|
124
|
+
// ── ws helpers (URLs only — open via subscribeEvents / streamSession) ─
|
|
125
|
+
/**
|
|
126
|
+
* Compose the WS URL for the events stream. Used by `subscribeEvents`,
|
|
127
|
+
* exposed publicly for callers that want to manage the socket lifecycle
|
|
128
|
+
* themselves.
|
|
129
|
+
*/
|
|
130
|
+
eventsUrl(topics) {
|
|
131
|
+
const u = new URL(`${this.baseUrl}/api/v1/integrations/_events`);
|
|
132
|
+
u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
|
|
133
|
+
u.searchParams.set("token", this.token);
|
|
134
|
+
if (topics.length > 0) u.searchParams.set("topics", topics.join(","));
|
|
135
|
+
return u.toString();
|
|
136
|
+
}
|
|
137
|
+
sessionStreamUrl(id) {
|
|
138
|
+
const u = new URL(
|
|
139
|
+
`${this.baseUrl}/api/v1/sessions/${encodeURIComponent(id)}/stream`
|
|
140
|
+
);
|
|
141
|
+
u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
|
|
142
|
+
u.searchParams.set("token", this.token);
|
|
143
|
+
return u.toString();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/events.ts
|
|
148
|
+
async function* subscribeEvents(client, opts) {
|
|
149
|
+
const WS = opts.WebSocket ?? globalThis.WebSocket;
|
|
150
|
+
if (!WS) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
"subscribeEvents: no WebSocket available. On Node <22 import 'ws' and pass it via `WebSocket`."
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const url = client.eventsUrl(opts.topics);
|
|
156
|
+
const reconnect = opts.reconnect ?? true;
|
|
157
|
+
const baseDelay = opts.reconnectBaseMs ?? 1e3;
|
|
158
|
+
let attempt = 0;
|
|
159
|
+
while (true) {
|
|
160
|
+
if (opts.signal?.aborted) return;
|
|
161
|
+
const queue = [];
|
|
162
|
+
let resolve = null;
|
|
163
|
+
let closed = false;
|
|
164
|
+
let closeReason = null;
|
|
165
|
+
const ws = new WS(url);
|
|
166
|
+
ws.addEventListener("message", (ev) => {
|
|
167
|
+
try {
|
|
168
|
+
const frame = JSON.parse(String(ev.data));
|
|
169
|
+
queue.push(frame);
|
|
170
|
+
if (resolve) {
|
|
171
|
+
const r = resolve;
|
|
172
|
+
resolve = null;
|
|
173
|
+
r();
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
closeReason = err instanceof Error ? err : new Error(String(err));
|
|
177
|
+
try {
|
|
178
|
+
ws.close();
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
ws.addEventListener("close", () => {
|
|
184
|
+
closed = true;
|
|
185
|
+
if (resolve) {
|
|
186
|
+
const r = resolve;
|
|
187
|
+
resolve = null;
|
|
188
|
+
r();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
ws.addEventListener("error", (ev) => {
|
|
192
|
+
closeReason = new Error(`websocket error: ${ev.message ?? "unknown"}`);
|
|
193
|
+
});
|
|
194
|
+
const onAbort = () => {
|
|
195
|
+
try {
|
|
196
|
+
ws.close();
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
201
|
+
try {
|
|
202
|
+
while (!closed) {
|
|
203
|
+
while (queue.length > 0) {
|
|
204
|
+
const frame = queue.shift();
|
|
205
|
+
yield frame;
|
|
206
|
+
attempt = 0;
|
|
207
|
+
}
|
|
208
|
+
if (closed) break;
|
|
209
|
+
await new Promise((r) => {
|
|
210
|
+
resolve = r;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
215
|
+
try {
|
|
216
|
+
ws.close();
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (opts.signal?.aborted) return;
|
|
221
|
+
if (closeReason) throw closeReason;
|
|
222
|
+
if (!reconnect) return;
|
|
223
|
+
attempt += 1;
|
|
224
|
+
const delay = Math.min(3e4, baseDelay * 2 ** (attempt - 1));
|
|
225
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/session.ts
|
|
230
|
+
function streamSession(client, sessionId, opts = {}) {
|
|
231
|
+
const WS = opts.WebSocket ?? globalThis.WebSocket;
|
|
232
|
+
if (!WS) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
"streamSession: no WebSocket available. On Node <22 import 'ws' and pass it via `WebSocket`."
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const ws = new WS(client.sessionStreamUrl(sessionId));
|
|
238
|
+
let resolveClosed;
|
|
239
|
+
const closed = new Promise((r) => {
|
|
240
|
+
resolveClosed = r;
|
|
241
|
+
});
|
|
242
|
+
ws.addEventListener("message", (ev) => {
|
|
243
|
+
let frame;
|
|
244
|
+
try {
|
|
245
|
+
frame = JSON.parse(String(ev.data));
|
|
246
|
+
} catch {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (frame.kind === "output" && typeof frame.data === "string") {
|
|
250
|
+
opts.onOutput?.(frame.data);
|
|
251
|
+
} else if (frame.kind === "ended") {
|
|
252
|
+
opts.onEnded?.({ exitCode: frame.exit_code, reason: frame.reason });
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
ws.addEventListener("close", () => {
|
|
256
|
+
resolveClosed();
|
|
257
|
+
});
|
|
258
|
+
const onAbort = () => {
|
|
259
|
+
try {
|
|
260
|
+
ws.close();
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
265
|
+
return {
|
|
266
|
+
send(data) {
|
|
267
|
+
if (ws.readyState !== WS.OPEN) {
|
|
268
|
+
throw new Error("streamSession: socket not open");
|
|
269
|
+
}
|
|
270
|
+
ws.send(JSON.stringify({ kind: "input", data }));
|
|
271
|
+
},
|
|
272
|
+
close() {
|
|
273
|
+
try {
|
|
274
|
+
ws.close();
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
closed
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
export {
|
|
282
|
+
Client,
|
|
283
|
+
OpendrayError,
|
|
284
|
+
streamSession,
|
|
285
|
+
subscribeEvents
|
|
286
|
+
};
|
|
287
|
+
//# sourceMappingURL=index.js.map
|