orchestrator-client 5.7.4 → 5.7.12

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/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ DEFAULT_FLOW_TIMEOUT_MS: () => DEFAULT_FLOW_TIMEOUT_MS,
33
34
  EVENT_ERROR_EVENT_RECORDED: () => EVENT_ERROR_EVENT_RECORDED,
34
35
  EVENT_MESSAGE_ADDED: () => EVENT_MESSAGE_ADDED,
35
36
  EVENT_MESSAGE_STREAMING: () => EVENT_MESSAGE_STREAMING,
@@ -41,6 +42,10 @@ __export(index_exports, {
41
42
  EVENT_TASK_ITERATION_CHANGED: () => EVENT_TASK_ITERATION_CHANGED,
42
43
  EVENT_TASK_RESULT_UPDATED: () => EVENT_TASK_RESULT_UPDATED,
43
44
  EVENT_TASK_STATUS_CHANGED: () => EVENT_TASK_STATUS_CHANGED,
45
+ Flow: () => Flow,
46
+ FlowCancelledError: () => FlowCancelledError,
47
+ FlowError: () => FlowError,
48
+ FlowTimeoutError: () => FlowTimeoutError,
44
49
  Orchestrator: () => Orchestrator,
45
50
  OrchestratorAPIError: () => OrchestratorAPIError,
46
51
  OrchestratorAsync: () => OrchestratorAsync,
@@ -54,7 +59,9 @@ __export(index_exports, {
54
59
  camelToSnake: () => camelToSnake,
55
60
  createInsecureFetch: () => createInsecureFetch,
56
61
  deepCamelCase: () => deepCamelCase,
62
+ extractJsonFromMessage: () => extractJsonFromMessage,
57
63
  loadConfig: () => loadConfig,
64
+ setupDefaultClient: () => setupDefaultClient,
58
65
  snakeToCamel: () => snakeToCamel
59
66
  });
60
67
  module.exports = __toCommonJS(index_exports);
@@ -232,6 +239,11 @@ var OrchestratorAsync = class {
232
239
  _makeUrl(path) {
233
240
  return `${this._baseUrl}${path}`;
234
241
  }
242
+ _makeAbsoluteUrl(path) {
243
+ const raw = this._makeUrl(path);
244
+ const base = typeof globalThis.location !== "undefined" ? globalThis.location.href : void 0;
245
+ return new URL(raw, base);
246
+ }
235
247
  async _resolveHeaders() {
236
248
  const headers = {};
237
249
  if (this._apiKey) {
@@ -248,7 +260,7 @@ var OrchestratorAsync = class {
248
260
  return headers;
249
261
  }
250
262
  async _request(method, path, opts) {
251
- const url = new URL(this._makeUrl(path));
263
+ const url = this._makeAbsoluteUrl(path);
252
264
  if (opts?.params) {
253
265
  for (const [key, value] of Object.entries(opts.params)) {
254
266
  if (value !== void 0) {
@@ -1042,7 +1054,7 @@ var OrchestratorAsync = class {
1042
1054
  // ------------------------------------------------------------------
1043
1055
  async listErrors(params) {
1044
1056
  const authHeaders = await this._resolveHeaders();
1045
- const url = new URL(this._makeUrl("/errors"));
1057
+ const url = this._makeAbsoluteUrl("/errors");
1046
1058
  if (params?.page !== void 0)
1047
1059
  url.searchParams.set("page", String(params.page));
1048
1060
  if (params?.limit !== void 0)
@@ -1836,6 +1848,11 @@ var RealtimeClient = class {
1836
1848
  this._socket = null;
1837
1849
  this._handlers = /* @__PURE__ */ new Map();
1838
1850
  this._connected = false;
1851
+ // Every room joined through any API (subscribeTask, joinRooms, subscribe(),
1852
+ // subscribeEvents, etc.) is tracked here so it can be replayed on reconnect.
1853
+ // Server-side room membership is tied to the socket connection and is lost
1854
+ // on disconnect — the full set must be re-emitted on every reconnect.
1855
+ this._rooms = /* @__PURE__ */ new Set();
1839
1856
  // Multi-subscriber room-diffing state (mirrors WebSocketProvider)
1840
1857
  this._subscriptions = /* @__PURE__ */ new Map();
1841
1858
  this._currentRooms = /* @__PURE__ */ new Set();
@@ -1872,6 +1889,10 @@ var RealtimeClient = class {
1872
1889
  this._socket.on("connect", () => {
1873
1890
  this._connected = true;
1874
1891
  this._currentRooms = /* @__PURE__ */ new Set();
1892
+ if (this._rooms.size > 0) {
1893
+ this._socket?.emit("join", { rooms: [...this._rooms] });
1894
+ this._currentRooms = new Set(this._rooms);
1895
+ }
1875
1896
  this._syncRooms();
1876
1897
  });
1877
1898
  this._socket.on("disconnect", () => {
@@ -1929,7 +1950,9 @@ var RealtimeClient = class {
1929
1950
  */
1930
1951
  subscribeTask(taskId) {
1931
1952
  if (!this._socket) throw new Error("RealtimeClient not connected");
1932
- this._socket.emit("join", { rooms: [`task:${taskId}`] });
1953
+ const room = `task:${taskId}`;
1954
+ this._socket.emit("join", { rooms: [room] });
1955
+ this._rooms.add(room);
1933
1956
  }
1934
1957
  /**
1935
1958
  * Unsubscribe from realtime events for a specific task.
@@ -1937,7 +1960,9 @@ var RealtimeClient = class {
1937
1960
  */
1938
1961
  unsubscribeTask(taskId) {
1939
1962
  if (!this._socket) throw new Error("RealtimeClient not connected");
1940
- this._socket.emit("leave", { rooms: [`task:${taskId}`] });
1963
+ const room = `task:${taskId}`;
1964
+ this._socket.emit("leave", { rooms: [room] });
1965
+ this._rooms.delete(room);
1941
1966
  }
1942
1967
  /**
1943
1968
  * Subscribe to event-type-scoped rooms.
@@ -1947,6 +1972,7 @@ var RealtimeClient = class {
1947
1972
  if (!this._socket) throw new Error("RealtimeClient not connected");
1948
1973
  const rooms = eventTypes.map((t) => `event:${t}`);
1949
1974
  this._socket.emit("join", { rooms });
1975
+ for (const r of rooms) this._rooms.add(r);
1950
1976
  }
1951
1977
  /**
1952
1978
  * Unsubscribe from event-type-scoped rooms.
@@ -1955,6 +1981,7 @@ var RealtimeClient = class {
1955
1981
  if (!this._socket) throw new Error("RealtimeClient not connected");
1956
1982
  const rooms = eventTypes.map((t) => `event:${t}`);
1957
1983
  this._socket.emit("leave", { rooms });
1984
+ for (const r of rooms) this._rooms.delete(r);
1958
1985
  }
1959
1986
  /**
1960
1987
  * Subscribe to the `all` broadcast room (receives all events).
@@ -1962,20 +1989,25 @@ var RealtimeClient = class {
1962
1989
  subscribeAll() {
1963
1990
  if (!this._socket) throw new Error("RealtimeClient not connected");
1964
1991
  this._socket.emit("join", { rooms: ["all"] });
1992
+ this._rooms.add("all");
1965
1993
  }
1966
1994
  /**
1967
1995
  * Subscribe to a locale-specific room.
1968
1996
  */
1969
1997
  subscribeLocale(locale) {
1970
1998
  if (!this._socket) throw new Error("RealtimeClient not connected");
1971
- this._socket.emit("join", { rooms: [`locale:${locale}`] });
1999
+ const room = `locale:${locale}`;
2000
+ this._socket.emit("join", { rooms: [room] });
2001
+ this._rooms.add(room);
1972
2002
  }
1973
2003
  /**
1974
2004
  * Unsubscribe from a locale-specific room.
1975
2005
  */
1976
2006
  unsubscribeLocale(locale) {
1977
2007
  if (!this._socket) throw new Error("RealtimeClient not connected");
1978
- this._socket.emit("leave", { rooms: [`locale:${locale}`] });
2008
+ const room = `locale:${locale}`;
2009
+ this._socket.emit("leave", { rooms: [room] });
2010
+ this._rooms.delete(room);
1979
2011
  }
1980
2012
  /**
1981
2013
  * Join arbitrary rooms by name.
@@ -1983,6 +2015,7 @@ var RealtimeClient = class {
1983
2015
  joinRooms(rooms) {
1984
2016
  if (!this._socket) throw new Error("RealtimeClient not connected");
1985
2017
  this._socket.emit("join", { rooms });
2018
+ for (const r of rooms) this._rooms.add(r);
1986
2019
  }
1987
2020
  /**
1988
2021
  * Leave arbitrary rooms by name.
@@ -1990,6 +2023,7 @@ var RealtimeClient = class {
1990
2023
  leaveRooms(rooms) {
1991
2024
  if (!this._socket) throw new Error("RealtimeClient not connected");
1992
2025
  this._socket.emit("leave", { rooms });
2026
+ for (const r of rooms) this._rooms.delete(r);
1993
2027
  }
1994
2028
  // ------------------------------------------------------------------
1995
2029
  // Multi-subscriber API (mirrors WebSocketProvider room-diffing)
@@ -2047,6 +2081,8 @@ var RealtimeClient = class {
2047
2081
  const toLeave = [...this._currentRooms].filter((r) => !needed.has(r));
2048
2082
  if (toJoin.length) socket.emit("join", { rooms: toJoin });
2049
2083
  if (toLeave.length) socket.emit("leave", { rooms: toLeave });
2084
+ for (const r of toJoin) this._rooms.add(r);
2085
+ for (const r of toLeave) this._rooms.delete(r);
2050
2086
  this._currentRooms = needed;
2051
2087
  }
2052
2088
  /**
@@ -2121,10 +2157,303 @@ var RealtimeClient = class {
2121
2157
  }
2122
2158
  };
2123
2159
 
2160
+ // src/flow.ts
2161
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
2162
+ "completed",
2163
+ "failed",
2164
+ "cancelled",
2165
+ "translation"
2166
+ ]);
2167
+ var DEFAULT_FLOW_TIMEOUT_MS = 6e5;
2168
+ var _defaultClient;
2169
+ var _defaultRealtime;
2170
+ function setupDefaultClient(client, realtime) {
2171
+ _defaultClient = client;
2172
+ _defaultRealtime = realtime;
2173
+ }
2174
+ function getDefaultClient() {
2175
+ if (!_defaultClient) {
2176
+ throw new Error(
2177
+ "No default OrchestratorAsync set. Call setupDefaultClient() during application startup, or pass client to flow.run()."
2178
+ );
2179
+ }
2180
+ return _defaultClient;
2181
+ }
2182
+ function getDefaultRealtime() {
2183
+ return _defaultRealtime;
2184
+ }
2185
+ var FlowError = class extends Error {
2186
+ constructor(message) {
2187
+ super(message);
2188
+ this.name = "FlowError";
2189
+ }
2190
+ };
2191
+ var FlowTimeoutError = class extends FlowError {
2192
+ constructor(message) {
2193
+ super(message);
2194
+ this.name = "FlowTimeoutError";
2195
+ }
2196
+ };
2197
+ var FlowCancelledError = class extends FlowError {
2198
+ constructor(taskId) {
2199
+ super(`Task ${taskId} was cancelled`);
2200
+ this.name = "FlowCancelledError";
2201
+ this.taskId = taskId;
2202
+ }
2203
+ };
2204
+ function extractJsonFromMessage(content) {
2205
+ const cleaned = content.replace(/^\ufeff|\u200b|\u200c|\u200d/, "").trim();
2206
+ if (cleaned.startsWith("{")) {
2207
+ try {
2208
+ return JSON.parse(cleaned);
2209
+ } catch {
2210
+ }
2211
+ }
2212
+ const start = content.indexOf("{");
2213
+ const end = content.lastIndexOf("}");
2214
+ if (start !== -1 && end !== -1 && end > start) {
2215
+ try {
2216
+ return JSON.parse(content.slice(start, end + 1));
2217
+ } catch {
2218
+ }
2219
+ }
2220
+ const jsonFenceMatch = content.match(/```(?:json)\s*\n([\s\S]*?)\n```/i);
2221
+ if (jsonFenceMatch) {
2222
+ try {
2223
+ return JSON.parse(jsonFenceMatch[1].trim());
2224
+ } catch {
2225
+ }
2226
+ }
2227
+ const anyFenceMatch = content.match(/```(?:[\w]*)\s*\n([\s\S]*?)\n```/);
2228
+ if (anyFenceMatch) {
2229
+ try {
2230
+ return JSON.parse(anyFenceMatch[1].trim());
2231
+ } catch {
2232
+ }
2233
+ }
2234
+ const bareBraceMatch = content.match(/\{[\s\S]*\}/);
2235
+ if (bareBraceMatch) {
2236
+ try {
2237
+ return JSON.parse(bareBraceMatch[0]);
2238
+ } catch {
2239
+ }
2240
+ }
2241
+ throw new FlowError(
2242
+ "Could not extract a valid JSON object from the agent's final message. The agent did not format its answer as required."
2243
+ );
2244
+ }
2245
+ var Flow = class {
2246
+ constructor() {
2247
+ // -- Workflow-level defaults (override in subclass) --------------------
2248
+ /** Orchestrator workflow type — `"proactive"` by default. */
2249
+ this.workflowId = "proactive";
2250
+ /** Maximum agent turns before the orchestrator forces a failure. */
2251
+ this.maxIterations = 100;
2252
+ /** LLM reasoning budget: `"low"`, `"medium"`, or `"high"`. */
2253
+ this.reasoningEffort = "medium";
2254
+ /**
2255
+ * Maximum milliseconds to wait for the orchestrator task to reach a
2256
+ * terminal state. When exceeded, {@link FlowTimeoutError} is raised.
2257
+ */
2258
+ this.flowTimeoutMs = DEFAULT_FLOW_TIMEOUT_MS;
2259
+ }
2260
+ /**
2261
+ * Optional system-prompt override.
2262
+ * When set, this replaces the orchestrator's default system prompt.
2263
+ */
2264
+ get systemPrompt() {
2265
+ return void 0;
2266
+ }
2267
+ /**
2268
+ * Optional developer-prompt override appended after system prompt.
2269
+ */
2270
+ get developerPrompt() {
2271
+ return void 0;
2272
+ }
2273
+ /**
2274
+ * Restrict which MCP / built-in tools the agent may use.
2275
+ * `undefined` means *all* tools are available. An empty array means
2276
+ * *no* tools — text-only reasoning.
2277
+ */
2278
+ get availableTools() {
2279
+ return void 0;
2280
+ }
2281
+ /**
2282
+ * Per-task feature toggles sent in the creation request.
2283
+ * By default summaries and translation are disabled since flow
2284
+ * output is typically machine-consumed, not human-read.
2285
+ * Override in subclasses that produce human-facing content.
2286
+ */
2287
+ get taskOptions() {
2288
+ return { disableSummaries: true, disableTranslation: true };
2289
+ }
2290
+ /**
2291
+ * Override the agent model for this flow.
2292
+ */
2293
+ get agentModelId() {
2294
+ return void 0;
2295
+ }
2296
+ /**
2297
+ * Override the orchestrator (validation) model for this flow.
2298
+ */
2299
+ get orchestratorModelId() {
2300
+ return void 0;
2301
+ }
2302
+ /**
2303
+ * Cancel the underlying orchestrator task, if one is running.
2304
+ *
2305
+ * Calling `cancel()` after `run()` has returned is a no-op.
2306
+ * The `run()` promise will reject with {@link FlowCancelledError}
2307
+ * after the orchestrator task transitions to `"cancelled"`.
2308
+ */
2309
+ async cancel(client) {
2310
+ if (!this._lastTaskId) return;
2311
+ const resolvedClient = client ?? getDefaultClient();
2312
+ await resolvedClient.cancelTask(this._lastTaskId);
2313
+ }
2314
+ // -- Lifecycle ---------------------------------------------------------
2315
+ /**
2316
+ * Execute the flow end-to-end.
2317
+ *
2318
+ * @returns The parsed result.
2319
+ * @throws {FlowError} If the task fails, times out, or cannot be parsed.
2320
+ */
2321
+ async run(params) {
2322
+ const client = params?.client ?? getDefaultClient();
2323
+ const realtime = params?.realtime ?? getDefaultRealtime();
2324
+ const timeoutMs = params?.timeoutMs ?? this.flowTimeoutMs;
2325
+ console.log(
2326
+ `[Flow] Starting ${this.constructor.name} \u2014 workflow=${this.workflowId} goal=${this.goalPrompt.slice(0, 80)}`
2327
+ );
2328
+ const response = await client.createTask({
2329
+ workflowId: this.workflowId,
2330
+ goalPrompt: this.goalPrompt,
2331
+ systemPrompt: this.systemPrompt,
2332
+ developerPrompt: this.developerPrompt,
2333
+ availableTools: this.availableTools,
2334
+ options: this.taskOptions,
2335
+ maxIterations: this.maxIterations,
2336
+ reasoningEffort: this.reasoningEffort,
2337
+ agentModelId: this.agentModelId,
2338
+ orchestratorModelId: this.orchestratorModelId
2339
+ });
2340
+ this._lastTaskId = response.taskId;
2341
+ const taskId = response.taskId;
2342
+ console.log(
2343
+ `[Flow] Task created \u2014 taskId=${taskId} status=${response.status}`
2344
+ );
2345
+ const status = await this._waitForTerminal(client, taskId, {
2346
+ realtime,
2347
+ timeoutMs
2348
+ });
2349
+ if (status.status === "cancelled") {
2350
+ throw new FlowCancelledError(taskId);
2351
+ }
2352
+ const finalMessage = await this._getFinalMessage(client, taskId);
2353
+ console.log(
2354
+ `[Flow] ${this.constructor.name} completed \u2014 taskId=${taskId} messageLen=${finalMessage.length}`
2355
+ );
2356
+ return this.parseResult(finalMessage);
2357
+ }
2358
+ // -- Completion waiting -----------------------------------------------
2359
+ async _waitForTerminal(client, taskId, opts) {
2360
+ const { realtime, timeoutMs } = opts;
2361
+ const status = await client.getTaskStatus(taskId);
2362
+ if (TERMINAL_STATUSES.has(status.status)) {
2363
+ console.log(
2364
+ `[Flow] Task ${taskId} already terminal \u2014 status=${status.status}`
2365
+ );
2366
+ return status;
2367
+ }
2368
+ if (realtime) {
2369
+ await this._waitViaSocketIO(taskId, realtime, timeoutMs);
2370
+ } else {
2371
+ console.log(
2372
+ `[Flow] No RealtimeClient available \u2014 polling task ${taskId} with backoff (timeout=${timeoutMs}ms)`
2373
+ );
2374
+ await this._waitViaPolling(client, taskId, timeoutMs);
2375
+ }
2376
+ return client.getTaskStatus(taskId);
2377
+ }
2378
+ async _waitViaSocketIO(taskId, realtime, timeoutMs) {
2379
+ realtime.subscribeTask(taskId);
2380
+ return new Promise((resolve, reject) => {
2381
+ const timer = setTimeout(() => {
2382
+ cleanup();
2383
+ reject(
2384
+ new FlowTimeoutError(
2385
+ `Task ${taskId} did not reach a terminal state within ${timeoutMs}ms`
2386
+ )
2387
+ );
2388
+ }, timeoutMs);
2389
+ const handler = (...args) => {
2390
+ const event = args[0] ?? {};
2391
+ if (event.task_id !== taskId) return;
2392
+ const newStatus = event.new_status ?? "";
2393
+ if (TERMINAL_STATUSES.has(newStatus)) {
2394
+ cleanup();
2395
+ resolve();
2396
+ }
2397
+ };
2398
+ const cleanup = () => {
2399
+ clearTimeout(timer);
2400
+ realtime.off("task_status_changed", handler);
2401
+ realtime.unsubscribeTask(taskId);
2402
+ };
2403
+ realtime.on("task_status_changed", handler);
2404
+ });
2405
+ }
2406
+ async _waitViaPolling(client, taskId, timeoutMs) {
2407
+ const deadline = Date.now() + timeoutMs;
2408
+ let delay = 1e3;
2409
+ while (Date.now() < deadline) {
2410
+ const status = await client.getTaskStatus(taskId);
2411
+ if (TERMINAL_STATUSES.has(status.status)) return;
2412
+ await this._sleep(delay);
2413
+ delay = Math.min(Math.round(delay * 1.5), 1e4);
2414
+ }
2415
+ throw new FlowTimeoutError(
2416
+ `Task ${taskId} did not reach a terminal state within ${timeoutMs}ms (polling fallback)`
2417
+ );
2418
+ }
2419
+ // -- Conversation helpers ---------------------------------------------
2420
+ async _getFinalMessage(client, taskId) {
2421
+ const conversation = await client.getTaskConversation(taskId, {
2422
+ includeSummaries: false,
2423
+ excludeArchived: false
2424
+ });
2425
+ const messages = conversation.conversation;
2426
+ for (let i = messages.length - 1; i >= 0; i--) {
2427
+ const msg = messages[i];
2428
+ if (msg.role === "assistant" && msg.content?.trim()) {
2429
+ return msg.content;
2430
+ }
2431
+ }
2432
+ for (let i = messages.length - 1; i >= 0; i--) {
2433
+ const msg = messages[i];
2434
+ if (msg.role === "assistant" && msg.archived && msg.id != null) {
2435
+ const archived = await client.getArchivedMessageContent(taskId, msg.id);
2436
+ if (archived.content?.trim()) {
2437
+ return archived.content;
2438
+ }
2439
+ }
2440
+ }
2441
+ const status = await client.getTaskStatus(taskId);
2442
+ throw new FlowError(
2443
+ `No assistant message with content found in task ${taskId}. Status: ${status.status}. Result: ${(status.result ?? "").slice(0, 200) || "empty"}. Messages in conversation: ${messages.length}.`
2444
+ );
2445
+ }
2446
+ // -- Helper -----------------------------------------------------------
2447
+ _sleep(ms) {
2448
+ return new Promise((resolve) => setTimeout(resolve, ms));
2449
+ }
2450
+ };
2451
+
2124
2452
  // src/index.ts
2125
2453
  var VERSION = "5.6.0";
2126
2454
  // Annotate the CommonJS export names for ESM import in node:
2127
2455
  0 && (module.exports = {
2456
+ DEFAULT_FLOW_TIMEOUT_MS,
2128
2457
  EVENT_ERROR_EVENT_RECORDED,
2129
2458
  EVENT_MESSAGE_ADDED,
2130
2459
  EVENT_MESSAGE_STREAMING,
@@ -2136,6 +2465,10 @@ var VERSION = "5.6.0";
2136
2465
  EVENT_TASK_ITERATION_CHANGED,
2137
2466
  EVENT_TASK_RESULT_UPDATED,
2138
2467
  EVENT_TASK_STATUS_CHANGED,
2468
+ Flow,
2469
+ FlowCancelledError,
2470
+ FlowError,
2471
+ FlowTimeoutError,
2139
2472
  Orchestrator,
2140
2473
  OrchestratorAPIError,
2141
2474
  OrchestratorAsync,
@@ -2149,7 +2482,9 @@ var VERSION = "5.6.0";
2149
2482
  camelToSnake,
2150
2483
  createInsecureFetch,
2151
2484
  deepCamelCase,
2485
+ extractJsonFromMessage,
2152
2486
  loadConfig,
2487
+ setupDefaultClient,
2153
2488
  snakeToCamel
2154
2489
  });
2155
2490
  //# sourceMappingURL=index.cjs.map