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.js CHANGED
@@ -171,6 +171,11 @@ var OrchestratorAsync = class {
171
171
  _makeUrl(path) {
172
172
  return `${this._baseUrl}${path}`;
173
173
  }
174
+ _makeAbsoluteUrl(path) {
175
+ const raw = this._makeUrl(path);
176
+ const base = typeof globalThis.location !== "undefined" ? globalThis.location.href : void 0;
177
+ return new URL(raw, base);
178
+ }
174
179
  async _resolveHeaders() {
175
180
  const headers = {};
176
181
  if (this._apiKey) {
@@ -187,7 +192,7 @@ var OrchestratorAsync = class {
187
192
  return headers;
188
193
  }
189
194
  async _request(method, path, opts) {
190
- const url = new URL(this._makeUrl(path));
195
+ const url = this._makeAbsoluteUrl(path);
191
196
  if (opts?.params) {
192
197
  for (const [key, value] of Object.entries(opts.params)) {
193
198
  if (value !== void 0) {
@@ -981,7 +986,7 @@ var OrchestratorAsync = class {
981
986
  // ------------------------------------------------------------------
982
987
  async listErrors(params) {
983
988
  const authHeaders = await this._resolveHeaders();
984
- const url = new URL(this._makeUrl("/errors"));
989
+ const url = this._makeAbsoluteUrl("/errors");
985
990
  if (params?.page !== void 0)
986
991
  url.searchParams.set("page", String(params.page));
987
992
  if (params?.limit !== void 0)
@@ -1775,6 +1780,11 @@ var RealtimeClient = class {
1775
1780
  this._socket = null;
1776
1781
  this._handlers = /* @__PURE__ */ new Map();
1777
1782
  this._connected = false;
1783
+ // Every room joined through any API (subscribeTask, joinRooms, subscribe(),
1784
+ // subscribeEvents, etc.) is tracked here so it can be replayed on reconnect.
1785
+ // Server-side room membership is tied to the socket connection and is lost
1786
+ // on disconnect — the full set must be re-emitted on every reconnect.
1787
+ this._rooms = /* @__PURE__ */ new Set();
1778
1788
  // Multi-subscriber room-diffing state (mirrors WebSocketProvider)
1779
1789
  this._subscriptions = /* @__PURE__ */ new Map();
1780
1790
  this._currentRooms = /* @__PURE__ */ new Set();
@@ -1811,6 +1821,10 @@ var RealtimeClient = class {
1811
1821
  this._socket.on("connect", () => {
1812
1822
  this._connected = true;
1813
1823
  this._currentRooms = /* @__PURE__ */ new Set();
1824
+ if (this._rooms.size > 0) {
1825
+ this._socket?.emit("join", { rooms: [...this._rooms] });
1826
+ this._currentRooms = new Set(this._rooms);
1827
+ }
1814
1828
  this._syncRooms();
1815
1829
  });
1816
1830
  this._socket.on("disconnect", () => {
@@ -1868,7 +1882,9 @@ var RealtimeClient = class {
1868
1882
  */
1869
1883
  subscribeTask(taskId) {
1870
1884
  if (!this._socket) throw new Error("RealtimeClient not connected");
1871
- this._socket.emit("join", { rooms: [`task:${taskId}`] });
1885
+ const room = `task:${taskId}`;
1886
+ this._socket.emit("join", { rooms: [room] });
1887
+ this._rooms.add(room);
1872
1888
  }
1873
1889
  /**
1874
1890
  * Unsubscribe from realtime events for a specific task.
@@ -1876,7 +1892,9 @@ var RealtimeClient = class {
1876
1892
  */
1877
1893
  unsubscribeTask(taskId) {
1878
1894
  if (!this._socket) throw new Error("RealtimeClient not connected");
1879
- this._socket.emit("leave", { rooms: [`task:${taskId}`] });
1895
+ const room = `task:${taskId}`;
1896
+ this._socket.emit("leave", { rooms: [room] });
1897
+ this._rooms.delete(room);
1880
1898
  }
1881
1899
  /**
1882
1900
  * Subscribe to event-type-scoped rooms.
@@ -1886,6 +1904,7 @@ var RealtimeClient = class {
1886
1904
  if (!this._socket) throw new Error("RealtimeClient not connected");
1887
1905
  const rooms = eventTypes.map((t) => `event:${t}`);
1888
1906
  this._socket.emit("join", { rooms });
1907
+ for (const r of rooms) this._rooms.add(r);
1889
1908
  }
1890
1909
  /**
1891
1910
  * Unsubscribe from event-type-scoped rooms.
@@ -1894,6 +1913,7 @@ var RealtimeClient = class {
1894
1913
  if (!this._socket) throw new Error("RealtimeClient not connected");
1895
1914
  const rooms = eventTypes.map((t) => `event:${t}`);
1896
1915
  this._socket.emit("leave", { rooms });
1916
+ for (const r of rooms) this._rooms.delete(r);
1897
1917
  }
1898
1918
  /**
1899
1919
  * Subscribe to the `all` broadcast room (receives all events).
@@ -1901,20 +1921,25 @@ var RealtimeClient = class {
1901
1921
  subscribeAll() {
1902
1922
  if (!this._socket) throw new Error("RealtimeClient not connected");
1903
1923
  this._socket.emit("join", { rooms: ["all"] });
1924
+ this._rooms.add("all");
1904
1925
  }
1905
1926
  /**
1906
1927
  * Subscribe to a locale-specific room.
1907
1928
  */
1908
1929
  subscribeLocale(locale) {
1909
1930
  if (!this._socket) throw new Error("RealtimeClient not connected");
1910
- this._socket.emit("join", { rooms: [`locale:${locale}`] });
1931
+ const room = `locale:${locale}`;
1932
+ this._socket.emit("join", { rooms: [room] });
1933
+ this._rooms.add(room);
1911
1934
  }
1912
1935
  /**
1913
1936
  * Unsubscribe from a locale-specific room.
1914
1937
  */
1915
1938
  unsubscribeLocale(locale) {
1916
1939
  if (!this._socket) throw new Error("RealtimeClient not connected");
1917
- this._socket.emit("leave", { rooms: [`locale:${locale}`] });
1940
+ const room = `locale:${locale}`;
1941
+ this._socket.emit("leave", { rooms: [room] });
1942
+ this._rooms.delete(room);
1918
1943
  }
1919
1944
  /**
1920
1945
  * Join arbitrary rooms by name.
@@ -1922,6 +1947,7 @@ var RealtimeClient = class {
1922
1947
  joinRooms(rooms) {
1923
1948
  if (!this._socket) throw new Error("RealtimeClient not connected");
1924
1949
  this._socket.emit("join", { rooms });
1950
+ for (const r of rooms) this._rooms.add(r);
1925
1951
  }
1926
1952
  /**
1927
1953
  * Leave arbitrary rooms by name.
@@ -1929,6 +1955,7 @@ var RealtimeClient = class {
1929
1955
  leaveRooms(rooms) {
1930
1956
  if (!this._socket) throw new Error("RealtimeClient not connected");
1931
1957
  this._socket.emit("leave", { rooms });
1958
+ for (const r of rooms) this._rooms.delete(r);
1932
1959
  }
1933
1960
  // ------------------------------------------------------------------
1934
1961
  // Multi-subscriber API (mirrors WebSocketProvider room-diffing)
@@ -1986,6 +2013,8 @@ var RealtimeClient = class {
1986
2013
  const toLeave = [...this._currentRooms].filter((r) => !needed.has(r));
1987
2014
  if (toJoin.length) socket.emit("join", { rooms: toJoin });
1988
2015
  if (toLeave.length) socket.emit("leave", { rooms: toLeave });
2016
+ for (const r of toJoin) this._rooms.add(r);
2017
+ for (const r of toLeave) this._rooms.delete(r);
1989
2018
  this._currentRooms = needed;
1990
2019
  }
1991
2020
  /**
@@ -2060,9 +2089,302 @@ var RealtimeClient = class {
2060
2089
  }
2061
2090
  };
2062
2091
 
2092
+ // src/flow.ts
2093
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
2094
+ "completed",
2095
+ "failed",
2096
+ "cancelled",
2097
+ "translation"
2098
+ ]);
2099
+ var DEFAULT_FLOW_TIMEOUT_MS = 6e5;
2100
+ var _defaultClient;
2101
+ var _defaultRealtime;
2102
+ function setupDefaultClient(client, realtime) {
2103
+ _defaultClient = client;
2104
+ _defaultRealtime = realtime;
2105
+ }
2106
+ function getDefaultClient() {
2107
+ if (!_defaultClient) {
2108
+ throw new Error(
2109
+ "No default OrchestratorAsync set. Call setupDefaultClient() during application startup, or pass client to flow.run()."
2110
+ );
2111
+ }
2112
+ return _defaultClient;
2113
+ }
2114
+ function getDefaultRealtime() {
2115
+ return _defaultRealtime;
2116
+ }
2117
+ var FlowError = class extends Error {
2118
+ constructor(message) {
2119
+ super(message);
2120
+ this.name = "FlowError";
2121
+ }
2122
+ };
2123
+ var FlowTimeoutError = class extends FlowError {
2124
+ constructor(message) {
2125
+ super(message);
2126
+ this.name = "FlowTimeoutError";
2127
+ }
2128
+ };
2129
+ var FlowCancelledError = class extends FlowError {
2130
+ constructor(taskId) {
2131
+ super(`Task ${taskId} was cancelled`);
2132
+ this.name = "FlowCancelledError";
2133
+ this.taskId = taskId;
2134
+ }
2135
+ };
2136
+ function extractJsonFromMessage(content) {
2137
+ const cleaned = content.replace(/^\ufeff|\u200b|\u200c|\u200d/, "").trim();
2138
+ if (cleaned.startsWith("{")) {
2139
+ try {
2140
+ return JSON.parse(cleaned);
2141
+ } catch {
2142
+ }
2143
+ }
2144
+ const start = content.indexOf("{");
2145
+ const end = content.lastIndexOf("}");
2146
+ if (start !== -1 && end !== -1 && end > start) {
2147
+ try {
2148
+ return JSON.parse(content.slice(start, end + 1));
2149
+ } catch {
2150
+ }
2151
+ }
2152
+ const jsonFenceMatch = content.match(/```(?:json)\s*\n([\s\S]*?)\n```/i);
2153
+ if (jsonFenceMatch) {
2154
+ try {
2155
+ return JSON.parse(jsonFenceMatch[1].trim());
2156
+ } catch {
2157
+ }
2158
+ }
2159
+ const anyFenceMatch = content.match(/```(?:[\w]*)\s*\n([\s\S]*?)\n```/);
2160
+ if (anyFenceMatch) {
2161
+ try {
2162
+ return JSON.parse(anyFenceMatch[1].trim());
2163
+ } catch {
2164
+ }
2165
+ }
2166
+ const bareBraceMatch = content.match(/\{[\s\S]*\}/);
2167
+ if (bareBraceMatch) {
2168
+ try {
2169
+ return JSON.parse(bareBraceMatch[0]);
2170
+ } catch {
2171
+ }
2172
+ }
2173
+ throw new FlowError(
2174
+ "Could not extract a valid JSON object from the agent's final message. The agent did not format its answer as required."
2175
+ );
2176
+ }
2177
+ var Flow = class {
2178
+ constructor() {
2179
+ // -- Workflow-level defaults (override in subclass) --------------------
2180
+ /** Orchestrator workflow type — `"proactive"` by default. */
2181
+ this.workflowId = "proactive";
2182
+ /** Maximum agent turns before the orchestrator forces a failure. */
2183
+ this.maxIterations = 100;
2184
+ /** LLM reasoning budget: `"low"`, `"medium"`, or `"high"`. */
2185
+ this.reasoningEffort = "medium";
2186
+ /**
2187
+ * Maximum milliseconds to wait for the orchestrator task to reach a
2188
+ * terminal state. When exceeded, {@link FlowTimeoutError} is raised.
2189
+ */
2190
+ this.flowTimeoutMs = DEFAULT_FLOW_TIMEOUT_MS;
2191
+ }
2192
+ /**
2193
+ * Optional system-prompt override.
2194
+ * When set, this replaces the orchestrator's default system prompt.
2195
+ */
2196
+ get systemPrompt() {
2197
+ return void 0;
2198
+ }
2199
+ /**
2200
+ * Optional developer-prompt override appended after system prompt.
2201
+ */
2202
+ get developerPrompt() {
2203
+ return void 0;
2204
+ }
2205
+ /**
2206
+ * Restrict which MCP / built-in tools the agent may use.
2207
+ * `undefined` means *all* tools are available. An empty array means
2208
+ * *no* tools — text-only reasoning.
2209
+ */
2210
+ get availableTools() {
2211
+ return void 0;
2212
+ }
2213
+ /**
2214
+ * Per-task feature toggles sent in the creation request.
2215
+ * By default summaries and translation are disabled since flow
2216
+ * output is typically machine-consumed, not human-read.
2217
+ * Override in subclasses that produce human-facing content.
2218
+ */
2219
+ get taskOptions() {
2220
+ return { disableSummaries: true, disableTranslation: true };
2221
+ }
2222
+ /**
2223
+ * Override the agent model for this flow.
2224
+ */
2225
+ get agentModelId() {
2226
+ return void 0;
2227
+ }
2228
+ /**
2229
+ * Override the orchestrator (validation) model for this flow.
2230
+ */
2231
+ get orchestratorModelId() {
2232
+ return void 0;
2233
+ }
2234
+ /**
2235
+ * Cancel the underlying orchestrator task, if one is running.
2236
+ *
2237
+ * Calling `cancel()` after `run()` has returned is a no-op.
2238
+ * The `run()` promise will reject with {@link FlowCancelledError}
2239
+ * after the orchestrator task transitions to `"cancelled"`.
2240
+ */
2241
+ async cancel(client) {
2242
+ if (!this._lastTaskId) return;
2243
+ const resolvedClient = client ?? getDefaultClient();
2244
+ await resolvedClient.cancelTask(this._lastTaskId);
2245
+ }
2246
+ // -- Lifecycle ---------------------------------------------------------
2247
+ /**
2248
+ * Execute the flow end-to-end.
2249
+ *
2250
+ * @returns The parsed result.
2251
+ * @throws {FlowError} If the task fails, times out, or cannot be parsed.
2252
+ */
2253
+ async run(params) {
2254
+ const client = params?.client ?? getDefaultClient();
2255
+ const realtime = params?.realtime ?? getDefaultRealtime();
2256
+ const timeoutMs = params?.timeoutMs ?? this.flowTimeoutMs;
2257
+ console.log(
2258
+ `[Flow] Starting ${this.constructor.name} \u2014 workflow=${this.workflowId} goal=${this.goalPrompt.slice(0, 80)}`
2259
+ );
2260
+ const response = await client.createTask({
2261
+ workflowId: this.workflowId,
2262
+ goalPrompt: this.goalPrompt,
2263
+ systemPrompt: this.systemPrompt,
2264
+ developerPrompt: this.developerPrompt,
2265
+ availableTools: this.availableTools,
2266
+ options: this.taskOptions,
2267
+ maxIterations: this.maxIterations,
2268
+ reasoningEffort: this.reasoningEffort,
2269
+ agentModelId: this.agentModelId,
2270
+ orchestratorModelId: this.orchestratorModelId
2271
+ });
2272
+ this._lastTaskId = response.taskId;
2273
+ const taskId = response.taskId;
2274
+ console.log(
2275
+ `[Flow] Task created \u2014 taskId=${taskId} status=${response.status}`
2276
+ );
2277
+ const status = await this._waitForTerminal(client, taskId, {
2278
+ realtime,
2279
+ timeoutMs
2280
+ });
2281
+ if (status.status === "cancelled") {
2282
+ throw new FlowCancelledError(taskId);
2283
+ }
2284
+ const finalMessage = await this._getFinalMessage(client, taskId);
2285
+ console.log(
2286
+ `[Flow] ${this.constructor.name} completed \u2014 taskId=${taskId} messageLen=${finalMessage.length}`
2287
+ );
2288
+ return this.parseResult(finalMessage);
2289
+ }
2290
+ // -- Completion waiting -----------------------------------------------
2291
+ async _waitForTerminal(client, taskId, opts) {
2292
+ const { realtime, timeoutMs } = opts;
2293
+ const status = await client.getTaskStatus(taskId);
2294
+ if (TERMINAL_STATUSES.has(status.status)) {
2295
+ console.log(
2296
+ `[Flow] Task ${taskId} already terminal \u2014 status=${status.status}`
2297
+ );
2298
+ return status;
2299
+ }
2300
+ if (realtime) {
2301
+ await this._waitViaSocketIO(taskId, realtime, timeoutMs);
2302
+ } else {
2303
+ console.log(
2304
+ `[Flow] No RealtimeClient available \u2014 polling task ${taskId} with backoff (timeout=${timeoutMs}ms)`
2305
+ );
2306
+ await this._waitViaPolling(client, taskId, timeoutMs);
2307
+ }
2308
+ return client.getTaskStatus(taskId);
2309
+ }
2310
+ async _waitViaSocketIO(taskId, realtime, timeoutMs) {
2311
+ realtime.subscribeTask(taskId);
2312
+ return new Promise((resolve, reject) => {
2313
+ const timer = setTimeout(() => {
2314
+ cleanup();
2315
+ reject(
2316
+ new FlowTimeoutError(
2317
+ `Task ${taskId} did not reach a terminal state within ${timeoutMs}ms`
2318
+ )
2319
+ );
2320
+ }, timeoutMs);
2321
+ const handler = (...args) => {
2322
+ const event = args[0] ?? {};
2323
+ if (event.task_id !== taskId) return;
2324
+ const newStatus = event.new_status ?? "";
2325
+ if (TERMINAL_STATUSES.has(newStatus)) {
2326
+ cleanup();
2327
+ resolve();
2328
+ }
2329
+ };
2330
+ const cleanup = () => {
2331
+ clearTimeout(timer);
2332
+ realtime.off("task_status_changed", handler);
2333
+ realtime.unsubscribeTask(taskId);
2334
+ };
2335
+ realtime.on("task_status_changed", handler);
2336
+ });
2337
+ }
2338
+ async _waitViaPolling(client, taskId, timeoutMs) {
2339
+ const deadline = Date.now() + timeoutMs;
2340
+ let delay = 1e3;
2341
+ while (Date.now() < deadline) {
2342
+ const status = await client.getTaskStatus(taskId);
2343
+ if (TERMINAL_STATUSES.has(status.status)) return;
2344
+ await this._sleep(delay);
2345
+ delay = Math.min(Math.round(delay * 1.5), 1e4);
2346
+ }
2347
+ throw new FlowTimeoutError(
2348
+ `Task ${taskId} did not reach a terminal state within ${timeoutMs}ms (polling fallback)`
2349
+ );
2350
+ }
2351
+ // -- Conversation helpers ---------------------------------------------
2352
+ async _getFinalMessage(client, taskId) {
2353
+ const conversation = await client.getTaskConversation(taskId, {
2354
+ includeSummaries: false,
2355
+ excludeArchived: false
2356
+ });
2357
+ const messages = conversation.conversation;
2358
+ for (let i = messages.length - 1; i >= 0; i--) {
2359
+ const msg = messages[i];
2360
+ if (msg.role === "assistant" && msg.content?.trim()) {
2361
+ return msg.content;
2362
+ }
2363
+ }
2364
+ for (let i = messages.length - 1; i >= 0; i--) {
2365
+ const msg = messages[i];
2366
+ if (msg.role === "assistant" && msg.archived && msg.id != null) {
2367
+ const archived = await client.getArchivedMessageContent(taskId, msg.id);
2368
+ if (archived.content?.trim()) {
2369
+ return archived.content;
2370
+ }
2371
+ }
2372
+ }
2373
+ const status = await client.getTaskStatus(taskId);
2374
+ throw new FlowError(
2375
+ `No assistant message with content found in task ${taskId}. Status: ${status.status}. Result: ${(status.result ?? "").slice(0, 200) || "empty"}. Messages in conversation: ${messages.length}.`
2376
+ );
2377
+ }
2378
+ // -- Helper -----------------------------------------------------------
2379
+ _sleep(ms) {
2380
+ return new Promise((resolve) => setTimeout(resolve, ms));
2381
+ }
2382
+ };
2383
+
2063
2384
  // src/index.ts
2064
2385
  var VERSION = "5.6.0";
2065
2386
  export {
2387
+ DEFAULT_FLOW_TIMEOUT_MS,
2066
2388
  EVENT_ERROR_EVENT_RECORDED,
2067
2389
  EVENT_MESSAGE_ADDED,
2068
2390
  EVENT_MESSAGE_STREAMING,
@@ -2074,6 +2396,10 @@ export {
2074
2396
  EVENT_TASK_ITERATION_CHANGED,
2075
2397
  EVENT_TASK_RESULT_UPDATED,
2076
2398
  EVENT_TASK_STATUS_CHANGED,
2399
+ Flow,
2400
+ FlowCancelledError,
2401
+ FlowError,
2402
+ FlowTimeoutError,
2077
2403
  Orchestrator,
2078
2404
  OrchestratorAPIError,
2079
2405
  OrchestratorAsync,
@@ -2087,7 +2413,9 @@ export {
2087
2413
  camelToSnake,
2088
2414
  createInsecureFetch,
2089
2415
  deepCamelCase,
2416
+ extractJsonFromMessage,
2090
2417
  loadConfig,
2418
+ setupDefaultClient,
2091
2419
  snakeToCamel
2092
2420
  };
2093
2421
  //# sourceMappingURL=index.js.map