@soederpop/luca 0.0.5 → 0.0.7
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/CLAUDE.md +10 -1
- package/bun.lock +1 -1
- package/commands/build-bootstrap.ts +78 -0
- package/commands/build-scaffolds.ts +24 -2
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/docs/README.md +52 -80
- package/docs/TABLE-OF-CONTENTS.md +82 -51
- package/docs/apis/clients/elevenlabs.md +232 -8
- package/docs/apis/clients/graph.md +59 -8
- package/docs/apis/clients/openai.md +362 -2
- package/docs/apis/clients/rest.md +122 -2
- package/docs/apis/clients/websocket.md +71 -17
- package/docs/apis/features/agi/assistant.md +9 -3
- package/docs/apis/features/agi/assistants-manager.md +2 -2
- package/docs/apis/features/agi/claude-code.md +153 -14
- package/docs/apis/features/agi/conversation-history.md +15 -3
- package/docs/apis/features/agi/conversation.md +133 -20
- package/docs/apis/features/agi/openai-codex.md +90 -12
- package/docs/apis/features/agi/skills-library.md +23 -5
- package/docs/apis/features/node/container-link.md +59 -0
- package/docs/apis/features/node/content-db.md +1 -1
- package/docs/apis/features/node/disk-cache.md +1 -1
- package/docs/apis/features/node/dns.md +1 -0
- package/docs/apis/features/node/docker.md +2 -1
- package/docs/apis/features/node/esbuild.md +4 -3
- package/docs/apis/features/node/file-manager.md +13 -4
- package/docs/apis/features/node/fs.md +726 -171
- package/docs/apis/features/node/git.md +1 -0
- package/docs/apis/features/node/google-auth.md +23 -4
- package/docs/apis/features/node/google-calendar.md +14 -2
- package/docs/apis/features/node/google-docs.md +15 -2
- package/docs/apis/features/node/google-drive.md +21 -3
- package/docs/apis/features/node/google-sheets.md +14 -2
- package/docs/apis/features/node/grep.md +2 -0
- package/docs/apis/features/node/helpers.md +29 -0
- package/docs/apis/features/node/ink.md +2 -2
- package/docs/apis/features/node/networking.md +39 -4
- package/docs/apis/features/node/os.md +28 -0
- package/docs/apis/features/node/postgres.md +26 -4
- package/docs/apis/features/node/proc.md +37 -28
- package/docs/apis/features/node/process-manager.md +33 -5
- package/docs/apis/features/node/repl.md +1 -1
- package/docs/apis/features/node/runpod.md +1 -0
- package/docs/apis/features/node/secure-shell.md +7 -0
- package/docs/apis/features/node/semantic-search.md +12 -5
- package/docs/apis/features/node/sqlite.md +26 -4
- package/docs/apis/features/node/telegram.md +30 -5
- package/docs/apis/features/node/tts.md +17 -2
- package/docs/apis/features/node/ui.md +1 -1
- package/docs/apis/features/node/vault.md +4 -9
- package/docs/apis/features/node/vm.md +3 -12
- package/docs/apis/features/node/window-manager.md +128 -20
- package/docs/apis/features/web/asset-loader.md +13 -1
- package/docs/apis/features/web/container-link.md +59 -0
- package/docs/apis/features/web/esbuild.md +4 -3
- package/docs/apis/features/web/helpers.md +29 -0
- package/docs/apis/features/web/network.md +16 -2
- package/docs/apis/features/web/speech.md +16 -2
- package/docs/apis/features/web/vault.md +4 -9
- package/docs/apis/features/web/vm.md +3 -12
- package/docs/apis/features/web/voice.md +18 -1
- package/docs/apis/servers/express.md +18 -2
- package/docs/apis/servers/mcp.md +29 -4
- package/docs/apis/servers/websocket.md +34 -6
- package/docs/bootstrap/CLAUDE.md +100 -0
- package/docs/bootstrap/SKILL.md +222 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +25 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/window-manager-layouts.md +180 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/models.ts +13 -10
- package/docs/philosophy.md +4 -3
- package/docs/reports/console-hmr-design.md +170 -0
- package/docs/reports/helper-semantic-search.md +72 -0
- package/docs/scaffolds/client.md +29 -20
- package/docs/scaffolds/command.md +64 -50
- package/docs/scaffolds/endpoint.md +31 -36
- package/docs/scaffolds/feature.md +28 -18
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +18 -9
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +148 -0
- package/docs/tutorials/07-endpoints.md +7 -7
- package/docs/tutorials/08-commands.md +153 -72
- package/luca.cli.ts +3 -0
- package/package.json +6 -5
- package/public/index.html +1430 -0
- package/scripts/examples/using-ollama.ts +2 -1
- package/scripts/update-introspection-data.ts +2 -2
- package/src/agi/endpoints/experts.ts +1 -1
- package/src/agi/features/assistant.ts +7 -0
- package/src/agi/features/assistants-manager.ts +5 -5
- package/src/agi/features/claude-code.ts +263 -3
- package/src/agi/features/conversation-history.ts +7 -1
- package/src/agi/features/conversation.ts +26 -3
- package/src/agi/features/openai-codex.ts +26 -2
- package/src/agi/features/openapi.ts +6 -1
- package/src/agi/features/skills-library.ts +9 -1
- package/src/bootstrap/generated.ts +540 -0
- package/src/cli/cli.ts +64 -21
- package/src/client.ts +23 -357
- package/src/clients/civitai/index.ts +1 -1
- package/src/clients/client-template.ts +1 -1
- package/src/clients/comfyui/index.ts +13 -2
- package/src/clients/elevenlabs/index.ts +2 -1
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +10 -1
- package/src/clients/rest.ts +207 -0
- package/src/clients/websocket.ts +176 -0
- package/src/command.ts +281 -34
- package/src/commands/bootstrap.ts +181 -0
- package/src/commands/chat.ts +5 -4
- package/src/commands/describe.ts +225 -2
- package/src/commands/help.ts +35 -9
- package/src/commands/index.ts +3 -0
- package/src/commands/introspect.ts +92 -2
- package/src/commands/prompt.ts +5 -6
- package/src/commands/run.ts +33 -10
- package/src/commands/save-api-docs.ts +49 -0
- package/src/commands/scaffold.ts +169 -23
- package/src/commands/select.ts +94 -0
- package/src/commands/serve.ts +10 -1
- package/src/container.ts +15 -0
- package/src/endpoint.ts +19 -0
- package/src/graft.ts +181 -0
- package/src/introspection/generated.agi.ts +12458 -8968
- package/src/introspection/generated.node.ts +10573 -7145
- package/src/introspection/generated.web.ts +1 -1
- package/src/introspection/index.ts +26 -0
- package/src/node/container.ts +6 -7
- package/src/node/features/content-db.ts +49 -2
- package/src/node/features/disk-cache.ts +16 -9
- package/src/node/features/dns.ts +16 -3
- package/src/node/features/docker.ts +16 -4
- package/src/node/features/esbuild.ts +20 -0
- package/src/node/features/file-manager.ts +184 -29
- package/src/node/features/fs.ts +704 -248
- package/src/node/features/git.ts +21 -8
- package/src/node/features/grep.ts +23 -3
- package/src/node/features/helpers.ts +372 -43
- package/src/node/features/networking.ts +39 -4
- package/src/node/features/opener.ts +28 -15
- package/src/node/features/os.ts +76 -0
- package/src/node/features/port-exposer.ts +11 -1
- package/src/node/features/postgres.ts +17 -1
- package/src/node/features/proc.ts +4 -1
- package/src/node/features/python.ts +63 -14
- package/src/node/features/repl.ts +11 -7
- package/src/node/features/runpod.ts +16 -3
- package/src/node/features/secure-shell.ts +27 -2
- package/src/node/features/semantic-search.ts +12 -1
- package/src/node/features/ui.ts +5 -69
- package/src/node/features/vm.ts +17 -0
- package/src/node/features/window-manager.ts +68 -20
- package/src/node.ts +5 -0
- package/src/scaffolds/generated.ts +492 -290
- package/src/scaffolds/template.ts +9 -0
- package/src/schemas/base.ts +46 -5
- package/src/selector.ts +282 -0
- package/src/server.ts +11 -0
- package/src/servers/express.ts +27 -12
- package/src/servers/socket.ts +45 -11
- package/src/web/clients/socket.ts +4 -1
- package/src/web/container.ts +2 -1
- package/src/web/features/network.ts +7 -1
- package/src/web/features/voice-recognition.ts +16 -1
- package/test/clients-servers.test.ts +2 -1
- package/test/command.test.ts +267 -0
- package/test-integration/assistants-manager.test.ts +10 -20
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/1b/b5/c75b28794f00f94c4d609a98978e9420e9b7146d204a7fbf5b0b30477292581705d207c0100dabaac27eef540aaaece3374af75104a93219d4ec8bfb44e7 +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/da/df/1d90ce4e042abeb035a197832c6d6893420a747a056be773eb00e4f745a037d505c8db13dde7d36b36b6b893addbb7df0f5fe9f0c13e665f20056447318b +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/ed/04/e1d0c2a58c2db29b3921ca2affb3ea4febe831c53b38ebc21019fb799823aba6ed5b4611873d2cd25d422d49955b852a9c326da0d678899bc1c2c2960901 +1 -0
- package/tmp/.cache/luca-disk-cache/index-v5/00/13/572aa4c9a94f99eda999695d050cdd0ca7fe2d23a50af03234d4c8ce0791 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/75/a9/cb61dc0f0589e8ec10a9aca27b834bc73884c479941042d22a2b22324cd3 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/9f/0f/8b1f915ee64cfff7667dd96acd7a5ac0a96aa91a346e19cefd45909a9c9c +2 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
- package/docs/examples/launcher-app-command-listener.md +0 -120
- package/docs/tasks/web-container-helper-discovery.md +0 -71
- package/docs/todos.md +0 -1
- package/scripts/test-command-listener.ts +0 -123
- package/src/node/features/launcher-app-command-listener.ts +0 -389
package/src/client.ts
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { Helper } from "./helper.js";
|
|
2
2
|
import type { Container, ContainerContext } from "./container.js";
|
|
3
|
-
import axios, { type AxiosError, type AxiosInstance, type AxiosRequestConfig } from "axios";
|
|
4
3
|
import { Registry } from "./registry.js";
|
|
5
4
|
import { z } from 'zod'
|
|
6
5
|
import {
|
|
7
6
|
ClientStateSchema, ClientOptionsSchema, ClientEventsSchema,
|
|
8
|
-
WebSocketClientStateSchema, WebSocketClientOptionsSchema, WebSocketClientEventsSchema,
|
|
9
|
-
GraphClientOptionsSchema, GraphClientEventsSchema,
|
|
10
7
|
} from './schemas/base.js'
|
|
11
8
|
|
|
12
9
|
export type ClientOptions = z.infer<typeof ClientOptionsSchema>
|
|
13
10
|
export type ClientState = z.infer<typeof ClientStateSchema>
|
|
14
|
-
export type WebSocketClientState = z.infer<typeof WebSocketClientStateSchema>
|
|
15
|
-
export type WebSocketClientOptions = z.infer<typeof WebSocketClientOptionsSchema>
|
|
16
|
-
export type GraphClientOptions = z.infer<typeof GraphClientOptionsSchema>
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
12
|
+
// Subclass types re-exported for backward compatibility.
|
|
13
|
+
// Import the concrete classes from their individual files:
|
|
14
|
+
// import { RestClient } from './clients/rest'
|
|
15
|
+
// import { GraphClient } from './clients/graph'
|
|
16
|
+
// import { WebSocketClient } from './clients/websocket'
|
|
17
|
+
export type { WebSocketClientState, WebSocketClientOptions } from './clients/websocket.js'
|
|
18
|
+
export type { GraphClientOptions } from './clients/graph.js'
|
|
19
|
+
|
|
20
|
+
// AvailableClients is an open interface — subclasses augment it via `declare module`
|
|
21
|
+
export interface AvailableClients {}
|
|
23
22
|
|
|
24
23
|
export interface ClientsInterface {
|
|
25
24
|
clients: ClientsRegistry;
|
|
@@ -29,6 +28,14 @@ export interface ClientsInterface {
|
|
|
29
28
|
): InstanceType<AvailableClients[T]>;
|
|
30
29
|
}
|
|
31
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Base client class for all Luca network clients. Provides connection state
|
|
33
|
+
* tracking, configuration, and the registry/factory infrastructure for
|
|
34
|
+
* creating typed client instances via `container.client('rest')`.
|
|
35
|
+
*
|
|
36
|
+
* Subclasses should override `connect()` and add protocol-specific methods.
|
|
37
|
+
* Register subclasses using `Client.register(this, 'myClient')` in a static block.
|
|
38
|
+
*/
|
|
32
39
|
export class Client<
|
|
33
40
|
T extends ClientState = ClientState,
|
|
34
41
|
K extends ClientOptions = ClientOptions
|
|
@@ -81,6 +88,7 @@ export class Client<
|
|
|
81
88
|
this.state.set("connected", false);
|
|
82
89
|
}
|
|
83
90
|
|
|
91
|
+
/** The base URL for this client's connections/requests. */
|
|
84
92
|
get baseURL() {
|
|
85
93
|
return this.options.baseURL || ''
|
|
86
94
|
}
|
|
@@ -89,14 +97,17 @@ export class Client<
|
|
|
89
97
|
return this._options as K;
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
/** Configure this client instance with additional options. */
|
|
92
101
|
configure(options?: any): this {
|
|
93
102
|
return this;
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
/** Whether the client is currently connected. */
|
|
96
106
|
get isConnected() {
|
|
97
107
|
return !!this.state.get("connected");
|
|
98
108
|
}
|
|
99
109
|
|
|
110
|
+
/** Establish a connection. Subclasses should override with protocol-specific logic. */
|
|
100
111
|
async connect(): Promise<this> {
|
|
101
112
|
this.state.set("connected", true);
|
|
102
113
|
return this;
|
|
@@ -104,7 +115,7 @@ export class Client<
|
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
// --- Registry and Client.register must be defined BEFORE subclasses ---
|
|
107
|
-
// because static blocks in
|
|
118
|
+
// because static blocks in subclass files run at class declaration time.
|
|
108
119
|
|
|
109
120
|
export class ClientsRegistry extends Registry<Client<any>> {
|
|
110
121
|
override scope = "clients"
|
|
@@ -156,349 +167,4 @@ Client.register = function registerClient(
|
|
|
156
167
|
return SubClass
|
|
157
168
|
}
|
|
158
169
|
|
|
159
|
-
// --- Built-in client subclasses ---
|
|
160
|
-
|
|
161
|
-
export class RestClient<
|
|
162
|
-
T extends ClientState = ClientState,
|
|
163
|
-
K extends ClientOptions = ClientOptions
|
|
164
|
-
> extends Client<T, K> {
|
|
165
|
-
axios!: AxiosInstance;
|
|
166
|
-
|
|
167
|
-
static override shortcut: string = "clients.rest"
|
|
168
|
-
static { Client.register(this, 'rest') }
|
|
169
|
-
|
|
170
|
-
constructor(options: K, context: ContainerContext) {
|
|
171
|
-
super(options, context);
|
|
172
|
-
|
|
173
|
-
this.axios = axios.create({
|
|
174
|
-
baseURL: this.baseURL,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (this.useJSON) {
|
|
178
|
-
this.axios.defaults.headers.common = {
|
|
179
|
-
...this.axios.defaults.headers.common,
|
|
180
|
-
"Content-Type": "application/json",
|
|
181
|
-
Accept: "application/json",
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async beforeRequest() {
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
get useJSON() {
|
|
190
|
-
return !!this.options.json
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
override get baseURL() {
|
|
194
|
-
return this.options.baseURL || '/'
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async patch(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
|
|
198
|
-
await this.beforeRequest();
|
|
199
|
-
return this.axios({
|
|
200
|
-
...options,
|
|
201
|
-
method: "PATCH",
|
|
202
|
-
url,
|
|
203
|
-
data,
|
|
204
|
-
})
|
|
205
|
-
.then((r) => r.data)
|
|
206
|
-
.catch((e: any) => {
|
|
207
|
-
if (e.isAxiosError) {
|
|
208
|
-
return this.handleError(e);
|
|
209
|
-
} else {
|
|
210
|
-
throw e;
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async put(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
|
|
216
|
-
await this.beforeRequest();
|
|
217
|
-
return this.axios({
|
|
218
|
-
...options,
|
|
219
|
-
method: "PUT",
|
|
220
|
-
url,
|
|
221
|
-
data,
|
|
222
|
-
})
|
|
223
|
-
.then((r) => r.data)
|
|
224
|
-
.catch((e: any) => {
|
|
225
|
-
if (e.isAxiosError) {
|
|
226
|
-
return this.handleError(e);
|
|
227
|
-
} else {
|
|
228
|
-
throw e;
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async post(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
|
|
234
|
-
await this.beforeRequest();
|
|
235
|
-
return this.axios({
|
|
236
|
-
...options,
|
|
237
|
-
method: "POST",
|
|
238
|
-
url,
|
|
239
|
-
data,
|
|
240
|
-
})
|
|
241
|
-
.then((r) => r.data)
|
|
242
|
-
.catch((e: any) => {
|
|
243
|
-
if (e.isAxiosError) {
|
|
244
|
-
return this.handleError(e);
|
|
245
|
-
} else {
|
|
246
|
-
throw e;
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async delete(url: string, params: any = {}, options: AxiosRequestConfig = {}) {
|
|
252
|
-
await this.beforeRequest();
|
|
253
|
-
return this.axios({
|
|
254
|
-
...options,
|
|
255
|
-
method: "DELETE",
|
|
256
|
-
url,
|
|
257
|
-
params,
|
|
258
|
-
})
|
|
259
|
-
.then((r) => r.data)
|
|
260
|
-
.catch((e: any) => {
|
|
261
|
-
if (e.isAxiosError) {
|
|
262
|
-
return this.handleError(e);
|
|
263
|
-
} else {
|
|
264
|
-
throw e;
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
async get(url: string, params: any = {}, options: AxiosRequestConfig = {}) {
|
|
271
|
-
await this.beforeRequest()
|
|
272
|
-
return this.axios({
|
|
273
|
-
...options,
|
|
274
|
-
method: "GET",
|
|
275
|
-
url,
|
|
276
|
-
params,
|
|
277
|
-
})
|
|
278
|
-
.then((r) => r.data)
|
|
279
|
-
.catch((e: any) => {
|
|
280
|
-
if (e.isAxiosError) {
|
|
281
|
-
return this.handleError(e);
|
|
282
|
-
} else {
|
|
283
|
-
throw e;
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async handleError(error: AxiosError) {
|
|
289
|
-
this.emit('failure', error)
|
|
290
|
-
return error.toJSON();
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* GraphQL client that wraps RestClient with convenience methods for executing
|
|
296
|
-
* queries and mutations. Automatically handles the GraphQL request envelope
|
|
297
|
-
* (query/variables/operationName) and unwraps responses, extracting the `data`
|
|
298
|
-
* field and emitting events for GraphQL-level errors.
|
|
299
|
-
*/
|
|
300
|
-
export class GraphClient<
|
|
301
|
-
T extends ClientState = ClientState,
|
|
302
|
-
K extends GraphClientOptions = GraphClientOptions
|
|
303
|
-
> extends RestClient<T, K> {
|
|
304
|
-
static override shortcut = "clients.graph" as const
|
|
305
|
-
static override optionsSchema = GraphClientOptionsSchema
|
|
306
|
-
static override eventsSchema = GraphClientEventsSchema
|
|
307
|
-
static { Client.register(this, 'graph') }
|
|
308
|
-
|
|
309
|
-
/** The GraphQL endpoint path. Defaults to '/graphql'. */
|
|
310
|
-
get endpoint() {
|
|
311
|
-
return (this.options as GraphClientOptions).endpoint || '/graphql'
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Execute a GraphQL query and return the unwrapped data.
|
|
316
|
-
* @param query - The GraphQL query string
|
|
317
|
-
* @param variables - Optional variables for the query
|
|
318
|
-
* @param operationName - Optional operation name when the query contains multiple operations
|
|
319
|
-
*/
|
|
320
|
-
async query<R = any>(query: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
|
|
321
|
-
return this.execute<R>(query, variables, operationName)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Execute a GraphQL mutation and return the unwrapped data.
|
|
326
|
-
* Semantically identical to query() but named for clarity when performing mutations.
|
|
327
|
-
* @param mutation - The GraphQL mutation string
|
|
328
|
-
* @param variables - Optional variables for the mutation
|
|
329
|
-
* @param operationName - Optional operation name when the mutation contains multiple operations
|
|
330
|
-
*/
|
|
331
|
-
async mutate<R = any>(mutation: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
|
|
332
|
-
return this.execute<R>(mutation, variables, operationName)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Execute a GraphQL operation, unwrap the response, and handle errors.
|
|
337
|
-
* Posts to the configured endpoint with the standard GraphQL envelope.
|
|
338
|
-
* If the response contains GraphQL-level errors, emits both 'graphqlError'
|
|
339
|
-
* and 'failure' events before returning the data.
|
|
340
|
-
*/
|
|
341
|
-
private async execute<R = any>(query: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
|
|
342
|
-
const body: Record<string, any> = { query }
|
|
343
|
-
if (variables) body.variables = variables
|
|
344
|
-
if (operationName) body.operationName = operationName
|
|
345
|
-
|
|
346
|
-
const response = await this.post(this.endpoint, body)
|
|
347
|
-
|
|
348
|
-
if (response?.errors?.length) {
|
|
349
|
-
this.emit('graphqlError', response.errors)
|
|
350
|
-
this.emit('failure', response.errors)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return response?.data as R
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* WebSocket client that bridges raw WebSocket events to Luca's Helper event bus,
|
|
359
|
-
* providing a clean interface for sending/receiving messages, tracking connection
|
|
360
|
-
* state, and optional auto-reconnection with exponential backoff.
|
|
361
|
-
*
|
|
362
|
-
* Events emitted:
|
|
363
|
-
* - `open` — connection established
|
|
364
|
-
* - `message` — message received (JSON-parsed when possible)
|
|
365
|
-
* - `close` — connection closed (with code and reason)
|
|
366
|
-
* - `error` — connection error
|
|
367
|
-
* - `reconnecting` — attempting reconnection (with attempt number)
|
|
368
|
-
*/
|
|
369
|
-
export class WebSocketClient<
|
|
370
|
-
T extends WebSocketClientState = WebSocketClientState,
|
|
371
|
-
K extends WebSocketClientOptions = WebSocketClientOptions
|
|
372
|
-
> extends Client<T, K> {
|
|
373
|
-
ws!: WebSocket
|
|
374
|
-
_intentionalClose: boolean
|
|
375
|
-
|
|
376
|
-
static override shortcut = "clients.websocket" as const
|
|
377
|
-
static override stateSchema = WebSocketClientStateSchema
|
|
378
|
-
static override optionsSchema = WebSocketClientOptionsSchema
|
|
379
|
-
static override eventsSchema = WebSocketClientEventsSchema
|
|
380
|
-
static { Client.register(this, 'websocket') }
|
|
381
|
-
|
|
382
|
-
constructor(options?: K, context?: ContainerContext) {
|
|
383
|
-
super(options, context)
|
|
384
|
-
this._intentionalClose = false
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
override get initialState(): T {
|
|
388
|
-
return {
|
|
389
|
-
connected: false,
|
|
390
|
-
reconnectAttempts: 0,
|
|
391
|
-
} as T
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Establish a WebSocket connection to the configured baseURL.
|
|
396
|
-
* Wires all raw WebSocket events (open, message, close, error) to the
|
|
397
|
-
* Helper event bus and updates connection state accordingly.
|
|
398
|
-
* Resolves once the connection is open; rejects on error.
|
|
399
|
-
*/
|
|
400
|
-
override async connect(): Promise<this> {
|
|
401
|
-
if (this.isConnected) {
|
|
402
|
-
return this
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const ws = this.ws = new WebSocket(this.baseURL)
|
|
406
|
-
const state = this.state as any
|
|
407
|
-
|
|
408
|
-
await new Promise<void>((resolve, reject) => {
|
|
409
|
-
ws.onopen = () => {
|
|
410
|
-
state.set('connected', true)
|
|
411
|
-
state.set('connectionError', undefined)
|
|
412
|
-
state.set('reconnectAttempts', 0)
|
|
413
|
-
this.emit('open')
|
|
414
|
-
resolve()
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
ws.onerror = (event: any) => {
|
|
418
|
-
state.set('connectionError', event)
|
|
419
|
-
this.emit('error', event)
|
|
420
|
-
reject(event)
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
ws.onmessage = (event: any) => {
|
|
424
|
-
let data = event?.data ?? event
|
|
425
|
-
try {
|
|
426
|
-
data = JSON.parse(data)
|
|
427
|
-
} catch {}
|
|
428
|
-
this.emit('message', data)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
ws.onclose = (event: any) => {
|
|
432
|
-
state.set('connected', false)
|
|
433
|
-
this.emit('close', event?.code, event?.reason)
|
|
434
|
-
if (!this._intentionalClose) {
|
|
435
|
-
this.maybeReconnect()
|
|
436
|
-
}
|
|
437
|
-
this._intentionalClose = false
|
|
438
|
-
}
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
return this
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Send data over the WebSocket connection. Automatically JSON-serializes
|
|
446
|
-
* the payload. If not currently connected, attempts to connect first.
|
|
447
|
-
* @param data - The data to send (will be JSON.stringify'd)
|
|
448
|
-
*/
|
|
449
|
-
async send(data: any): Promise<void> {
|
|
450
|
-
if (!this.isConnected) {
|
|
451
|
-
await this.connect()
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (!this.ws) {
|
|
455
|
-
throw new Error('WebSocket instance not available')
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
this.ws.send(JSON.stringify(data))
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Gracefully close the WebSocket connection. Suppresses auto-reconnect
|
|
463
|
-
* and updates connection state to disconnected.
|
|
464
|
-
*/
|
|
465
|
-
async disconnect(): Promise<this> {
|
|
466
|
-
this._intentionalClose = true
|
|
467
|
-
if (this.ws) {
|
|
468
|
-
this.ws.close()
|
|
469
|
-
}
|
|
470
|
-
;(this.state as any).set('connected', false)
|
|
471
|
-
return this
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/** Whether the client is in an error state. */
|
|
475
|
-
get hasError() {
|
|
476
|
-
return !!(this.state as any).get('connectionError')
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Attempt to reconnect if the reconnect option is enabled and we haven't
|
|
481
|
-
* exceeded maxReconnectAttempts. Uses exponential backoff capped at 30s.
|
|
482
|
-
*/
|
|
483
|
-
private maybeReconnect() {
|
|
484
|
-
const opts = this.options as WebSocketClientOptions
|
|
485
|
-
if (!opts.reconnect) return
|
|
486
|
-
|
|
487
|
-
const state = this.state as any
|
|
488
|
-
const maxAttempts = opts.maxReconnectAttempts ?? Infinity
|
|
489
|
-
const baseInterval = opts.reconnectInterval ?? 1000
|
|
490
|
-
const attempts = ((state.get('reconnectAttempts') as number) ?? 0) + 1
|
|
491
|
-
|
|
492
|
-
if (attempts > maxAttempts) return
|
|
493
|
-
|
|
494
|
-
state.set('reconnectAttempts', attempts)
|
|
495
|
-
this.emit('reconnecting', attempts)
|
|
496
|
-
|
|
497
|
-
const delay = Math.min(baseInterval * Math.pow(2, attempts - 1), 30000)
|
|
498
|
-
setTimeout(() => {
|
|
499
|
-
this.connect().catch(() => {})
|
|
500
|
-
}, delay)
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
170
|
export default Client;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Client,
|
|
3
3
|
type ClientOptions,
|
|
4
|
-
RestClient,
|
|
5
4
|
} from "@soederpop/luca/client";
|
|
5
|
+
import { RestClient } from "../rest";
|
|
6
6
|
import { type ContainerContext } from "@soederpop/luca/container";
|
|
7
7
|
import { isEmpty, maxBy, omitBy } from "lodash-es";
|
|
8
8
|
import { NodeContainer } from "@soederpop/luca/node/container";
|
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
type ClientOptions,
|
|
3
3
|
type ClientsInterface,
|
|
4
4
|
clients,
|
|
5
|
-
RestClient,
|
|
6
5
|
} from "@soederpop/luca/client";
|
|
6
|
+
import { RestClient } from "./rest";
|
|
7
7
|
import { type ContainerContext } from "@soederpop/luca/container";
|
|
8
8
|
import { z } from 'zod'
|
|
9
9
|
import { ClientStateSchema } from '@soederpop/luca/schemas/base.js'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Client,
|
|
3
|
-
RestClient,
|
|
4
3
|
} from "@soederpop/luca/client";
|
|
4
|
+
import { RestClient } from "../rest";
|
|
5
5
|
import type { ContainerContext } from "@soederpop/luca/container";
|
|
6
6
|
import { z } from 'zod'
|
|
7
|
-
import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca/schemas/base.js'
|
|
7
|
+
import { ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca/schemas/base.js'
|
|
8
8
|
|
|
9
9
|
declare module "@soederpop/luca/client" {
|
|
10
10
|
interface AvailableClients {
|
|
@@ -25,6 +25,16 @@ export const ComfyUIClientOptionsSchema = ClientOptionsSchema.extend({
|
|
|
25
25
|
export type ComfyUIClientState = z.infer<typeof ComfyUIClientStateSchema>
|
|
26
26
|
export type ComfyUIClientOptions = z.infer<typeof ComfyUIClientOptionsSchema>
|
|
27
27
|
|
|
28
|
+
export const ComfyUIClientEventsSchema = ClientEventsSchema.extend({
|
|
29
|
+
execution_start: z.tuple([z.object({ promptId: z.string().describe('The prompt ID that started executing') })]).describe('Emitted when prompt execution begins'),
|
|
30
|
+
executing: z.tuple([z.object({ node: z.string().describe('The node ID currently executing'), promptId: z.string().describe('The prompt ID') })]).describe('Emitted when a specific node begins executing'),
|
|
31
|
+
progress: z.tuple([z.object({ node: z.string().describe('The node ID'), value: z.number().describe('Current progress value'), max: z.number().describe('Maximum progress value'), promptId: z.string().describe('The prompt ID') })]).describe('Emitted during node execution with progress updates'),
|
|
32
|
+
executed: z.tuple([z.object({ node: z.string().describe('The node ID that finished'), output: z.any().describe('The node output data'), promptId: z.string().describe('The prompt ID') })]).describe('Emitted when a node finishes execution'),
|
|
33
|
+
execution_cached: z.tuple([z.object({ nodes: z.array(z.string()).describe('Array of cached node IDs'), promptId: z.string().describe('The prompt ID') })]).describe('Emitted when nodes are served from cache'),
|
|
34
|
+
execution_error: z.tuple([z.object({ promptId: z.string().describe('The prompt ID'), exception_message: z.string().optional().describe('Error message') }).passthrough()]).describe('Emitted when prompt execution fails'),
|
|
35
|
+
execution_complete: z.tuple([z.object({ promptId: z.string().describe('The prompt ID that completed') })]).describe('Emitted when prompt execution finishes successfully'),
|
|
36
|
+
}).describe('ComfyUI client events')
|
|
37
|
+
|
|
28
38
|
/** Maps a semantic input name to a specific node ID and field */
|
|
29
39
|
export type InputMapping = Record<string, { nodeId: string; field: string }>;
|
|
30
40
|
|
|
@@ -66,6 +76,7 @@ export class ComfyUIClient extends RestClient<ComfyUIClientState, ComfyUIClientO
|
|
|
66
76
|
static override description = "ComfyUI workflow execution client";
|
|
67
77
|
static override stateSchema = ComfyUIClientStateSchema;
|
|
68
78
|
static override optionsSchema = ComfyUIClientOptionsSchema;
|
|
79
|
+
static override eventsSchema = ComfyUIClientEventsSchema;
|
|
69
80
|
|
|
70
81
|
static { Client.register(this, 'comfyui') }
|
|
71
82
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca/schemas/base.js'
|
|
3
|
-
import { Client
|
|
3
|
+
import { Client } from "@soederpop/luca/client";
|
|
4
|
+
import { RestClient } from "../rest";
|
|
4
5
|
import type { ContainerContext } from "@soederpop/luca/container";
|
|
5
6
|
import type { AxiosRequestConfig } from 'axios'
|
|
6
7
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { Client } from '../client.js'
|
|
3
|
+
import { RestClient } from './rest.js'
|
|
4
|
+
import type { ClientState } from '../client.js'
|
|
5
|
+
import {
|
|
6
|
+
GraphClientOptionsSchema, GraphClientEventsSchema,
|
|
7
|
+
} from '../schemas/base.js'
|
|
8
|
+
|
|
9
|
+
export type GraphClientOptions = z.infer<typeof GraphClientOptionsSchema>
|
|
10
|
+
|
|
11
|
+
declare module '../client' {
|
|
12
|
+
interface AvailableClients {
|
|
13
|
+
graph: typeof GraphClient
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* GraphQL client that wraps RestClient with convenience methods for executing
|
|
19
|
+
* queries and mutations. Automatically handles the GraphQL request envelope
|
|
20
|
+
* (query/variables/operationName) and unwraps responses, extracting the `data`
|
|
21
|
+
* field and emitting events for GraphQL-level errors.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const gql = container.client('graph', { baseURL: 'https://api.example.com' })
|
|
26
|
+
* const data = await gql.query(`{ users { id name } }`)
|
|
27
|
+
* await gql.mutate(`mutation($name: String!) { createUser(name: $name) { id } }`, { name: 'Alice' })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class GraphClient<
|
|
31
|
+
T extends ClientState = ClientState,
|
|
32
|
+
K extends GraphClientOptions = GraphClientOptions
|
|
33
|
+
> extends RestClient<T, K> {
|
|
34
|
+
static override shortcut = "clients.graph" as const
|
|
35
|
+
static override optionsSchema = GraphClientOptionsSchema
|
|
36
|
+
static override eventsSchema = GraphClientEventsSchema
|
|
37
|
+
static { Client.register(this, 'graph') }
|
|
38
|
+
|
|
39
|
+
/** The GraphQL endpoint path. Defaults to '/graphql'. */
|
|
40
|
+
get endpoint() {
|
|
41
|
+
return (this.options as GraphClientOptions).endpoint || '/graphql'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Execute a GraphQL query and return the unwrapped data.
|
|
46
|
+
* @param query - The GraphQL query string
|
|
47
|
+
* @param variables - Optional variables for the query
|
|
48
|
+
* @param operationName - Optional operation name when the query contains multiple operations
|
|
49
|
+
*/
|
|
50
|
+
async query<R = any>(query: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
|
|
51
|
+
return this.execute<R>(query, variables, operationName)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Execute a GraphQL mutation and return the unwrapped data.
|
|
56
|
+
* Semantically identical to query() but named for clarity when performing mutations.
|
|
57
|
+
* @param mutation - The GraphQL mutation string
|
|
58
|
+
* @param variables - Optional variables for the mutation
|
|
59
|
+
* @param operationName - Optional operation name when the mutation contains multiple operations
|
|
60
|
+
*/
|
|
61
|
+
async mutate<R = any>(mutation: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
|
|
62
|
+
return this.execute<R>(mutation, variables, operationName)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Execute a GraphQL operation, unwrap the response, and handle errors.
|
|
67
|
+
* Posts to the configured endpoint with the standard GraphQL envelope.
|
|
68
|
+
* If the response contains GraphQL-level errors, emits both 'graphqlError'
|
|
69
|
+
* and 'failure' events before returning the data.
|
|
70
|
+
*/
|
|
71
|
+
private async execute<R = any>(query: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
|
|
72
|
+
const body: Record<string, any> = { query }
|
|
73
|
+
if (variables) body.variables = variables
|
|
74
|
+
if (operationName) body.operationName = operationName
|
|
75
|
+
|
|
76
|
+
const response = await this.post(this.endpoint, body)
|
|
77
|
+
|
|
78
|
+
if (response?.errors?.length) {
|
|
79
|
+
this.emit('graphqlError', response.errors)
|
|
80
|
+
this.emit('failure', response.errors)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return response?.data as R
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default GraphClient
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca/schemas/base.js'
|
|
2
|
+
import { ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca/schemas/base.js'
|
|
3
3
|
import { Client } from "@soederpop/luca/client";
|
|
4
4
|
import type { Container, ContainerContext } from "@soederpop/luca/container";
|
|
5
5
|
|
|
@@ -27,6 +27,14 @@ export const OpenAIClientOptionsSchema = ClientOptionsSchema.extend({
|
|
|
27
27
|
})
|
|
28
28
|
export type OpenAIClientOptions = z.infer<typeof OpenAIClientOptionsSchema>
|
|
29
29
|
|
|
30
|
+
export const OpenAIClientEventsSchema = ClientEventsSchema.extend({
|
|
31
|
+
connected: z.tuple([]).describe('Emitted when the API connection is verified'),
|
|
32
|
+
completion: z.tuple([z.any().describe('The completion or response object')]).describe('Emitted after a chat completion, legacy completion, or response is created'),
|
|
33
|
+
embedding: z.tuple([z.any().describe('The embedding response object')]).describe('Emitted after embeddings are created'),
|
|
34
|
+
image: z.tuple([z.any().describe('The image generation response object')]).describe('Emitted after an image is generated'),
|
|
35
|
+
models: z.tuple([z.any().describe('The models list response')]).describe('Emitted after listing available models'),
|
|
36
|
+
}).describe('OpenAI client events')
|
|
37
|
+
|
|
30
38
|
/**
|
|
31
39
|
* OpenAI client — wraps the OpenAI SDK for chat completions, responses API, embeddings, and image generation.
|
|
32
40
|
*
|
|
@@ -47,6 +55,7 @@ export class OpenAIClient extends Client<OpenAIClientState, OpenAIClientOptions>
|
|
|
47
55
|
static override envVars = ['OPENAI_API_KEY']
|
|
48
56
|
static override stateSchema = OpenAIClientStateSchema
|
|
49
57
|
static override optionsSchema = OpenAIClientOptionsSchema
|
|
58
|
+
static override eventsSchema = OpenAIClientEventsSchema
|
|
50
59
|
|
|
51
60
|
static { Client.register(this, 'openai') }
|
|
52
61
|
|