@relevanceai/sdk 3.0.2 → 3.1.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.
@@ -4,9 +4,11 @@ import type { ToolMessage } from "./tool.js";
4
4
  import type { UserMessage } from "./user.js";
5
5
  import type { WorkforceAgentMessage } from "./workforce-agent.js";
6
6
  import type { WorkforceAgentHandoverMessage } from "./workforce-agent-handover.js";
7
- export type AnyTaskMessage = AgentMessage | AgentErrorMessage | ToolMessage | UserMessage | WorkforceAgentMessage | WorkforceAgentHandoverMessage;
7
+ import type { ThinkingMessage } from "./stream.js";
8
+ import type { TypingMessage } from "./stream.js";
9
+ export type AnyTaskMessage = AgentMessage | AgentErrorMessage | ThinkingMessage | TypingMessage | ToolMessage | UserMessage | WorkforceAgentMessage | WorkforceAgentHandoverMessage;
8
10
  export type TaskMessageType = AnyTaskMessage["type"];
9
- interface MessageContent {
11
+ export interface MessageContent {
10
12
  type: AnyTaskMessage["type"];
11
13
  }
12
14
  export interface TaskMessageData<C extends MessageContent = MessageContent> {
@@ -54,10 +56,15 @@ export declare abstract class GenericMessage<C extends MessageContent = MessageC
54
56
  */
55
57
  isUser(): this is UserMessage;
56
58
  /**
57
- * Returns if the message was an error sent by the agent.
59
+ * Returns if the message is agent thinking.
58
60
  *
59
61
  * @returns {boolean}
60
62
  */
61
- isAgentError(): this is AgentErrorMessage;
63
+ isThinking(): this is ThinkingMessage;
64
+ /**
65
+ * Returns if the message is agent typing.
66
+ *
67
+ * @returns {boolean}
68
+ */
69
+ isTyping(): this is TypingMessage;
62
70
  }
63
- export {};
@@ -55,12 +55,20 @@ class GenericMessage {
55
55
  return this.type === "user-message";
56
56
  }
57
57
  /**
58
- * Returns if the message was an error sent by the agent.
58
+ * Returns if the message is agent thinking.
59
59
  *
60
60
  * @returns {boolean}
61
61
  */
62
- isAgentError() {
63
- return this.type === "agent-error";
62
+ isThinking() {
63
+ return this.type === "agent-thinking";
64
+ }
65
+ /**
66
+ * Returns if the message is agent typing.
67
+ *
68
+ * @returns {boolean}
69
+ */
70
+ isTyping() {
71
+ return this.type === "agent-typing";
64
72
  }
65
73
  }
66
74
  exports.GenericMessage = GenericMessage;
@@ -3,3 +3,4 @@ export declare const REGION_US = "bcbe5a";
3
3
  export declare const REGION_EU = "d7b62b";
4
4
  export declare const REGION_AU = "f1db6c";
5
5
  export declare function regionBaseURL(region: Region): string;
6
+ export declare function regionStreamingURL(region: Region, token: string): string;
package/script/region.js CHANGED
@@ -2,9 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.REGION_AU = exports.REGION_EU = exports.REGION_US = void 0;
4
4
  exports.regionBaseURL = regionBaseURL;
5
+ exports.regionStreamingURL = regionStreamingURL;
5
6
  exports.REGION_US = "bcbe5a";
6
7
  exports.REGION_EU = "d7b62b";
7
8
  exports.REGION_AU = "f1db6c";
8
9
  function regionBaseURL(region) {
9
10
  return `https://api-${region}.stack.tryrelevance.com`;
10
11
  }
12
+ function regionStreamingURL(region, token) {
13
+ return `https://${region}.streaming.tryrelevance.com/v1/stream?authorization=Bearer+${token}`;
14
+ }
@@ -25,15 +25,22 @@ class AgentStrategy {
25
25
  return this.agent;
26
26
  }
27
27
  async getMetadata() {
28
- const { metadata } = await this.client.fetch(`/agents/${this.agent.id}/tasks/${this.id}/metadata`);
28
+ const url = `/agents/${this.agent.id}/tasks/${this.id}/metadata?include_streaming_token=true`;
29
+ const res = await this.client.fetch(url);
29
30
  return {
30
31
  id: this.id,
31
32
  region: this.client.region,
32
33
  project: this.client.project,
33
- name: metadata.conversation.title,
34
- status: (0, agent_js_1.stateToStatus)(metadata.conversation.state),
35
- createdAt: new Date(metadata.insert_date),
36
- updatedAt: new Date(metadata.update_date),
34
+ name: res.metadata.conversation.title,
35
+ status: (0, agent_js_1.stateToStatus)(res.metadata.conversation.state),
36
+ createdAt: new Date(res.metadata.insert_date),
37
+ updatedAt: new Date(res.metadata.update_date),
38
+ streamingToken: res.streaming_token
39
+ ? {
40
+ token: res.streaming_token.token,
41
+ expiresAt: res.streaming_token.expiry_time_ms,
42
+ }
43
+ : undefined,
37
44
  };
38
45
  }
39
46
  async getMessages({ after = new Date(0) } = {}) {
@@ -0,0 +1,24 @@
1
+ import { Emitter } from "../emitter.js";
2
+ import { type Region } from "../region.js";
3
+ import type { TaskStrategy } from "./task.js";
4
+ export type StreamingToken = {
5
+ token: string;
6
+ expiresAt: number;
7
+ };
8
+ export type StreamDetail = {
9
+ content: string;
10
+ documentId: string;
11
+ };
12
+ type TaskStreamEventMap = {
13
+ thinking: StreamDetail;
14
+ typing: StreamDetail;
15
+ };
16
+ export declare class TaskStream extends Emitter<TaskStreamEventMap> {
17
+ #private;
18
+ constructor(strategy: TaskStrategy<any>, metadata: {
19
+ region: Region;
20
+ streamingToken: StreamingToken;
21
+ });
22
+ close(): void;
23
+ }
24
+ export {};
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskStream = void 0;
4
+ const emitter_js_1 = require("../emitter.js");
5
+ const region_js_1 = require("../region.js");
6
+ class TaskStream extends emitter_js_1.Emitter {
7
+ #strategy;
8
+ #region;
9
+ #token;
10
+ #closed = false;
11
+ #refreshTimer = null;
12
+ #source = null;
13
+ #fetchController = null;
14
+ constructor(strategy, metadata) {
15
+ super();
16
+ this.#strategy = strategy;
17
+ this.#region = metadata.region;
18
+ this.#token = metadata.streamingToken;
19
+ this.#connect();
20
+ }
21
+ close() {
22
+ this.#closed = true;
23
+ this.#clearRefreshTimer();
24
+ this.#disconnect();
25
+ }
26
+ #connect() {
27
+ if (this.#closed)
28
+ return;
29
+ this.#scheduleRefresh();
30
+ if (typeof EventSource !== "undefined") {
31
+ this.#connectEventSource();
32
+ }
33
+ else {
34
+ this.#connectFetch();
35
+ }
36
+ }
37
+ #disconnect() {
38
+ this.#clearRefreshTimer();
39
+ if (this.#source) {
40
+ this.#source.close();
41
+ this.#source = null;
42
+ }
43
+ if (this.#fetchController) {
44
+ this.#fetchController.abort();
45
+ this.#fetchController = null;
46
+ }
47
+ }
48
+ #connectEventSource() {
49
+ const url = (0, region_js_1.regionStreamingURL)(this.#region, this.#token.token);
50
+ const source = new EventSource(url);
51
+ this.#source = source;
52
+ source.addEventListener("message", (event) => {
53
+ this.#handleData(event.data);
54
+ });
55
+ }
56
+ #connectFetch() {
57
+ const url = (0, region_js_1.regionStreamingURL)(this.#region, this.#token.token);
58
+ const controller = new AbortController();
59
+ this.#fetchController = controller;
60
+ void (async () => {
61
+ try {
62
+ const response = await fetch(url, {
63
+ headers: { Accept: "text/event-stream" },
64
+ signal: controller.signal,
65
+ });
66
+ if (!response.ok || !response.body)
67
+ return;
68
+ const reader = response.body.getReader();
69
+ const decoder = new TextDecoder();
70
+ let buffer = "";
71
+ while (true) {
72
+ const { done, value } = await reader.read();
73
+ if (done)
74
+ break;
75
+ buffer += decoder.decode(value, { stream: true });
76
+ const { events, remainder } = this.#parseSSE(buffer);
77
+ buffer = remainder;
78
+ for (const data of events) {
79
+ this.#handleData(data);
80
+ }
81
+ }
82
+ }
83
+ catch {
84
+ // aborted or network error — ignore if closed
85
+ }
86
+ })();
87
+ }
88
+ #parseSSE(buffer) {
89
+ const events = [];
90
+ const blocks = buffer.split("\n\n");
91
+ const remainder = blocks.pop(); // last chunk may be incomplete
92
+ for (const block of blocks) {
93
+ for (const line of block.split("\n")) {
94
+ if (line.startsWith("data:")) {
95
+ events.push(line.slice(5).trimStart());
96
+ }
97
+ }
98
+ }
99
+ return { events, remainder };
100
+ }
101
+ #handleData(raw) {
102
+ try {
103
+ const data = JSON.parse(raw);
104
+ switch (data.type) {
105
+ case "thinking":
106
+ this.dispatchEvent(new CustomEvent("thinking", {
107
+ detail: { content: data.content, documentId: data.documentId },
108
+ }));
109
+ break;
110
+ case "text":
111
+ this.dispatchEvent(new CustomEvent("typing", {
112
+ detail: { content: data.content, documentId: data.documentId },
113
+ }));
114
+ break;
115
+ }
116
+ }
117
+ catch {
118
+ // ignore malformed JSON
119
+ }
120
+ }
121
+ #scheduleRefresh() {
122
+ this.#clearRefreshTimer();
123
+ const delay = this.#token.expiresAt - Date.now() - 30_000;
124
+ if (delay <= 0)
125
+ return; // already expired or about to
126
+ this.#refreshTimer = setTimeout(() => this.#refresh(), delay);
127
+ }
128
+ #clearRefreshTimer() {
129
+ if (this.#refreshTimer !== null) {
130
+ clearTimeout(this.#refreshTimer);
131
+ this.#refreshTimer = null;
132
+ }
133
+ }
134
+ async #refresh() {
135
+ if (this.#closed)
136
+ return;
137
+ try {
138
+ const metadata = await this.#strategy.getMetadata();
139
+ if (this.#closed)
140
+ return;
141
+ if (!metadata.streamingToken) {
142
+ this.close();
143
+ return;
144
+ }
145
+ this.#region = metadata.region;
146
+ this.#token = metadata.streamingToken;
147
+ this.#disconnect();
148
+ this.#connect();
149
+ }
150
+ catch {
151
+ // refresh failed — try again on next schedule
152
+ }
153
+ }
154
+ }
155
+ exports.TaskStream = TaskStream;
@@ -4,7 +4,9 @@ import type { Region } from "../region.js";
4
4
  import type { Workforce } from "../workforce.js";
5
5
  import type { Agent } from "../agent.js";
6
6
  import type { AgentErrorMessage } from "../message/agent-error.js";
7
+ import { type StreamingToken } from "./stream.js";
7
8
  export type TaskStatus = "not-started" | "idle" | "paused" | "queued" | "running" | "action" | "completed" | "cancelled" | "error";
9
+ export type { StreamingToken };
8
10
  export interface TaskMetadata {
9
11
  id: string;
10
12
  region: Region;
@@ -13,6 +15,7 @@ export interface TaskMetadata {
13
15
  name: string;
14
16
  createdAt: Date;
15
17
  updatedAt: Date;
18
+ streamingToken?: StreamingToken;
16
19
  }
17
20
  type TaskEventMap = {
18
21
  updated: undefined;
@@ -56,4 +59,3 @@ export declare class Task<S extends Agent | Workforce, E extends TaskEventMap =
56
59
  handleEvent: (event: CustomEvent<E[K]>) => void;
57
60
  } | null, options?: boolean | AddEventListenerOptions): void;
58
61
  }
59
- export {};
@@ -4,6 +4,8 @@ exports.Task = exports.resetBackoffDuration = void 0;
4
4
  const emitter_js_1 = require("../emitter.js");
5
5
  const utils_js_1 = require("../utils.js");
6
6
  const event_js_1 = require("../event.js");
7
+ const stream_js_1 = require("../message/stream.js");
8
+ const stream_js_2 = require("./stream.js");
7
9
  exports.resetBackoffDuration = Symbol("resetBackoffDuration");
8
10
  const backoffStartingDuration = 1_000;
9
11
  const backoffMaxDuration = 60_000;
@@ -13,6 +15,7 @@ class Task extends emitter_js_1.Emitter {
13
15
  backoffDuration = 0;
14
16
  strategy;
15
17
  #metadata;
18
+ #stream = null;
16
19
  constructor(metadata, strategy) {
17
20
  super();
18
21
  this.strategy = strategy;
@@ -54,6 +57,33 @@ class Task extends emitter_js_1.Emitter {
54
57
  return false;
55
58
  }
56
59
  }
60
+ #connectStream() {
61
+ const stream = new stream_js_2.TaskStream(this.strategy, {
62
+ region: this.#metadata.region,
63
+ streamingToken: this.#metadata.streamingToken,
64
+ });
65
+ stream.addEventListener("thinking", (event) => {
66
+ this.dispatchEvent(new event_js_1.TaskMessageEvent(new stream_js_1.ThinkingMessage({
67
+ item_id: event.detail.documentId,
68
+ insert_date_: new Date().toISOString(),
69
+ content: {
70
+ type: "agent-thinking",
71
+ text: event.detail.content,
72
+ },
73
+ })));
74
+ });
75
+ stream.addEventListener("typing", (event) => {
76
+ this.dispatchEvent(new event_js_1.TaskMessageEvent(new stream_js_1.TypingMessage({
77
+ item_id: event.detail.documentId,
78
+ insert_date_: new Date().toISOString(),
79
+ content: {
80
+ type: "agent-typing",
81
+ text: event.detail.content,
82
+ },
83
+ })));
84
+ });
85
+ this.#stream = stream;
86
+ }
57
87
  #subscribe() {
58
88
  if (this.subscribed) {
59
89
  return;
@@ -62,9 +92,12 @@ class Task extends emitter_js_1.Emitter {
62
92
  this.subscribed = subscribed; // subscribed ref
63
93
  const isSubscribed = () => !subscribed.signal.aborted;
64
94
  this.backoffDuration = backoffStartingDuration;
65
- const cursor = new Date();
95
+ const cursor = new Date(this.#metadata.createdAt);
66
96
  const emitted = new Set();
67
97
  const pending = new Map();
98
+ if (this.#metadata.streamingToken) {
99
+ this.#connectStream();
100
+ }
68
101
  void (async () => {
69
102
  while (isSubscribed()) {
70
103
  try {
@@ -81,6 +114,9 @@ class Task extends emitter_js_1.Emitter {
81
114
  hasChanges = true;
82
115
  }
83
116
  this.#metadata = metadata;
117
+ if (!this.#stream && metadata.streamingToken) {
118
+ this.#connectStream();
119
+ }
84
120
  if (messages.length) {
85
121
  for (const message of messages) {
86
122
  if (emitted.has(message.id)) {
@@ -107,7 +143,17 @@ class Task extends emitter_js_1.Emitter {
107
143
  this.dispatchEvent(new event_js_1.TaskMessageEvent(message));
108
144
  break;
109
145
  }
110
- case "agent-message":
146
+ case "agent-message": {
147
+ if (message.isAgent() && message.isGenerating()) {
148
+ pending.set(message.id, message);
149
+ break;
150
+ }
151
+ emitted.add(message.id);
152
+ pending.delete(message.id);
153
+ hasChanges = true;
154
+ this.dispatchEvent(new event_js_1.TaskMessageEvent(message));
155
+ break;
156
+ }
111
157
  case "user-message":
112
158
  hasChanges = true;
113
159
  emitted.add(message.id);
@@ -143,6 +189,8 @@ class Task extends emitter_js_1.Emitter {
143
189
  })();
144
190
  }
145
191
  unsubscribe() {
192
+ this.#stream?.close();
193
+ this.#stream = null;
146
194
  this.subscribed?.abort();
147
195
  this.subscribed = null;
148
196
  this.backoff?.abort();
@@ -81,15 +81,22 @@ class WorkforceStrategy {
81
81
  });
82
82
  }
83
83
  async getMetadata() {
84
- const { metadata } = await this.client.fetch(`/workforce/tasks/${this.id}/metadata`);
84
+ const url = `/workforce/tasks/${this.id}/metadata?include_streaming_token=true`;
85
+ const res = await this.client.fetch(url);
85
86
  return {
86
87
  id: this.id,
87
88
  region: this.client.region,
88
89
  project: this.client.project,
89
- name: metadata.title,
90
- status: WorkforceStrategy.convertStatus(metadata.state),
91
- createdAt: new Date(metadata.insert_date),
92
- updatedAt: new Date(metadata.update_date),
90
+ name: res.metadata.title,
91
+ status: WorkforceStrategy.convertStatus(res.metadata.state),
92
+ createdAt: new Date(res.metadata.insert_date),
93
+ updatedAt: new Date(res.metadata.update_date),
94
+ streamingToken: res.streaming_token
95
+ ? {
96
+ token: res.streaming_token.token,
97
+ expiresAt: res.streaming_token.expiry_time_ms,
98
+ }
99
+ : undefined,
93
100
  };
94
101
  }
95
102
  }
@@ -72,6 +72,7 @@ class Workforce {
72
72
  workforce_id: this.id,
73
73
  workforce_task_id: taskId,
74
74
  trigger: {
75
+ use_streaming: true,
75
76
  message: {
76
77
  role: "user",
77
78
  content: message,