modulex-js 0.1.0 → 1.0.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/README.md CHANGED
@@ -10,9 +10,9 @@ Official JavaScript/TypeScript SDK for the [ModuleX](https://modulex.dev) AI wor
10
10
  - Zero runtime dependencies
11
11
  - Full TypeScript support with exported types
12
12
  - Dual ESM + CommonJS build
13
- - SSE streaming for real-time workflow events
13
+ - SSE streaming for workflows, executions, composer, and assistant (incl. HITL)
14
14
  - Automatic retries with exponential backoff
15
- - 122 API endpoints covered
15
+ - 130 API endpoints across 17 resource groups
16
16
 
17
17
  ## Installation
18
18
 
@@ -143,8 +143,23 @@ await client.executions.resume({
143
143
  await client.executions.cancel(run.run_id, { reason: 'No longer needed' });
144
144
  ```
145
145
 
146
+ ### Workflow Run History (durable)
147
+
148
+ ```typescript
149
+ // List persisted runs (newest first; cursor-style has_more pagination)
150
+ const { runs, has_more } = await client.workflowRuns.list({ workflowId: 'workflow-uuid', limit: 20 });
151
+
152
+ // Full run detail — pass the run's table id (run.id), NOT the string run_id
153
+ const detail = await client.workflowRuns.get(runs[0].id);
154
+ ```
155
+
146
156
  ### SSE Streaming
147
157
 
158
+ Workflow/composer/assistant streams are **data-only** — discriminate on `event.type`
159
+ (not `event.event`, which is always `"message"`). Some frames are flat
160
+ (`node_update`/`node_started`/`error`) and some are wrapped under `data`
161
+ (`metadata`/`done`/`interrupt`/`resumed`/`cancelled`).
162
+
148
163
  ```typescript
149
164
  const run = await client.executions.run({
150
165
  workflowId: 'workflow-uuid',
@@ -153,15 +168,15 @@ const run = await client.executions.run({
153
168
  });
154
169
 
155
170
  for await (const event of client.executions.listen(run.run_id)) {
156
- switch (event.event) {
171
+ switch (event.type) {
157
172
  case 'node_update':
158
- console.log(`Node ${event.data.node_id}: ${event.data.status}`);
173
+ console.log(`Node ${event.node} (${event.name})`, event.output); // flat
159
174
  break;
160
175
  case 'done':
161
- console.log(`Completed in ${event.data.total_execution_time_ms}ms`);
176
+ console.log(event.data.message); // wrapped
162
177
  break;
163
178
  case 'error':
164
- console.error(event.data.error_message);
179
+ console.error(event.message); // flat
165
180
  break;
166
181
  }
167
182
  }
@@ -224,14 +239,6 @@ await client.schedules.pause(schedule.id);
224
239
  await client.schedules.resume(schedule.id);
225
240
  ```
226
241
 
227
- ### Templates
228
-
229
- ```typescript
230
- const templates = await client.templates.list();
231
- const template = await client.templates.get('template-uuid');
232
- const used = await client.templates.use('template-uuid');
233
- ```
234
-
235
242
  ### Deployments
236
243
 
237
244
  ```typescript
@@ -250,10 +257,39 @@ const session = await client.composer.chat({
250
257
  });
251
258
 
252
259
  for await (const event of client.composer.listen(session.composer_chat_id, session.run_id)) {
253
- console.log(event.event, event.data);
260
+ if (event.type === 'user_input_request') {
261
+ // Human-in-the-loop: the agent is asking a structured question.
262
+ const q = event.data; // UserInputRequest (discriminated on `kind`)
263
+ await client.composer.resume(session.composer_chat_id, {
264
+ requestId: q.request_id,
265
+ response: { kind: 'yes_no', answer: true },
266
+ llm: { integration_name: 'openai', provider_id: 'openai', model_id: 'gpt-4o-mini' },
267
+ });
268
+ }
254
269
  }
255
270
 
256
- await client.composer.save(session.composer_chat_id);
271
+ // Save returns a workflow_sync payload for refreshing the canvas
272
+ const saved = await client.composer.save(session.composer_chat_id);
273
+ ```
274
+
275
+ ### Assistant (HITL chat agent)
276
+
277
+ ```typescript
278
+ // A general chat agent with the same HITL flow as Composer, not bound to a workflow.
279
+ const chat = await client.assistant.chat({ message: 'Help me set up a GitHub integration' });
280
+
281
+ for await (const event of client.assistant.listen(chat.chat_id, chat.run_id)) {
282
+ if (event.type === 'response_chunk') process.stdout.write(String(event.delta ?? ''));
283
+ if (event.type === 'user_input_request') {
284
+ await client.assistant.resume(chat.chat_id, {
285
+ requestId: event.data.request_id,
286
+ response: { kind: 'skipped' },
287
+ llm: { integration_name: 'openai', provider_id: 'openai', model_id: 'gpt-4o-mini' },
288
+ });
289
+ }
290
+ }
291
+
292
+ const { items } = await client.assistant.list({ limit: 20 });
257
293
  ```
258
294
 
259
295
  ### Dashboard & Analytics
@@ -264,20 +300,26 @@ const overview = await client.dashboard.analyticsOverview();
264
300
  const users = await client.dashboard.users({ search: 'john' });
265
301
  ```
266
302
 
267
- ### Subscriptions
303
+ ### System
268
304
 
269
305
  ```typescript
270
- const plans = await client.subscriptions.organizationPlans();
271
- const billing = await client.subscriptions.billing();
272
- const checkout = await client.subscriptions.checkoutLink({ planId: 'plan-uuid', interval: 'month' });
306
+ // Timezone utilities (e.g. for building a schedule timezone picker)
307
+ const { popular, all_timezones } = await client.system.timezones();
308
+ const matches = await client.system.searchTimezones('Istanbul');
273
309
  ```
274
310
 
275
- ### System
311
+ ## Real-time collaboration (WebSocket)
276
312
 
277
- ```typescript
278
- const health = await client.system.health();
279
- const timezones = await client.system.timezones();
280
- ```
313
+ ModuleX also runs a Socket.IO server (`modulex-ws`) for live multi-user workflow
314
+ collaboration cursors, node locks, JSON-patch sync, and presence. **This is
315
+ intentionally out of scope for this SDK.** It uses a different transport
316
+ (Socket.IO, not REST/SSE), a different auth model (Clerk browser JWT, not the
317
+ `mx_live_` api key), and a camelCase wire format incompatible with this client's
318
+ snake_case conventions. If you need realtime collaboration, connect with a
319
+ `socket.io-client` directly; a dedicated `@modulex/realtime` package may be
320
+ provided in the future. This SDK covers the REST API and its SSE execution
321
+ streams (`executions.listen`, `workflows.listenChanges`, `composer.listen`,
322
+ `assistant.listen`).
281
323
 
282
324
  ## Error Handling
283
325
 
@@ -296,7 +338,9 @@ try {
296
338
  if (error instanceof NotFoundError) {
297
339
  console.log('Workflow not found');
298
340
  } else if (error instanceof RateLimitError) {
299
- console.log(`Rate limited. Retry after ${error.retryAfter}s`);
341
+ // Rate-limit + billing errors expose structured fields parsed from the envelope
342
+ console.log(`Rate limited (${error.code}): ${error.reason}. Retry after ${error.retryAfter}s`);
343
+ console.log(`limit=${error.limit} remaining=${error.remaining} reset=${error.reset}`);
300
344
  } else if (error instanceof AuthenticationError) {
301
345
  console.log('Invalid API key');
302
346
  } else if (error instanceof ValidationError) {
@@ -305,6 +349,10 @@ try {
305
349
  }
306
350
  ```
307
351
 
352
+ All `ModulexError` subclasses expose `.code`, `.reason`, and `.layer` when the API
353
+ returns a structured error envelope (e.g. billing/rate-limit denials), in addition
354
+ to `.status`, `.body`, and `.headers`.
355
+
308
356
  The SDK automatically retries requests on transient errors (429, 500, 502, 503) with exponential backoff.
309
357
 
310
358
  ## Cancellation