@agent-os-sdk/client 0.9.1 → 0.9.3
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 +26 -102
- package/dist/client/AgentOsClient.d.ts +3 -3
- package/dist/client/AgentOsClient.d.ts.map +1 -1
- package/dist/client/AgentOsClient.js +4 -4
- package/dist/generated/openapi.d.ts +951 -519
- package/dist/generated/openapi.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/modules/agents.d.ts +5 -2
- package/dist/modules/agents.d.ts.map +1 -1
- package/dist/modules/agents.js +4 -1
- package/dist/modules/chatwoot.d.ts +59 -0
- package/dist/modules/chatwoot.d.ts.map +1 -0
- package/dist/modules/chatwoot.js +200 -0
- package/dist/modules/credentials.d.ts +1 -1
- package/dist/modules/credentials.d.ts.map +1 -1
- package/dist/modules/credentials.js +5 -2
- package/dist/modules/runs.d.ts +40 -63
- package/dist/modules/runs.d.ts.map +1 -1
- package/dist/modules/runs.js +86 -78
- package/dist/modules/triggers.d.ts +21 -0
- package/dist/modules/triggers.d.ts.map +1 -1
- package/dist/sse/client.d.ts +2 -2
- package/dist/sse/client.js +2 -2
- package/package.json +2 -2
- package/src/client/AgentOsClient.ts +4 -4
- package/src/generated/openapi.ts +951 -519
- package/src/generated/swagger.json +1295 -474
- package/src/index.ts +1 -0
- package/src/modules/agents.ts +9 -3
- package/src/modules/chatwoot.ts +242 -0
- package/src/modules/credentials.ts +5 -2
- package/src/modules/runs.ts +120 -107
- package/src/modules/triggers.ts +21 -0
- package/src/sse/client.ts +2 -2
- package/dist/modules/mcp.d.ts +0 -39
- package/dist/modules/mcp.d.ts.map +0 -1
- package/dist/modules/mcp.js +0 -38
- package/src/modules/mcp.ts +0 -59
package/src/index.ts
CHANGED
|
@@ -96,6 +96,7 @@ export type { SSEEvent, SSEOptions } from "./sse/client.js";
|
|
|
96
96
|
|
|
97
97
|
export * from "./modules/agents.js";
|
|
98
98
|
export * from "./modules/auth.js";
|
|
99
|
+
export * from "./modules/chatwoot.js";
|
|
99
100
|
export * from "./modules/credentials.js";
|
|
100
101
|
export * from "./modules/me.js";
|
|
101
102
|
export * from "./modules/members.js";
|
package/src/modules/agents.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* - create*, update*, delete* for mutations
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
10
|
+
import type { PaginatedResponse, PaginationParams } from "../client/helpers.js";
|
|
11
|
+
import type { APIResponse, components, RawClient } from "../client/raw.js";
|
|
12
12
|
|
|
13
13
|
// Type aliases for this module
|
|
14
14
|
|
|
@@ -31,6 +31,7 @@ export interface Agent {
|
|
|
31
31
|
last_run_id?: string | null;
|
|
32
32
|
last_run_status?: string | null;
|
|
33
33
|
last_run_at?: string | null;
|
|
34
|
+
metadata?: any | null;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export interface AgentBundle extends AgentBundleSchema { }
|
|
@@ -106,6 +107,7 @@ export class AgentsModule {
|
|
|
106
107
|
*/
|
|
107
108
|
async create(body: {
|
|
108
109
|
name: string;
|
|
110
|
+
metadata?: any | null;
|
|
109
111
|
/** Idempotency key for safe retries. When set, duplicate requests with the same key return 409 Conflict. */
|
|
110
112
|
idempotency_key?: string;
|
|
111
113
|
}): Promise<APIResponse<Agent>> {
|
|
@@ -115,7 +117,10 @@ export class AgentsModule {
|
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
return this.client.POST<Agent>("/v1/api/agents", {
|
|
118
|
-
body: {
|
|
120
|
+
body: {
|
|
121
|
+
name: body.name,
|
|
122
|
+
metadata: body.metadata
|
|
123
|
+
},
|
|
119
124
|
headers,
|
|
120
125
|
});
|
|
121
126
|
}
|
|
@@ -127,6 +132,7 @@ export class AgentsModule {
|
|
|
127
132
|
async update(agentId: string, body: {
|
|
128
133
|
name?: string;
|
|
129
134
|
live_bundle_id?: string;
|
|
135
|
+
metadata?: any | null;
|
|
130
136
|
}): Promise<APIResponse<Agent>> {
|
|
131
137
|
return this.client.PATCH<Agent>("/v1/api/agents/{id}", {
|
|
132
138
|
params: { path: { id: agentId } },
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chatwoot Module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { APIResponse, RawClient } from "../client/raw.js";
|
|
6
|
+
|
|
7
|
+
export interface ChatwootInboxUrlResponse {
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ChatwootConfig {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
accountId: string;
|
|
14
|
+
apiAccessToken: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ChatwootModule {
|
|
18
|
+
constructor(
|
|
19
|
+
private client: RawClient,
|
|
20
|
+
private headers: () => Record<string, string>
|
|
21
|
+
) { }
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Internal helper to resolve Chatwoot configuration from AgentOS credentials
|
|
25
|
+
*/
|
|
26
|
+
private async _getChatwootConfig(credentialId: string): Promise<{ data: ChatwootConfig | undefined; error: any; response?: Response }> {
|
|
27
|
+
const { data: credential, error } = await this.client.GET<any>("/v1/api/credentials/{id}", {
|
|
28
|
+
params: {
|
|
29
|
+
path: { id: credentialId },
|
|
30
|
+
query: { includeValues: true }
|
|
31
|
+
},
|
|
32
|
+
headers: this.headers(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (error || !credential) {
|
|
36
|
+
return { error: error || { message: "Credential not found", code: "CREDENTIAL_NOT_FOUND" }, data: undefined };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Backend now returns 'values' when includeValues=true
|
|
40
|
+
const values = credential.values || credential.Values || {};
|
|
41
|
+
const publicConfig = credential.publicConfig || credential.PublicConfig || {};
|
|
42
|
+
const data = credential.data || {};
|
|
43
|
+
|
|
44
|
+
const url = publicConfig.url || data.url || values.url || publicConfig.endpoint || data.endpoint || values.endpoint || values.base_url;
|
|
45
|
+
let accountId = publicConfig.account_id || data.account_id || values.account_id || publicConfig.accountId || data.accountId || values.accountId;
|
|
46
|
+
const apiAccessToken = values.api_access_token || data.api_access_token || values.api_key || data.api_key || values.apiKey || data.apiKey || values.api_token;
|
|
47
|
+
|
|
48
|
+
if (!url || !apiAccessToken) {
|
|
49
|
+
return { error: { message: "Invalid Chatwoot credential: missing URL/Endpoint or API Access Token/Key", code: "INVALID_CREDENTIAL" }, data: undefined };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const baseUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
|
53
|
+
|
|
54
|
+
// Auto-discover accountId if missing
|
|
55
|
+
if (!accountId) {
|
|
56
|
+
try {
|
|
57
|
+
const profileRes = await fetch(`${baseUrl}/api/v1/profile`, {
|
|
58
|
+
headers: { "api_access_token": apiAccessToken }
|
|
59
|
+
});
|
|
60
|
+
if (profileRes.ok) {
|
|
61
|
+
const profile = await profileRes.json();
|
|
62
|
+
// Use the first available account
|
|
63
|
+
if (profile.accounts && profile.accounts.length > 0) {
|
|
64
|
+
accountId = profile.accounts[0].id;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.warn("Failed to auto-discover Chatwoot account ID:", err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!accountId) {
|
|
73
|
+
return { error: { message: "Invalid Chatwoot credential: missing Account ID and auto-discovery failed.", code: "ACCOUNT_ID_MISSING" }, data: undefined };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
data: {
|
|
78
|
+
baseUrl,
|
|
79
|
+
accountId: String(accountId),
|
|
80
|
+
apiAccessToken
|
|
81
|
+
},
|
|
82
|
+
error: null
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Direct call to Chatwoot API
|
|
88
|
+
*/
|
|
89
|
+
private async _chatwootRequest(config: ChatwootConfig, method: string, path: string, body?: any): Promise<APIResponse<any>> {
|
|
90
|
+
const url = `${config.baseUrl}${path}`;
|
|
91
|
+
const headers = {
|
|
92
|
+
"api_access_token": config.apiAccessToken,
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(url, {
|
|
98
|
+
method,
|
|
99
|
+
headers,
|
|
100
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
let data = null;
|
|
104
|
+
const contentType = response.headers.get("content-type");
|
|
105
|
+
if (contentType && contentType.includes("application/json")) {
|
|
106
|
+
try {
|
|
107
|
+
const text = await response.text();
|
|
108
|
+
if (text) data = JSON.parse(text);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// Ignore JSON parse errors for empty bodies
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
return { error: data || { message: `Chatwoot API error: ${response.statusText}`, code: "API_ERROR" }, data: undefined, response };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { data, error: undefined, response };
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return { error: { message: (err as Error).message, code: "UNKNOWN_ERROR" }, data: undefined, response: new Response() };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the inbox URL for a specific credential.
|
|
126
|
+
* Use this to open the Chatwoot inbox directly.
|
|
127
|
+
*/
|
|
128
|
+
async getInboxUrl(credentialId: string): Promise<APIResponse<ChatwootInboxUrlResponse>> {
|
|
129
|
+
const { data: config, error } = await this._getChatwootConfig(credentialId);
|
|
130
|
+
|
|
131
|
+
if (error || !config) {
|
|
132
|
+
return { error: error || { message: "Config not found", code: "CONFIG_MISSING" }, data: undefined, response: new Response() };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const inboxUrl = `${config.baseUrl}/app/accounts/${config.accountId}/inbox`;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
data: { url: inboxUrl },
|
|
139
|
+
error: undefined,
|
|
140
|
+
response: new Response(),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* List all inboxes
|
|
146
|
+
*/
|
|
147
|
+
async listInboxes(credentialId: string): Promise<APIResponse<any[]>> {
|
|
148
|
+
const { data: config, error } = await this._getChatwootConfig(credentialId);
|
|
149
|
+
if (error || !config) return { error: error || { message: "Config not found", code: "CONFIG_MISSING" }, data: undefined, response: new Response() };
|
|
150
|
+
|
|
151
|
+
const res = await this._chatwootRequest(config, "GET", `/api/v1/accounts/${config.accountId}/inboxes`);
|
|
152
|
+
|
|
153
|
+
// Chatwoot API returns { payload: [...] }
|
|
154
|
+
if (res.data && Array.isArray(res.data.payload)) {
|
|
155
|
+
return { data: res.data.payload, error: undefined, response: res.response };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return res;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a new inbox
|
|
163
|
+
*/
|
|
164
|
+
async createInbox(credentialId: string, data: any): Promise<APIResponse<any>> {
|
|
165
|
+
const { data: config, error } = await this._getChatwootConfig(credentialId);
|
|
166
|
+
if (error || !config) return { error: error || { message: "Config not found", code: "CONFIG_MISSING" }, data: undefined, response: new Response() };
|
|
167
|
+
|
|
168
|
+
return this._chatwootRequest(config, "POST", `/api/v1/accounts/${config.accountId}/inboxes`, data);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get a specific inbox
|
|
173
|
+
*/
|
|
174
|
+
async getInbox(credentialId: string, inboxId: string | number): Promise<APIResponse<any>> {
|
|
175
|
+
const { data: config, error } = await this._getChatwootConfig(credentialId);
|
|
176
|
+
if (error || !config) return { error: error || { message: "Config not found", code: "CONFIG_MISSING" }, data: undefined, response: new Response() };
|
|
177
|
+
|
|
178
|
+
return this._chatwootRequest(config, "GET", `/api/v1/accounts/${config.accountId}/inboxes/${inboxId}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Update an inbox
|
|
183
|
+
*/
|
|
184
|
+
async updateInbox(credentialId: string, inboxId: string | number, data: any): Promise<APIResponse<any>> {
|
|
185
|
+
const { data: config, error } = await this._getChatwootConfig(credentialId);
|
|
186
|
+
if (error || !config) return { error: error || { message: "Config not found", code: "CONFIG_MISSING" }, data: undefined, response: new Response() };
|
|
187
|
+
|
|
188
|
+
return this._chatwootRequest(config, "PATCH", `/api/v1/accounts/${config.accountId}/inboxes/${inboxId}`, data);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Delete an inbox
|
|
193
|
+
*/
|
|
194
|
+
async deleteInbox(credentialId: string, inboxId: string | number): Promise<APIResponse<any>> {
|
|
195
|
+
const { data: config, error } = await this._getChatwootConfig(credentialId);
|
|
196
|
+
if (error || !config) return { error: error || { message: "Config not found", code: "CONFIG_MISSING" }, data: undefined, response: new Response() };
|
|
197
|
+
|
|
198
|
+
return this._chatwootRequest(config, "DELETE", `/api/v1/accounts/${config.accountId}/inboxes/${inboxId}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get inbox metrics
|
|
203
|
+
*/
|
|
204
|
+
async getInboxMetrics(credentialId: string, inboxId: string | number): Promise<APIResponse<any>> {
|
|
205
|
+
const { data: config, error } = await this._getChatwootConfig(credentialId);
|
|
206
|
+
if (error || !config) return { error: error || { message: "Config not found", code: "CONFIG_MISSING" }, data: undefined, response: new Response() };
|
|
207
|
+
|
|
208
|
+
return this._chatwootRequest(config, "GET", `/api/v1/accounts/${config.accountId}/inboxes/${inboxId}/metrics`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get the inbox URL for a specific agent.
|
|
213
|
+
*/
|
|
214
|
+
async getAgentInboxUrl(agentId: string): Promise<APIResponse<ChatwootInboxUrlResponse>> {
|
|
215
|
+
const { data: triggers, error: triggerError } = await this.client.GET<any>("/v1/api/triggers", {
|
|
216
|
+
params: { query: { agent_id: agentId } },
|
|
217
|
+
headers: this.headers(),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (triggerError || !triggers) {
|
|
221
|
+
return { error: triggerError || { message: "Failed to fetch triggers", code: "TRIGGER_FETCH_FAILED" }, data: undefined, response: new Response() };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const chatwootTrigger = triggers.items?.find((t: any) =>
|
|
225
|
+
t.type === "chatwoot" ||
|
|
226
|
+
(t.type === "evolution_whatsapp" && t.config?.chat_platform === "chatwoot") ||
|
|
227
|
+
(t.config?.credential_id && (t.type === "chatwoot" || t.type.includes("whatsapp")))
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (!chatwootTrigger) {
|
|
231
|
+
return { error: { message: "No compatible trigger found for this agent", code: "NO_TRIGGER" }, data: undefined, response: new Response() };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const credentialId = chatwootTrigger.config?.credential_id;
|
|
235
|
+
|
|
236
|
+
if (!credentialId) {
|
|
237
|
+
return { error: { message: "Trigger configuration missing credential_id", code: "MISSING_CREDENTIAL_ID" }, data: undefined, response: new Response() };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return this.getInboxUrl(credentialId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -68,9 +68,12 @@ export class CredentialsModule {
|
|
|
68
68
|
/**
|
|
69
69
|
* Get a credential by ID.
|
|
70
70
|
*/
|
|
71
|
-
async get(credentialId: string): Promise<APIResponse<Credential>> {
|
|
71
|
+
async get(credentialId: string, includeValues: boolean = false): Promise<APIResponse<Credential>> {
|
|
72
72
|
return this.client.GET<Credential>("/v1/api/credentials/{id}", {
|
|
73
|
-
params: {
|
|
73
|
+
params: {
|
|
74
|
+
path: { id: credentialId },
|
|
75
|
+
query: { includeValues }
|
|
76
|
+
},
|
|
74
77
|
headers: this.headers(),
|
|
75
78
|
});
|
|
76
79
|
}
|
package/src/modules/runs.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { RawClient, APIResponse, components } from "../client/raw.js";
|
|
11
11
|
import type { PaginationParams, PaginatedResponse } from "../client/helpers.js";
|
|
12
|
-
import { parseSSE, type SSEEvent
|
|
12
|
+
import { parseSSE, type SSEEvent } from "../sse/client.js";
|
|
13
13
|
|
|
14
14
|
// Type aliases from OpenAPI
|
|
15
15
|
type WaitRunResponse = components["schemas"]["WaitRunResponse"];
|
|
@@ -29,6 +29,9 @@ export interface Run {
|
|
|
29
29
|
input?: unknown;
|
|
30
30
|
output?: unknown;
|
|
31
31
|
error?: unknown;
|
|
32
|
+
current_attempt_id?: string;
|
|
33
|
+
current_attempt_no?: number;
|
|
34
|
+
latest_seq?: number;
|
|
32
35
|
created_at: string;
|
|
33
36
|
started_at?: string;
|
|
34
37
|
completed_at?: string;
|
|
@@ -54,15 +57,6 @@ export type RunStatus =
|
|
|
54
57
|
| "waiting_for_human"
|
|
55
58
|
| "resumed";
|
|
56
59
|
|
|
57
|
-
export interface RunEvent {
|
|
58
|
-
id: string;
|
|
59
|
-
run_id: string;
|
|
60
|
-
event_type: string;
|
|
61
|
-
node?: string;
|
|
62
|
-
data?: unknown;
|
|
63
|
-
timestamp: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
60
|
export interface CreateRunResponse {
|
|
67
61
|
run_id: string;
|
|
68
62
|
status: string;
|
|
@@ -70,13 +64,12 @@ export interface CreateRunResponse {
|
|
|
70
64
|
}
|
|
71
65
|
|
|
72
66
|
export type RunListResponse = PaginatedResponse<Run>;
|
|
73
|
-
export type RunEventsResponse = PaginatedResponse<RunEvent>;
|
|
74
67
|
|
|
75
68
|
/** Wave 2.3: Seq-based polling response for execution events */
|
|
76
69
|
export interface RunEventsPollResponse {
|
|
77
70
|
events: RunEventDto[];
|
|
78
71
|
latest_seq: number;
|
|
79
|
-
next_after_seq: number;
|
|
72
|
+
next_after_seq: number | null;
|
|
80
73
|
has_more: boolean;
|
|
81
74
|
}
|
|
82
75
|
|
|
@@ -321,20 +314,28 @@ export class RunsModule {
|
|
|
321
314
|
// ======================== Events ========================
|
|
322
315
|
|
|
323
316
|
/**
|
|
324
|
-
* Get run events
|
|
325
|
-
* @example
|
|
326
|
-
* ```ts
|
|
327
|
-
* const { data } = await client.runs.getEvents("run-uuid");
|
|
328
|
-
* ```
|
|
317
|
+
* Get canonical run events from SSOT ledger (polling by seq).
|
|
329
318
|
*/
|
|
330
|
-
async getEvents(runId: string, params
|
|
331
|
-
|
|
332
|
-
|
|
319
|
+
async getEvents(runId: string, params: {
|
|
320
|
+
attemptId: string;
|
|
321
|
+
afterSeq?: number;
|
|
322
|
+
limit?: number;
|
|
323
|
+
}): Promise<APIResponse<RunEventsPollResponse>> {
|
|
324
|
+
return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events", {
|
|
325
|
+
params: {
|
|
326
|
+
path: { runId },
|
|
327
|
+
query: {
|
|
328
|
+
attemptId: params.attemptId,
|
|
329
|
+
afterSeq: params.afterSeq ?? 0,
|
|
330
|
+
limit: params.limit ?? 100,
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
333
|
});
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
/** Alias: runs.events() -> runs.getEvents() */
|
|
337
|
-
events = (runId: string, params?:
|
|
337
|
+
events = (runId: string, params: { attemptId: string; afterSeq?: number; limit?: number }) =>
|
|
338
|
+
this.getEvents(runId, params);
|
|
338
339
|
|
|
339
340
|
/**
|
|
340
341
|
* Wave 2.3: Seq-based event polling for execution trace.
|
|
@@ -347,8 +348,9 @@ export class RunsModule {
|
|
|
347
348
|
* @example
|
|
348
349
|
* ```ts
|
|
349
350
|
* let afterSeq = 0;
|
|
351
|
+
* const attemptId = "attempt-uuid";
|
|
350
352
|
* while (run.status === 'running') {
|
|
351
|
-
* const { data } = await client.runs.pollEvents(runId, { afterSeq });
|
|
353
|
+
* const { data } = await client.runs.pollEvents(runId, { attemptId, afterSeq });
|
|
352
354
|
* for (const event of data.events) {
|
|
353
355
|
* console.log(event.type, event.payload);
|
|
354
356
|
* }
|
|
@@ -357,16 +359,18 @@ export class RunsModule {
|
|
|
357
359
|
* }
|
|
358
360
|
* ```
|
|
359
361
|
*/
|
|
360
|
-
async pollEvents(runId: string, params
|
|
362
|
+
async pollEvents(runId: string, params: {
|
|
363
|
+
attemptId: string;
|
|
361
364
|
afterSeq?: number;
|
|
362
365
|
limit?: number;
|
|
363
366
|
}): Promise<APIResponse<RunEventsPollResponse>> {
|
|
364
|
-
return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events
|
|
367
|
+
return this.client.GET<RunEventsPollResponse>("/v1/api/runs/{runId}/events", {
|
|
365
368
|
params: {
|
|
366
369
|
path: { runId },
|
|
367
370
|
query: {
|
|
368
|
-
|
|
369
|
-
|
|
371
|
+
attemptId: params.attemptId,
|
|
372
|
+
afterSeq: params.afterSeq ?? 0,
|
|
373
|
+
limit: params.limit ?? 100
|
|
370
374
|
}
|
|
371
375
|
},
|
|
372
376
|
});
|
|
@@ -386,79 +390,19 @@ export class RunsModule {
|
|
|
386
390
|
/** Alias: runs.checkpoints() -> runs.getCheckpoints() */
|
|
387
391
|
checkpoints = (runId: string) => this.getCheckpoints(runId);
|
|
388
392
|
|
|
389
|
-
// ========================
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Stream run events via SSE.
|
|
393
|
-
* @example
|
|
394
|
-
* ```ts
|
|
395
|
-
* for await (const event of client.runs.stream("run-uuid")) {
|
|
396
|
-
* console.log(event.data);
|
|
397
|
-
* }
|
|
398
|
-
* ```
|
|
399
|
-
*/
|
|
400
|
-
async *stream(runId: string, options?: SSEOptions): AsyncGenerator<SSEEvent<RunStreamEvent>> {
|
|
401
|
-
const response = await this.client.streamGet("/v1/api/runs/{runId}/stream", {
|
|
402
|
-
params: { path: { runId } },
|
|
403
|
-
headers: options?.headers,
|
|
404
|
-
});
|
|
405
|
-
yield* parseSSE<RunStreamEvent>(response, { onOpen: options?.onOpen });
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Create run and stream output.
|
|
410
|
-
* @example
|
|
411
|
-
* ```ts
|
|
412
|
-
* for await (const event of client.runs.createAndStream({
|
|
413
|
-
* agent_id: "...",
|
|
414
|
-
* input: { message: "Hello" }
|
|
415
|
-
* })) {
|
|
416
|
-
* console.log(event);
|
|
417
|
-
* }
|
|
418
|
-
* ```
|
|
419
|
-
*/
|
|
420
|
-
async *createAndStream(
|
|
421
|
-
body: {
|
|
422
|
-
agent_id: string;
|
|
423
|
-
thread?: { thread_id?: string } | { new_thread: true };
|
|
424
|
-
input?: unknown;
|
|
425
|
-
},
|
|
426
|
-
options?: SSEOptions
|
|
427
|
-
): AsyncGenerator<SSEEvent<RunStreamEvent>> {
|
|
428
|
-
const { data, error } = await this.create(body);
|
|
429
|
-
if (error || !data) {
|
|
430
|
-
throw new Error(`Failed to create run: ${JSON.stringify(error)}`);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const runId = data.run_id;
|
|
434
|
-
yield* this.stream(runId, options);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Join an existing run's stream (resume watching).
|
|
439
|
-
*/
|
|
440
|
-
async *join(runId: string, options?: SSEOptions): AsyncGenerator<SSEEvent<RunStreamEvent>> {
|
|
441
|
-
const response = await this.client.streamGet("/v1/api/runs/{runId}/join", {
|
|
442
|
-
params: { path: { runId } },
|
|
443
|
-
headers: options?.headers,
|
|
444
|
-
});
|
|
445
|
-
yield* parseSSE<RunStreamEvent>(response, { onOpen: options?.onOpen });
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// ======================== FOLLOW (Enterprise SSE) ========================
|
|
393
|
+
// ======================== FOLLOW (SSOT SSE) ========================
|
|
449
394
|
|
|
450
395
|
/**
|
|
451
|
-
* Follow a run's event stream with automatic reconnection
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* -
|
|
455
|
-
* -
|
|
456
|
-
* -
|
|
457
|
-
* - Terminates cleanly on 'close' event
|
|
396
|
+
* Follow a run attempt's canonical event stream with automatic reconnection.
|
|
397
|
+
*
|
|
398
|
+
* SSOT contract:
|
|
399
|
+
* - Stream is keyed by (run_id, attempt_id, seq)
|
|
400
|
+
* - Resume is driven by `afterSeq` (monotonic)
|
|
401
|
+
* - No implicit attempt inference
|
|
458
402
|
*
|
|
459
403
|
* @example
|
|
460
404
|
* ```ts
|
|
461
|
-
* for await (const event of client.runs.follow(runId)) {
|
|
405
|
+
* for await (const event of client.runs.follow(runId, attemptId)) {
|
|
462
406
|
* if (event.type === "run_event") {
|
|
463
407
|
* console.log(event.payload);
|
|
464
408
|
* } else if (event.type === "close") {
|
|
@@ -467,7 +411,11 @@ export class RunsModule {
|
|
|
467
411
|
* }
|
|
468
412
|
* ```
|
|
469
413
|
*/
|
|
470
|
-
async *follow(runId: string, options?: FollowOptions): AsyncGenerator<FollowEvent, void, unknown> {
|
|
414
|
+
async *follow(runId: string, attemptId: string, options?: FollowOptions): AsyncGenerator<FollowEvent, void, unknown> {
|
|
415
|
+
if (!attemptId) {
|
|
416
|
+
throw new Error("attemptId is required for run event streaming");
|
|
417
|
+
}
|
|
418
|
+
|
|
471
419
|
const signal = options?.signal;
|
|
472
420
|
const maxReconnects = options?.maxReconnects ?? 10;
|
|
473
421
|
const baseDelayMs = options?.baseDelayMs ?? 1000;
|
|
@@ -490,10 +438,13 @@ export class RunsModule {
|
|
|
490
438
|
headers["Last-Event-ID"] = String(nextSeq - 1);
|
|
491
439
|
}
|
|
492
440
|
|
|
493
|
-
const response = await this.client.streamGet("/v1/api/runs/{runId}/stream", {
|
|
441
|
+
const response = await this.client.streamGet("/v1/api/runs/{runId}/events/stream", {
|
|
494
442
|
params: {
|
|
495
443
|
path: { runId },
|
|
496
|
-
query:
|
|
444
|
+
query: {
|
|
445
|
+
attemptId,
|
|
446
|
+
afterSeq: nextSeq > 0 ? nextSeq : 0,
|
|
447
|
+
},
|
|
497
448
|
},
|
|
498
449
|
headers,
|
|
499
450
|
});
|
|
@@ -533,13 +484,6 @@ export class RunsModule {
|
|
|
533
484
|
nextSeq = event.seq + 1;
|
|
534
485
|
}
|
|
535
486
|
|
|
536
|
-
// Check for terminal events
|
|
537
|
-
if (event.type === "close") {
|
|
538
|
-
receivedTerminal = true;
|
|
539
|
-
yield event;
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
487
|
if (event.type === "error") {
|
|
544
488
|
// Error event from server - yield but continue (might reconnect)
|
|
545
489
|
yield event;
|
|
@@ -548,6 +492,17 @@ export class RunsModule {
|
|
|
548
492
|
}
|
|
549
493
|
|
|
550
494
|
yield event;
|
|
495
|
+
|
|
496
|
+
if (event.type === "run_event" && isTerminalRunEvent(event)) {
|
|
497
|
+
receivedTerminal = true;
|
|
498
|
+
yield {
|
|
499
|
+
type: "close",
|
|
500
|
+
seq: event.seq,
|
|
501
|
+
timestamp: event.timestamp,
|
|
502
|
+
payload: { terminal: true },
|
|
503
|
+
};
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
551
506
|
}
|
|
552
507
|
|
|
553
508
|
// Stream ended without close event - this is unexpected EOF
|
|
@@ -582,24 +537,52 @@ export class RunsModule {
|
|
|
582
537
|
}
|
|
583
538
|
}
|
|
584
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Create run and follow canonical SSOT stream for the current attempt.
|
|
542
|
+
*/
|
|
543
|
+
async *createAndStream(
|
|
544
|
+
body: {
|
|
545
|
+
agent_id: string;
|
|
546
|
+
thread?: { thread_id?: string } | { new_thread: true };
|
|
547
|
+
input?: unknown;
|
|
548
|
+
/** Idempotency key for safe retries. */
|
|
549
|
+
idempotency_key?: string;
|
|
550
|
+
},
|
|
551
|
+
options?: FollowOptions
|
|
552
|
+
): AsyncGenerator<FollowEvent, void, unknown> {
|
|
553
|
+
const { data, error } = await this.create(body);
|
|
554
|
+
if (error || !data) {
|
|
555
|
+
throw new Error(`Failed to create run: ${JSON.stringify(error)}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const runId = data.run_id;
|
|
559
|
+
const runResponse = await this.get(runId);
|
|
560
|
+
if (runResponse.error || !runResponse.data?.current_attempt_id) {
|
|
561
|
+
throw new Error("Run created without current_attempt_id; cannot stream SSOT events");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
yield* this.follow(runId, runResponse.data.current_attempt_id, options);
|
|
565
|
+
}
|
|
566
|
+
|
|
585
567
|
/**
|
|
586
568
|
* Wait for a specific event that matches a predicate.
|
|
587
569
|
*
|
|
588
570
|
* @example
|
|
589
571
|
* ```ts
|
|
590
572
|
* // Wait for run completion
|
|
591
|
-
* const event = await client.runs.waitFor(runId, (e) => e.type === "close", {
|
|
573
|
+
* const event = await client.runs.waitFor(runId, attemptId, (e) => e.type === "close", {
|
|
592
574
|
* timeoutMs: 60000
|
|
593
575
|
* });
|
|
594
576
|
*
|
|
595
577
|
* // Wait for specific node execution
|
|
596
|
-
* const nodeEvent = await client.runs.waitFor(runId, (e) =>
|
|
578
|
+
* const nodeEvent = await client.runs.waitFor(runId, attemptId, (e) =>
|
|
597
579
|
* e.type === "run_event" && e.payload?.node === "my_node"
|
|
598
580
|
* );
|
|
599
581
|
* ```
|
|
600
582
|
*/
|
|
601
583
|
async waitFor(
|
|
602
584
|
runId: string,
|
|
585
|
+
attemptId: string,
|
|
603
586
|
predicate: (event: FollowEvent) => boolean,
|
|
604
587
|
options?: { timeoutMs?: number; signal?: AbortSignal; startSeq?: number }
|
|
605
588
|
): Promise<FollowEvent> {
|
|
@@ -613,7 +596,7 @@ export class RunsModule {
|
|
|
613
596
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
614
597
|
|
|
615
598
|
try {
|
|
616
|
-
for await (const event of this.follow(runId, {
|
|
599
|
+
for await (const event of this.follow(runId, attemptId, {
|
|
617
600
|
signal: controller.signal,
|
|
618
601
|
startSeq: options?.startSeq,
|
|
619
602
|
})) {
|
|
@@ -689,6 +672,28 @@ function narrowFollowEvent(raw: SSEEvent<unknown>): FollowEvent | null {
|
|
|
689
672
|
|
|
690
673
|
const data = raw.data as Record<string, unknown> | null;
|
|
691
674
|
|
|
675
|
+
// Canonical payload from backend SSE is RunEventDto:
|
|
676
|
+
// { id, seq, type, timestamp, attempt_id, payload }
|
|
677
|
+
if (eventType === "run_event" && data) {
|
|
678
|
+
const seq = typeof data.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1);
|
|
679
|
+
const timestamp = typeof data.timestamp === "string" ? data.timestamp : new Date().toISOString();
|
|
680
|
+
const innerPayload = (typeof data.payload === "object" && data.payload !== null)
|
|
681
|
+
? (data.payload as Record<string, unknown>)
|
|
682
|
+
: null;
|
|
683
|
+
|
|
684
|
+
return {
|
|
685
|
+
type: "run_event",
|
|
686
|
+
seq,
|
|
687
|
+
timestamp,
|
|
688
|
+
payload: {
|
|
689
|
+
...(innerPayload ?? {}),
|
|
690
|
+
type: typeof data.type === "string" ? data.type : "UNKNOWN",
|
|
691
|
+
attempt_id: typeof data.attempt_id === "string" ? data.attempt_id : undefined,
|
|
692
|
+
},
|
|
693
|
+
node: typeof innerPayload?.node === "string" ? innerPayload.node : undefined,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
692
697
|
return {
|
|
693
698
|
type: eventType as FollowEvent["type"],
|
|
694
699
|
seq: typeof data?.seq === "number" ? data.seq : (raw.id ? parseInt(raw.id, 10) : -1),
|
|
@@ -698,6 +703,14 @@ function narrowFollowEvent(raw: SSEEvent<unknown>): FollowEvent | null {
|
|
|
698
703
|
};
|
|
699
704
|
}
|
|
700
705
|
|
|
706
|
+
function isTerminalRunEvent(event: FollowEvent): boolean {
|
|
707
|
+
const type = typeof event.payload?.type === "string"
|
|
708
|
+
? event.payload.type.toUpperCase()
|
|
709
|
+
: "";
|
|
710
|
+
|
|
711
|
+
return type === "RUN_FINISHED" || type === "RUN_FAILED" || type === "RUN_CANCELLED";
|
|
712
|
+
}
|
|
713
|
+
|
|
701
714
|
/** Calculate exponential backoff with jitter */
|
|
702
715
|
function calculateBackoff(attempt: number, baseMs: number, maxMs: number): number {
|
|
703
716
|
const exponential = baseMs * Math.pow(2, attempt - 1);
|