orchestrator-client 5.7.1 → 5.7.2

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.d.cts CHANGED
@@ -511,6 +511,15 @@ interface OrchestratorClientOptions {
511
511
  timeoutMs?: number;
512
512
  /** Max retry attempts on transient failures (default: 3). */
513
513
  maxRetries?: number;
514
+ /**
515
+ * Locale tag sent as `X-Locale` on every request (e.g. `"hu-hu"`, `"en-us"`).
516
+ *
517
+ * When set the API returns translated content where available.
518
+ * When not set the API returns its default (English) content.
519
+ * Per-call locale overrides (on `listTasks`, `getTaskStatus`, etc.)
520
+ * take precedence over this global setting.
521
+ */
522
+ locale?: string;
514
523
  /**
515
524
  * Custom fetch implementation override.
516
525
  *
@@ -538,6 +547,7 @@ declare class OrchestratorAsync {
538
547
  protected _getToken: (() => string | Promise<string>) | undefined;
539
548
  protected _timeoutMs: number;
540
549
  protected _maxRetries: number;
550
+ protected _locale: string | undefined;
541
551
  protected _fetch: typeof globalThis.fetch;
542
552
  protected _abortController: AbortController | null;
543
553
  constructor(opts?: OrchestratorClientOptions);
@@ -687,7 +697,7 @@ declare class OrchestratorAsync {
687
697
  orderDirection?: string;
688
698
  }): Promise<ErrorEventListResult>;
689
699
  getErrorDetail(errorId: string): Promise<ErrorEventDetail>;
690
- getErrorStats(since?: string): Promise<ErrorStatsResult>;
700
+ getErrorStats(since?: string, topN?: number): Promise<ErrorStatsResult>;
691
701
  countErrors(since?: string): Promise<ErrorCountResult>;
692
702
  purgeErrors(): Promise<ErrorPurgeResult>;
693
703
  health(): Promise<HealthStatus>;
@@ -926,6 +936,19 @@ interface RealtimeClientOptions {
926
936
  getToken?: (() => string | Promise<string>) | string;
927
937
  /** Whether to auto-connect on construction (default: false). */
928
938
  autoConnect?: boolean;
939
+ /**
940
+ * Client identifier sent as the `client_id` query parameter at connection
941
+ * time. The server uses this for logging/diagnostics.
942
+ * Defaults to `"orchestrator-client"`.
943
+ */
944
+ clientId?: string;
945
+ /**
946
+ * Locale tag sent as the `locale` query parameter at connection time
947
+ * (e.g. `"hu-hu"`, `"en-us"`). When set the server automatically joins
948
+ * the client into the matching `locale:<tag>` room so translation-ready
949
+ * events are delivered without a separate `subscribeLocale()` call.
950
+ */
951
+ locale?: string;
929
952
  }
930
953
  /**
931
954
  * Socket.IO-based realtime client for receiving orchestrator events.
@@ -933,20 +956,40 @@ interface RealtimeClientOptions {
933
956
  * The server wraps all domain events in a `message` Socket.IO event with an
934
957
  * inner `event_type` field. This client unwraps the envelope and dispatches
935
958
  * to registered handlers by event_type.
959
+ *
960
+ * ## Two subscription APIs — choose one or mix them:
961
+ *
962
+ * ### 1. Event-type handlers via `on(event, handler)`
963
+ * Registers a callback for a specific event type string (e.g.
964
+ * `"task_status_changed"`). You still need to join the relevant rooms
965
+ * manually via `subscribeTask()`, `subscribeEvents()`, etc.
966
+ *
967
+ * ### 2. Room-scoped subscribers via `subscribe(handler, rooms)`
968
+ * Mirrors the WebSocketProvider pattern used in the webui. Each call
969
+ * returns an opaque subscription id. Calling `unsubscribe(id)` removes it.
970
+ * Room membership is kept in sync automatically: the client joins the union
971
+ * of all subscribers' rooms and leaves rooms that are no longer needed.
972
+ * The handler receives the unwrapped event dict for every event from those
973
+ * rooms, regardless of type.
936
974
  */
937
975
  declare class RealtimeClient {
938
976
  private _baseUrl;
939
977
  private _socketOptions;
940
978
  private _getToken?;
979
+ private _clientId;
980
+ private _locale?;
941
981
  private _socket;
942
982
  private _handlers;
943
983
  private _connected;
984
+ private _subscriptions;
985
+ private _currentRooms;
986
+ private _subIdCounter;
944
987
  constructor(baseUrl: string, opts: RealtimeClientOptions);
945
988
  get connected(): boolean;
946
989
  connect(): Promise<void>;
947
990
  disconnect(): Promise<void>;
948
991
  /**
949
- * Dispatch a message envelope to registered handlers.
992
+ * Dispatch a message envelope to registered handlers and subscribers.
950
993
  * The server sends: socket.emit("message", {type: "message", event: {..., event_type: "...", ...}})
951
994
  */
952
995
  private _dispatch;
@@ -989,6 +1032,41 @@ declare class RealtimeClient {
989
1032
  * Leave arbitrary rooms by name.
990
1033
  */
991
1034
  leaveRooms(rooms: string[]): void;
1035
+ /**
1036
+ * Register a subscriber that receives all events from the given rooms.
1037
+ *
1038
+ * Room membership is managed automatically: the client joins the union
1039
+ * of all active subscribers' rooms and leaves rooms that are no longer
1040
+ * needed when the last subscriber referencing them is removed.
1041
+ *
1042
+ * The `handler` receives the unwrapped event dict (same object that
1043
+ * `on()` handlers receive).
1044
+ *
1045
+ * Returns an opaque subscription id that must be passed to
1046
+ * `unsubscribe()` to remove the subscription.
1047
+ *
1048
+ * Example — mirror the webui's per-component subscription pattern:
1049
+ *
1050
+ * const id = rt.subscribe((event) => {
1051
+ * if (event.event_type === "task_status_changed") { ... }
1052
+ * }, [`task:${taskId}`]);
1053
+ *
1054
+ * // later, on cleanup:
1055
+ * rt.unsubscribe(id);
1056
+ */
1057
+ subscribe(handler: EventHandler, rooms: string[]): string;
1058
+ /**
1059
+ * Remove a subscription registered via `subscribe()`.
1060
+ *
1061
+ * Rooms that are no longer referenced by any remaining subscriber are
1062
+ * left automatically.
1063
+ */
1064
+ unsubscribe(id: string): void;
1065
+ /**
1066
+ * Diff the union of all subscribers' rooms against the currently joined
1067
+ * rooms and emit `join`/`leave` for the delta. No-ops when not connected.
1068
+ */
1069
+ private _syncRooms;
992
1070
  /**
993
1071
  * Send a ping to the server.
994
1072
  */
package/dist/index.d.ts CHANGED
@@ -511,6 +511,15 @@ interface OrchestratorClientOptions {
511
511
  timeoutMs?: number;
512
512
  /** Max retry attempts on transient failures (default: 3). */
513
513
  maxRetries?: number;
514
+ /**
515
+ * Locale tag sent as `X-Locale` on every request (e.g. `"hu-hu"`, `"en-us"`).
516
+ *
517
+ * When set the API returns translated content where available.
518
+ * When not set the API returns its default (English) content.
519
+ * Per-call locale overrides (on `listTasks`, `getTaskStatus`, etc.)
520
+ * take precedence over this global setting.
521
+ */
522
+ locale?: string;
514
523
  /**
515
524
  * Custom fetch implementation override.
516
525
  *
@@ -538,6 +547,7 @@ declare class OrchestratorAsync {
538
547
  protected _getToken: (() => string | Promise<string>) | undefined;
539
548
  protected _timeoutMs: number;
540
549
  protected _maxRetries: number;
550
+ protected _locale: string | undefined;
541
551
  protected _fetch: typeof globalThis.fetch;
542
552
  protected _abortController: AbortController | null;
543
553
  constructor(opts?: OrchestratorClientOptions);
@@ -687,7 +697,7 @@ declare class OrchestratorAsync {
687
697
  orderDirection?: string;
688
698
  }): Promise<ErrorEventListResult>;
689
699
  getErrorDetail(errorId: string): Promise<ErrorEventDetail>;
690
- getErrorStats(since?: string): Promise<ErrorStatsResult>;
700
+ getErrorStats(since?: string, topN?: number): Promise<ErrorStatsResult>;
691
701
  countErrors(since?: string): Promise<ErrorCountResult>;
692
702
  purgeErrors(): Promise<ErrorPurgeResult>;
693
703
  health(): Promise<HealthStatus>;
@@ -926,6 +936,19 @@ interface RealtimeClientOptions {
926
936
  getToken?: (() => string | Promise<string>) | string;
927
937
  /** Whether to auto-connect on construction (default: false). */
928
938
  autoConnect?: boolean;
939
+ /**
940
+ * Client identifier sent as the `client_id` query parameter at connection
941
+ * time. The server uses this for logging/diagnostics.
942
+ * Defaults to `"orchestrator-client"`.
943
+ */
944
+ clientId?: string;
945
+ /**
946
+ * Locale tag sent as the `locale` query parameter at connection time
947
+ * (e.g. `"hu-hu"`, `"en-us"`). When set the server automatically joins
948
+ * the client into the matching `locale:<tag>` room so translation-ready
949
+ * events are delivered without a separate `subscribeLocale()` call.
950
+ */
951
+ locale?: string;
929
952
  }
930
953
  /**
931
954
  * Socket.IO-based realtime client for receiving orchestrator events.
@@ -933,20 +956,40 @@ interface RealtimeClientOptions {
933
956
  * The server wraps all domain events in a `message` Socket.IO event with an
934
957
  * inner `event_type` field. This client unwraps the envelope and dispatches
935
958
  * to registered handlers by event_type.
959
+ *
960
+ * ## Two subscription APIs — choose one or mix them:
961
+ *
962
+ * ### 1. Event-type handlers via `on(event, handler)`
963
+ * Registers a callback for a specific event type string (e.g.
964
+ * `"task_status_changed"`). You still need to join the relevant rooms
965
+ * manually via `subscribeTask()`, `subscribeEvents()`, etc.
966
+ *
967
+ * ### 2. Room-scoped subscribers via `subscribe(handler, rooms)`
968
+ * Mirrors the WebSocketProvider pattern used in the webui. Each call
969
+ * returns an opaque subscription id. Calling `unsubscribe(id)` removes it.
970
+ * Room membership is kept in sync automatically: the client joins the union
971
+ * of all subscribers' rooms and leaves rooms that are no longer needed.
972
+ * The handler receives the unwrapped event dict for every event from those
973
+ * rooms, regardless of type.
936
974
  */
937
975
  declare class RealtimeClient {
938
976
  private _baseUrl;
939
977
  private _socketOptions;
940
978
  private _getToken?;
979
+ private _clientId;
980
+ private _locale?;
941
981
  private _socket;
942
982
  private _handlers;
943
983
  private _connected;
984
+ private _subscriptions;
985
+ private _currentRooms;
986
+ private _subIdCounter;
944
987
  constructor(baseUrl: string, opts: RealtimeClientOptions);
945
988
  get connected(): boolean;
946
989
  connect(): Promise<void>;
947
990
  disconnect(): Promise<void>;
948
991
  /**
949
- * Dispatch a message envelope to registered handlers.
992
+ * Dispatch a message envelope to registered handlers and subscribers.
950
993
  * The server sends: socket.emit("message", {type: "message", event: {..., event_type: "...", ...}})
951
994
  */
952
995
  private _dispatch;
@@ -989,6 +1032,41 @@ declare class RealtimeClient {
989
1032
  * Leave arbitrary rooms by name.
990
1033
  */
991
1034
  leaveRooms(rooms: string[]): void;
1035
+ /**
1036
+ * Register a subscriber that receives all events from the given rooms.
1037
+ *
1038
+ * Room membership is managed automatically: the client joins the union
1039
+ * of all active subscribers' rooms and leaves rooms that are no longer
1040
+ * needed when the last subscriber referencing them is removed.
1041
+ *
1042
+ * The `handler` receives the unwrapped event dict (same object that
1043
+ * `on()` handlers receive).
1044
+ *
1045
+ * Returns an opaque subscription id that must be passed to
1046
+ * `unsubscribe()` to remove the subscription.
1047
+ *
1048
+ * Example — mirror the webui's per-component subscription pattern:
1049
+ *
1050
+ * const id = rt.subscribe((event) => {
1051
+ * if (event.event_type === "task_status_changed") { ... }
1052
+ * }, [`task:${taskId}`]);
1053
+ *
1054
+ * // later, on cleanup:
1055
+ * rt.unsubscribe(id);
1056
+ */
1057
+ subscribe(handler: EventHandler, rooms: string[]): string;
1058
+ /**
1059
+ * Remove a subscription registered via `subscribe()`.
1060
+ *
1061
+ * Rooms that are no longer referenced by any remaining subscriber are
1062
+ * left automatically.
1063
+ */
1064
+ unsubscribe(id: string): void;
1065
+ /**
1066
+ * Diff the union of all subscribers' rooms against the currently joined
1067
+ * rooms and emit `join`/`leave` for the delta. No-ops when not connected.
1068
+ */
1069
+ private _syncRooms;
992
1070
  /**
993
1071
  * Send a ping to the server.
994
1072
  */
package/dist/index.js CHANGED
@@ -90,6 +90,7 @@ var OrchestratorAsync = class {
90
90
  this._getToken = opts.getToken;
91
91
  this._timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
92
92
  this._maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
93
+ this._locale = opts.locale;
93
94
  this._fetch = opts.fetch ?? globalThis.fetch;
94
95
  if (opts.insecure && !opts.fetch && typeof process !== "undefined" && process.versions?.node) {
95
96
  this._insecure = true;
@@ -114,6 +115,9 @@ var OrchestratorAsync = class {
114
115
  headers.Authorization = `Bearer ${token}`;
115
116
  }
116
117
  }
118
+ if (this._locale) {
119
+ headers["X-Locale"] = this._locale;
120
+ }
117
121
  return headers;
118
122
  }
119
123
  async _request(method, path, opts) {
@@ -344,10 +348,9 @@ var OrchestratorAsync = class {
344
348
  );
345
349
  }
346
350
  async getTaskCompactions(taskId) {
347
- const data = await this._get("/task/compactions", {
351
+ return this._get("/task/compactions", {
348
352
  task_id: taskId
349
353
  });
350
- return data.compactions ?? [];
351
354
  }
352
355
  async getTaskJournal(taskId) {
353
356
  const data = await this._get("/task/journal", {
@@ -924,8 +927,10 @@ var OrchestratorAsync = class {
924
927
  async getErrorDetail(errorId) {
925
928
  return this._get(`/errors/${errorId}`);
926
929
  }
927
- async getErrorStats(since) {
928
- const params = {};
930
+ async getErrorStats(since, topN = 10) {
931
+ const params = {
932
+ top_n: topN
933
+ };
929
934
  if (since) params.since = since;
930
935
  return this._get("/errors/stats", params);
931
936
  }
@@ -1635,9 +1640,15 @@ var RealtimeClient = class {
1635
1640
  this._socket = null;
1636
1641
  this._handlers = /* @__PURE__ */ new Map();
1637
1642
  this._connected = false;
1643
+ // Multi-subscriber room-diffing state (mirrors WebSocketProvider)
1644
+ this._subscriptions = /* @__PURE__ */ new Map();
1645
+ this._currentRooms = /* @__PURE__ */ new Set();
1646
+ this._subIdCounter = 0;
1638
1647
  this._baseUrl = baseUrl.replace(/\/+$/, "");
1639
1648
  this._socketOptions = opts.socketOptions ?? {};
1640
1649
  this._getToken = opts.getToken;
1650
+ this._clientId = opts.clientId ?? "orchestrator-client";
1651
+ this._locale = opts.locale;
1641
1652
  if (opts.autoConnect) {
1642
1653
  this.connect();
1643
1654
  }
@@ -1650,18 +1661,26 @@ var RealtimeClient = class {
1650
1661
  if (this._getToken) {
1651
1662
  auth.token = typeof this._getToken === "function" ? await this._getToken() : this._getToken;
1652
1663
  }
1664
+ const query = { client_id: this._clientId };
1665
+ if (this._locale) {
1666
+ query.locale = this._locale;
1667
+ }
1653
1668
  const { io } = await import("socket.io-client");
1654
1669
  this._socket = io(this._baseUrl, {
1655
1670
  path: "/socket.io",
1656
1671
  auth,
1672
+ query,
1657
1673
  transports: ["websocket", "polling"],
1658
1674
  ...this._socketOptions
1659
1675
  });
1660
1676
  this._socket.on("connect", () => {
1661
1677
  this._connected = true;
1678
+ this._currentRooms = /* @__PURE__ */ new Set();
1679
+ this._syncRooms();
1662
1680
  });
1663
1681
  this._socket.on("disconnect", () => {
1664
1682
  this._connected = false;
1683
+ this._currentRooms = /* @__PURE__ */ new Set();
1665
1684
  });
1666
1685
  this._socket.on("message", (payload) => this._dispatch(payload));
1667
1686
  return new Promise((resolve, reject) => {
@@ -1684,7 +1703,7 @@ var RealtimeClient = class {
1684
1703
  this._connected = false;
1685
1704
  }
1686
1705
  /**
1687
- * Dispatch a message envelope to registered handlers.
1706
+ * Dispatch a message envelope to registered handlers and subscribers.
1688
1707
  * The server sends: socket.emit("message", {type: "message", event: {..., event_type: "...", ...}})
1689
1708
  */
1690
1709
  _dispatch(payload) {
@@ -1698,6 +1717,9 @@ var RealtimeClient = class {
1698
1717
  h(event);
1699
1718
  }
1700
1719
  }
1720
+ for (const sub of this._subscriptions.values()) {
1721
+ sub.handler(event);
1722
+ }
1701
1723
  }
1702
1724
  /**
1703
1725
  * Subscribe to realtime events for a specific task.
@@ -1767,6 +1789,64 @@ var RealtimeClient = class {
1767
1789
  if (!this._socket) throw new Error("RealtimeClient not connected");
1768
1790
  this._socket.emit("leave", { rooms });
1769
1791
  }
1792
+ // ------------------------------------------------------------------
1793
+ // Multi-subscriber API (mirrors WebSocketProvider room-diffing)
1794
+ // ------------------------------------------------------------------
1795
+ /**
1796
+ * Register a subscriber that receives all events from the given rooms.
1797
+ *
1798
+ * Room membership is managed automatically: the client joins the union
1799
+ * of all active subscribers' rooms and leaves rooms that are no longer
1800
+ * needed when the last subscriber referencing them is removed.
1801
+ *
1802
+ * The `handler` receives the unwrapped event dict (same object that
1803
+ * `on()` handlers receive).
1804
+ *
1805
+ * Returns an opaque subscription id that must be passed to
1806
+ * `unsubscribe()` to remove the subscription.
1807
+ *
1808
+ * Example — mirror the webui's per-component subscription pattern:
1809
+ *
1810
+ * const id = rt.subscribe((event) => {
1811
+ * if (event.event_type === "task_status_changed") { ... }
1812
+ * }, [`task:${taskId}`]);
1813
+ *
1814
+ * // later, on cleanup:
1815
+ * rt.unsubscribe(id);
1816
+ */
1817
+ subscribe(handler, rooms) {
1818
+ const id = `sub_${++this._subIdCounter}`;
1819
+ this._subscriptions.set(id, { handler, rooms });
1820
+ this._syncRooms();
1821
+ return id;
1822
+ }
1823
+ /**
1824
+ * Remove a subscription registered via `subscribe()`.
1825
+ *
1826
+ * Rooms that are no longer referenced by any remaining subscriber are
1827
+ * left automatically.
1828
+ */
1829
+ unsubscribe(id) {
1830
+ this._subscriptions.delete(id);
1831
+ this._syncRooms();
1832
+ }
1833
+ /**
1834
+ * Diff the union of all subscribers' rooms against the currently joined
1835
+ * rooms and emit `join`/`leave` for the delta. No-ops when not connected.
1836
+ */
1837
+ _syncRooms() {
1838
+ const socket = this._socket;
1839
+ if (!socket?.connected) return;
1840
+ const needed = /* @__PURE__ */ new Set();
1841
+ for (const sub of this._subscriptions.values()) {
1842
+ for (const r of sub.rooms) needed.add(r);
1843
+ }
1844
+ const toJoin = [...needed].filter((r) => !this._currentRooms.has(r));
1845
+ const toLeave = [...this._currentRooms].filter((r) => !needed.has(r));
1846
+ if (toJoin.length) socket.emit("join", { rooms: toJoin });
1847
+ if (toLeave.length) socket.emit("leave", { rooms: toLeave });
1848
+ this._currentRooms = needed;
1849
+ }
1770
1850
  /**
1771
1851
  * Send a ping to the server.
1772
1852
  */