@andocorp/sdk 0.0.1 → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/agent-endpoints.d.ts +89 -0
- package/dist/agent-endpoints.d.ts.map +1 -0
- package/dist/agent-endpoints.js +229 -0
- package/dist/agent-endpoints.js.map +1 -0
- package/dist/agent-types.d.ts +81 -0
- package/dist/agent-types.d.ts.map +1 -0
- package/dist/agent-types.js +3 -0
- package/dist/agent-types.js.map +1 -0
- package/dist/client.d.ts +133 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +525 -57
- package/dist/client.js.map +1 -1
- package/dist/generated/contracts.d.ts +326 -11
- package/dist/generated/contracts.d.ts.map +1 -1
- package/dist/generated/contracts.js +21 -1
- package/dist/generated/contracts.js.map +1 -1
- package/dist/generated/public-api.d.ts +856 -0
- package/dist/generated/public-api.d.ts.map +1 -0
- package/dist/generated/public-api.js +382 -0
- package/dist/generated/public-api.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/realtime-events.d.ts +9 -4
- package/dist/realtime-events.d.ts.map +1 -1
- package/dist/realtime-events.js +113 -157
- package/dist/realtime-events.js.map +1 -1
- package/dist/realtime-runtime.d.ts +6 -17
- package/dist/realtime-runtime.d.ts.map +1 -1
- package/dist/realtime-runtime.js +45 -62
- package/dist/realtime-runtime.js.map +1 -1
- package/dist/realtime-types.d.ts +60 -77
- package/dist/realtime-types.d.ts.map +1 -1
- package/dist/realtime-types.js +2 -0
- package/dist/realtime-types.js.map +1 -1
- package/dist/realtime.d.ts +6 -5
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +282 -144
- package/dist/realtime.js.map +1 -1
- package/dist/targets.d.ts.map +1 -1
- package/dist/targets.js.map +1 -1
- package/dist/types.d.ts +61 -20
- package/dist/types.d.ts.map +1 -1
- package/package.json +24 -16
package/dist/client.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AndoClient = void 0;
|
|
3
|
+
exports.ANDO_PUBLIC_API_CLIENT_METHODS = exports.AndoClient = exports.AndoApiError = exports.DEFAULT_ANDO_REALTIME_HOST = exports.DEFAULT_ANDO_PUBLIC_API_BASE_URL = exports.DEFAULT_ANDO_BASE_URL = void 0;
|
|
4
|
+
exports.requestAndoPublicApi = requestAndoPublicApi;
|
|
5
|
+
exports.resolveAndoPublicApiBaseUrl = resolveAndoPublicApiBaseUrl;
|
|
6
|
+
const agent_endpoints_1 = require("./agent-endpoints");
|
|
7
|
+
exports.DEFAULT_ANDO_BASE_URL = "https://api.ando.so";
|
|
8
|
+
exports.DEFAULT_ANDO_PUBLIC_API_BASE_URL = "https://api.ando.so/v1";
|
|
9
|
+
exports.DEFAULT_ANDO_REALTIME_HOST = "realtime.ando.so";
|
|
4
10
|
class AndoApiError extends Error {
|
|
5
|
-
constructor(
|
|
6
|
-
|
|
11
|
+
constructor(params) {
|
|
12
|
+
const detail = params.bodyText === "" ? "" : ` ${params.bodyText}`;
|
|
13
|
+
super(`[ando-sdk] ${params.method} ${params.path} failed: ${params.status}${detail}`);
|
|
14
|
+
this.body = params.body;
|
|
15
|
+
this.bodyText = params.bodyText;
|
|
16
|
+
this.code = params.code;
|
|
17
|
+
this.method = params.method;
|
|
18
|
+
this.path = params.path;
|
|
19
|
+
this.requestId = params.requestId;
|
|
7
20
|
this.name = "AndoApiError";
|
|
8
|
-
this.status = status;
|
|
21
|
+
this.status = params.status;
|
|
9
22
|
}
|
|
10
23
|
}
|
|
24
|
+
exports.AndoApiError = AndoApiError;
|
|
11
25
|
const DEFAULT_HISTORY_LIMIT = 8;
|
|
12
26
|
const DEFAULT_THREAD_HISTORY_LIMIT = 12;
|
|
27
|
+
const TRANSIENT_API_RETRY_DELAYS_MS = [100, 250];
|
|
13
28
|
function toIsoString(value) {
|
|
14
29
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
15
30
|
}
|
|
@@ -18,33 +33,271 @@ function isNullConversationMessageError(error) {
|
|
|
18
33
|
error.message.includes("/conversation-messages/") &&
|
|
19
34
|
error.message.includes("returned null"));
|
|
20
35
|
}
|
|
36
|
+
function isOptionalContextHistoryError(error) {
|
|
37
|
+
return (error instanceof AndoApiError &&
|
|
38
|
+
(error.status === 404 ||
|
|
39
|
+
error.status === 410 ||
|
|
40
|
+
error.status === 422 ||
|
|
41
|
+
error.status >= 500));
|
|
42
|
+
}
|
|
43
|
+
function isRequestRetryable(method, headers) {
|
|
44
|
+
if (method === "GET" || method === "HEAD") {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return headers?.["Idempotency-Key"] != null;
|
|
48
|
+
}
|
|
49
|
+
function isOptimisticConcurrencyFailure(error) {
|
|
50
|
+
return (error.code === "OptimisticConcurrencyControlFailure" ||
|
|
51
|
+
error.bodyText.includes("OptimisticConcurrencyControlFailure"));
|
|
52
|
+
}
|
|
53
|
+
function isRetryableApiError(error) {
|
|
54
|
+
if (!(error instanceof AndoApiError)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (isOptimisticConcurrencyFailure(error)) {
|
|
58
|
+
return error.status === 409 || error.status >= 500;
|
|
59
|
+
}
|
|
60
|
+
return error.status === 429 || error.status >= 500;
|
|
61
|
+
}
|
|
62
|
+
function isRetryableNetworkError(error) {
|
|
63
|
+
return error instanceof TypeError && error.message === "fetch failed";
|
|
64
|
+
}
|
|
65
|
+
function isRetryableRequestError(error) {
|
|
66
|
+
return isRetryableApiError(error) || isRetryableNetworkError(error);
|
|
67
|
+
}
|
|
68
|
+
function createAbortError(signal) {
|
|
69
|
+
if (signal.reason instanceof Error) {
|
|
70
|
+
return signal.reason;
|
|
71
|
+
}
|
|
72
|
+
const error = new Error("[ando-sdk] request aborted");
|
|
73
|
+
error.name = "AbortError";
|
|
74
|
+
return error;
|
|
75
|
+
}
|
|
76
|
+
async function waitForRetryDelay(ms, signal) {
|
|
77
|
+
if (signal?.aborted) {
|
|
78
|
+
throw createAbortError(signal);
|
|
79
|
+
}
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
const timeout = setTimeout(() => {
|
|
82
|
+
cleanup();
|
|
83
|
+
resolve();
|
|
84
|
+
}, ms);
|
|
85
|
+
const abort = () => {
|
|
86
|
+
cleanup();
|
|
87
|
+
reject(createAbortError(signal));
|
|
88
|
+
};
|
|
89
|
+
const cleanup = () => {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
signal?.removeEventListener("abort", abort);
|
|
92
|
+
};
|
|
93
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function createJsonRequestInit(params) {
|
|
97
|
+
const requestInit = {
|
|
98
|
+
method: params.method,
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: params.authHeaderValue,
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
...params.headers,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
if (params.signal != null) {
|
|
106
|
+
requestInit.signal = params.signal;
|
|
107
|
+
}
|
|
108
|
+
if (params.body != null) {
|
|
109
|
+
requestInit.body = JSON.stringify(params.body);
|
|
110
|
+
}
|
|
111
|
+
return requestInit;
|
|
112
|
+
}
|
|
113
|
+
function parseResponseBody(bodyText) {
|
|
114
|
+
if (bodyText.trim() === "") {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const trimmedBody = bodyText.trim();
|
|
118
|
+
if (trimmedBody.startsWith("{") || trimmedBody.startsWith("[")) {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(bodyText);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return bodyText;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return bodyText;
|
|
127
|
+
}
|
|
128
|
+
async function requestAndoPublicApi(params) {
|
|
129
|
+
const fetchImpl = params.fetchImpl ?? fetch;
|
|
130
|
+
const response = await fetchImpl(params.url, createJsonRequestInit({
|
|
131
|
+
authHeaderValue: `Bearer ${params.apiKey}`,
|
|
132
|
+
body: params.body,
|
|
133
|
+
headers: {
|
|
134
|
+
Accept: "application/json",
|
|
135
|
+
...params.headers,
|
|
136
|
+
},
|
|
137
|
+
method: params.method,
|
|
138
|
+
signal: params.signal,
|
|
139
|
+
}));
|
|
140
|
+
const bodyText = await response.text();
|
|
141
|
+
return {
|
|
142
|
+
body: parseResponseBody(bodyText),
|
|
143
|
+
bodyText,
|
|
144
|
+
empty: bodyText.trim() === "",
|
|
145
|
+
ok: response.ok,
|
|
146
|
+
status: response.status,
|
|
147
|
+
statusText: response.statusText,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
21
150
|
function isDefined(value) {
|
|
22
151
|
return value != null;
|
|
23
152
|
}
|
|
153
|
+
function stripTrailingSlashes(value) {
|
|
154
|
+
let end = value.length;
|
|
155
|
+
while (end > 0 && value[end - 1] === "/") {
|
|
156
|
+
end -= 1;
|
|
157
|
+
}
|
|
158
|
+
return value.slice(0, end);
|
|
159
|
+
}
|
|
160
|
+
function normalizeConfiguredHost(value, fallback) {
|
|
161
|
+
return value == null || value === ""
|
|
162
|
+
? fallback
|
|
163
|
+
: stripTrailingSlashes(value) || fallback;
|
|
164
|
+
}
|
|
165
|
+
function createIdempotencyKey(operation) {
|
|
166
|
+
const random = globalThis.crypto.randomUUID();
|
|
167
|
+
return `ando-sdk-${operation}-${random}`;
|
|
168
|
+
}
|
|
169
|
+
function requireIdempotencyKey(operation, idempotencyKey) {
|
|
170
|
+
const resolved = idempotencyKey ?? createIdempotencyKey(operation);
|
|
171
|
+
if (resolved.trim() === "") {
|
|
172
|
+
throw new Error(`[ando-sdk] ${operation} idempotency key cannot be empty`);
|
|
173
|
+
}
|
|
174
|
+
if (resolved.length > 255) {
|
|
175
|
+
throw new Error(`[ando-sdk] ${operation} idempotency key must be 255 characters or fewer`);
|
|
176
|
+
}
|
|
177
|
+
return resolved;
|
|
178
|
+
}
|
|
179
|
+
function parseErrorBody(bodyText) {
|
|
180
|
+
if (bodyText.trim() === "") {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
return JSON.parse(bodyText);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return bodyText;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function getErrorDetails(body) {
|
|
191
|
+
if (body == null || typeof body !== "object" || !("error" in body)) {
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
const error = body.error;
|
|
195
|
+
if (typeof error === "string") {
|
|
196
|
+
const code = body.error_code;
|
|
197
|
+
return typeof code === "string" ? { code } : {};
|
|
198
|
+
}
|
|
199
|
+
if (error == null || typeof error !== "object") {
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
const code = error.code;
|
|
203
|
+
const requestId = error.request_id;
|
|
204
|
+
return {
|
|
205
|
+
...(typeof code === "string" ? { code } : {}),
|
|
206
|
+
...(typeof requestId === "string" ? { requestId } : {}),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function emitDiagnostic(diagnostics, event) {
|
|
210
|
+
try {
|
|
211
|
+
const result = diagnostics?.(event);
|
|
212
|
+
if (result != null) {
|
|
213
|
+
result.then(() => undefined, () => undefined);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Diagnostics should never affect SDK behavior.
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function createRequestSignal(params) {
|
|
221
|
+
if (params.timeoutMs == null || params.timeoutMs <= 0) {
|
|
222
|
+
return {
|
|
223
|
+
signal: params.signal,
|
|
224
|
+
cleanup: () => undefined,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const controller = new AbortController();
|
|
228
|
+
let timeout = setTimeout(() => {
|
|
229
|
+
controller.abort(new Error(params.timeoutMessage));
|
|
230
|
+
}, params.timeoutMs);
|
|
231
|
+
const abortFromCaller = () => {
|
|
232
|
+
controller.abort(params.signal?.reason);
|
|
233
|
+
};
|
|
234
|
+
if (params.signal?.aborted) {
|
|
235
|
+
abortFromCaller();
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
params.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
signal: controller.signal,
|
|
242
|
+
cleanup: () => {
|
|
243
|
+
if (timeout != null) {
|
|
244
|
+
clearTimeout(timeout);
|
|
245
|
+
timeout = undefined;
|
|
246
|
+
}
|
|
247
|
+
params.signal?.removeEventListener("abort", abortFromCaller);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
24
251
|
function buildMessageText(message) {
|
|
25
252
|
// Context generation prefers one canonical body plus lightweight
|
|
26
253
|
// placeholders for non-text inputs.
|
|
27
|
-
const body = message.markdown_content?.trim();
|
|
254
|
+
const body = (message.markdown_content ?? message.content)?.trim();
|
|
28
255
|
if (body) {
|
|
29
256
|
return body;
|
|
30
257
|
}
|
|
31
258
|
const imageCount = message.image_urls?.length ?? 0;
|
|
32
259
|
if (imageCount > 0) {
|
|
33
|
-
return imageCount === 1
|
|
34
|
-
? "[image attachment]"
|
|
35
|
-
: `[${imageCount} image attachments]`;
|
|
260
|
+
return imageCount === 1 ? "[image attachment]" : `[${imageCount} image attachments]`;
|
|
36
261
|
}
|
|
37
262
|
const fileCount = message.files?.length ?? 0;
|
|
38
263
|
if (fileCount > 0) {
|
|
39
|
-
return fileCount === 1
|
|
40
|
-
? "[file attachment]"
|
|
41
|
-
: `[${fileCount} file attachments]`;
|
|
264
|
+
return fileCount === 1 ? "[file attachment]" : `[${fileCount} file attachments]`;
|
|
42
265
|
}
|
|
43
266
|
if (message.call_id != null) {
|
|
44
267
|
return "[Jam started]";
|
|
45
268
|
}
|
|
46
269
|
return null;
|
|
47
270
|
}
|
|
271
|
+
function resolveAndoPublicApiBaseUrl(apiHost) {
|
|
272
|
+
const url = new URL(apiHost);
|
|
273
|
+
url.search = "";
|
|
274
|
+
url.hash = "";
|
|
275
|
+
const trimmedPath = url.pathname.replace(/\/+$/, "");
|
|
276
|
+
if (trimmedPath.endsWith("/v1") || trimmedPath.endsWith("/api/v1")) {
|
|
277
|
+
url.pathname = trimmedPath;
|
|
278
|
+
return url.toString().replace(/\/+$/, "");
|
|
279
|
+
}
|
|
280
|
+
if (url.hostname === "api.ando.so") {
|
|
281
|
+
url.pathname = "/v1";
|
|
282
|
+
return url.toString().replace(/\/+$/, "");
|
|
283
|
+
}
|
|
284
|
+
url.pathname = `${trimmedPath === "" ? "" : trimmedPath}/v1`;
|
|
285
|
+
return url.toString().replace(/\/+$/, "");
|
|
286
|
+
}
|
|
287
|
+
function publicApiPathForConfiguredBase(baseUrl, publicPath) {
|
|
288
|
+
const basePath = new URL(baseUrl).pathname.replace(/\/+$/, "");
|
|
289
|
+
if (publicPath.startsWith("/v1/") &&
|
|
290
|
+
(basePath.endsWith("/v1") || basePath.endsWith("/api/v1"))) {
|
|
291
|
+
return publicPath.slice("/v1".length);
|
|
292
|
+
}
|
|
293
|
+
return publicPath;
|
|
294
|
+
}
|
|
295
|
+
function messageAuthorId(message) {
|
|
296
|
+
return message.author?.id ?? message.author_id ?? "unknown";
|
|
297
|
+
}
|
|
298
|
+
function messageAuthorName(message) {
|
|
299
|
+
return message.author?.display_name ?? message.author_name ?? null;
|
|
300
|
+
}
|
|
48
301
|
function buildHistoryEntry(message) {
|
|
49
302
|
const body = buildMessageText(message);
|
|
50
303
|
if (!body) {
|
|
@@ -52,8 +305,8 @@ function buildHistoryEntry(message) {
|
|
|
52
305
|
}
|
|
53
306
|
return {
|
|
54
307
|
message_id: message.id,
|
|
55
|
-
author_id: message
|
|
56
|
-
author_name: message
|
|
308
|
+
author_id: messageAuthorId(message),
|
|
309
|
+
author_name: messageAuthorName(message),
|
|
57
310
|
body,
|
|
58
311
|
created_at: toIsoString(message.created_at),
|
|
59
312
|
};
|
|
@@ -65,57 +318,127 @@ function joinBodies(messages) {
|
|
|
65
318
|
if (!body) {
|
|
66
319
|
return null;
|
|
67
320
|
}
|
|
68
|
-
return `${message
|
|
321
|
+
return `${messageAuthorName(message) ?? messageAuthorId(message)}: ${body}`;
|
|
69
322
|
})
|
|
70
323
|
.filter(Boolean);
|
|
71
324
|
return lines.length > 0 ? lines.join("\n\n") : null;
|
|
72
325
|
}
|
|
73
326
|
class AndoClient {
|
|
74
327
|
constructor(options) {
|
|
75
|
-
this.baseUrl = options.baseUrl.
|
|
76
|
-
this.realtimeHost = options.realtimeHost
|
|
328
|
+
this.baseUrl = normalizeConfiguredHost(options.baseUrl, exports.DEFAULT_ANDO_BASE_URL);
|
|
329
|
+
this.realtimeHost = normalizeConfiguredHost(options.realtimeHost, exports.DEFAULT_ANDO_REALTIME_HOST);
|
|
77
330
|
this.authHeaderValue = `Bearer ${options.auth.apiKey}`;
|
|
331
|
+
this.diagnostics = options.diagnostics;
|
|
78
332
|
this.fetchImpl = options.fetch ?? fetch;
|
|
333
|
+
this.requestTimeoutMs = options.requestTimeoutMs;
|
|
79
334
|
this.realtime = {
|
|
80
335
|
subscribeMember: async (realtimeOptions) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
336
|
+
const realtimeClient = await this.createRealtimeClient();
|
|
337
|
+
return realtimeClient.subscribeMember({
|
|
338
|
+
...realtimeOptions,
|
|
339
|
+
diagnostics: realtimeOptions?.diagnostics ?? this.diagnostics,
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
on: async (event, realtimeOptions) => {
|
|
343
|
+
const realtimeClient = await this.createRealtimeClient();
|
|
344
|
+
return realtimeClient.on(event, {
|
|
345
|
+
...realtimeOptions,
|
|
346
|
+
diagnostics: realtimeOptions?.diagnostics ?? this.diagnostics,
|
|
92
347
|
});
|
|
93
|
-
return realtimeClient.subscribeMember(realtimeOptions);
|
|
94
348
|
},
|
|
95
349
|
};
|
|
96
350
|
}
|
|
351
|
+
async createRealtimeClient() {
|
|
352
|
+
const { AndoRealtimeClient } = await import("./realtime.js");
|
|
353
|
+
return new AndoRealtimeClient({
|
|
354
|
+
diagnostics: this.diagnostics,
|
|
355
|
+
openRealtimeConnection: (request, signal) => this.openRealtimeConnection(request, signal),
|
|
356
|
+
getMessageContext: (messageId, signal, contextOptions) => this.getMessageContext(messageId, signal, contextOptions),
|
|
357
|
+
getConversation: (conversationId, signal) => this.getConversation(conversationId, signal),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
97
360
|
async fetchJson(path, options = {}) {
|
|
98
361
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
362
|
+
const method = options.method ?? "GET";
|
|
99
363
|
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
100
364
|
if (value == null || value === "") {
|
|
101
365
|
continue;
|
|
102
366
|
}
|
|
103
367
|
url.searchParams.set(key, String(value));
|
|
104
368
|
}
|
|
105
|
-
const
|
|
106
|
-
method: options.method ?? "GET",
|
|
369
|
+
const requestSignal = createRequestSignal({
|
|
107
370
|
signal: options.signal,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"Content-Type": "application/json",
|
|
111
|
-
},
|
|
112
|
-
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
371
|
+
timeoutMs: this.requestTimeoutMs,
|
|
372
|
+
timeoutMessage: `[ando-sdk] ${method} ${path} timed out after ${this.requestTimeoutMs}ms`,
|
|
113
373
|
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
374
|
+
const canRetry = isRequestRetryable(method, options.headers);
|
|
375
|
+
const startedAt = Date.now();
|
|
376
|
+
try {
|
|
377
|
+
for (let attempt = 0;; attempt += 1) {
|
|
378
|
+
let status;
|
|
379
|
+
try {
|
|
380
|
+
const requestInit = createJsonRequestInit({
|
|
381
|
+
authHeaderValue: this.authHeaderValue,
|
|
382
|
+
body: options.body,
|
|
383
|
+
headers: options.headers,
|
|
384
|
+
method,
|
|
385
|
+
signal: requestSignal.signal,
|
|
386
|
+
});
|
|
387
|
+
const response = await this.fetchImpl(url, requestInit);
|
|
388
|
+
status = response.status;
|
|
389
|
+
if (!response.ok) {
|
|
390
|
+
const bodyText = await response.text().catch(() => "");
|
|
391
|
+
const body = parseErrorBody(bodyText);
|
|
392
|
+
const details = getErrorDetails(body);
|
|
393
|
+
throw new AndoApiError({
|
|
394
|
+
body,
|
|
395
|
+
bodyText,
|
|
396
|
+
code: details.code,
|
|
397
|
+
method,
|
|
398
|
+
path,
|
|
399
|
+
requestId: details.requestId,
|
|
400
|
+
status: response.status,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
const data = (await response.json());
|
|
404
|
+
emitDiagnostic(this.diagnostics, {
|
|
405
|
+
type: "http",
|
|
406
|
+
method,
|
|
407
|
+
path,
|
|
408
|
+
status,
|
|
409
|
+
ok: true,
|
|
410
|
+
durationMs: Date.now() - startedAt,
|
|
411
|
+
});
|
|
412
|
+
return data;
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
const retryDelay = TRANSIENT_API_RETRY_DELAYS_MS[attempt];
|
|
416
|
+
if (canRetry &&
|
|
417
|
+
retryDelay != null &&
|
|
418
|
+
!requestSignal.signal?.aborted &&
|
|
419
|
+
isRetryableRequestError(error)) {
|
|
420
|
+
await waitForRetryDelay(retryDelay, requestSignal.signal);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
emitDiagnostic(this.diagnostics, {
|
|
424
|
+
type: "http",
|
|
425
|
+
method,
|
|
426
|
+
path,
|
|
427
|
+
status,
|
|
428
|
+
ok: false,
|
|
429
|
+
durationMs: Date.now() - startedAt,
|
|
430
|
+
error,
|
|
431
|
+
});
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
117
435
|
}
|
|
118
|
-
|
|
436
|
+
finally {
|
|
437
|
+
requestSignal.cleanup();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
fetchPublicJson(path, options = {}) {
|
|
441
|
+
return this.fetchJson(publicApiPathForConfiguredBase(this.baseUrl, path), options);
|
|
119
442
|
}
|
|
120
443
|
encodeMessageCursor(message) {
|
|
121
444
|
const cursor = {
|
|
@@ -131,6 +454,73 @@ class AndoClient {
|
|
|
131
454
|
}
|
|
132
455
|
return data;
|
|
133
456
|
}
|
|
457
|
+
async searchMessages(params, signal) {
|
|
458
|
+
return (0, agent_endpoints_1.fetchSearchMessages)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
459
|
+
}
|
|
460
|
+
async searchMembers(params, signal) {
|
|
461
|
+
return (0, agent_endpoints_1.fetchSearchMembers)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
462
|
+
}
|
|
463
|
+
async searchConversations(params, signal) {
|
|
464
|
+
return (0, agent_endpoints_1.fetchSearchConversations)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
465
|
+
}
|
|
466
|
+
async searchClipboard(params, signal) {
|
|
467
|
+
return (0, agent_endpoints_1.fetchSearchClipboard)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
468
|
+
}
|
|
469
|
+
async searchClipboards(params, signal) {
|
|
470
|
+
return this.searchClipboard(params, signal);
|
|
471
|
+
}
|
|
472
|
+
async searchCalls(params, signal) {
|
|
473
|
+
return (0, agent_endpoints_1.fetchSearchCalls)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
474
|
+
}
|
|
475
|
+
async searchTasks(params, signal) {
|
|
476
|
+
return (0, agent_endpoints_1.fetchSearchTasks)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
477
|
+
}
|
|
478
|
+
async getConversationMessageResult(messageId, signal) {
|
|
479
|
+
return (0, agent_endpoints_1.fetchConversationMessageResult)((path, options) => this.fetchPublicJson(path, options), messageId, signal);
|
|
480
|
+
}
|
|
481
|
+
async getMember(memberId, signal) {
|
|
482
|
+
return (0, agent_endpoints_1.fetchMember)((path, options) => this.fetchPublicJson(path, options), memberId, signal);
|
|
483
|
+
}
|
|
484
|
+
async getClipboard(clipboardId, signal) {
|
|
485
|
+
return (0, agent_endpoints_1.fetchClipboard)((path, options) => this.fetchPublicJson(path, options), clipboardId, signal);
|
|
486
|
+
}
|
|
487
|
+
async getCall(callId, signal) {
|
|
488
|
+
return (0, agent_endpoints_1.fetchCall)((path, options) => this.fetchPublicJson(path, options), callId, signal);
|
|
489
|
+
}
|
|
490
|
+
async getCallTranscript(params, signal) {
|
|
491
|
+
return (0, agent_endpoints_1.fetchCallTranscript)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
492
|
+
}
|
|
493
|
+
async getConversationMessageResults(params, signal) {
|
|
494
|
+
return (0, agent_endpoints_1.fetchConversationMessageResults)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
495
|
+
}
|
|
496
|
+
async listConversationMessages(params, signal) {
|
|
497
|
+
return this.getConversationMessageResults(params, signal);
|
|
498
|
+
}
|
|
499
|
+
async listThreadReplyResults(params, signal) {
|
|
500
|
+
return (0, agent_endpoints_1.fetchThreadReplyResults)((path, options) => this.fetchPublicJson(path, options), params, signal);
|
|
501
|
+
}
|
|
502
|
+
async listThreadReplies(params, signal) {
|
|
503
|
+
return this.listThreadReplyResults(params, signal);
|
|
504
|
+
}
|
|
505
|
+
async recordTaskUpdate(params, signal) {
|
|
506
|
+
return (0, agent_endpoints_1.fetchRecordTaskUpdate)((path, options) => this.fetchPublicJson(path, options), {
|
|
507
|
+
...params,
|
|
508
|
+
idempotencyKey: requireIdempotencyKey("recordTaskUpdate", params.idempotencyKey),
|
|
509
|
+
}, signal);
|
|
510
|
+
}
|
|
511
|
+
async getTask(taskId, signal) {
|
|
512
|
+
return (0, agent_endpoints_1.fetchTask)((path, options) => this.fetchPublicJson(path, options), taskId, signal);
|
|
513
|
+
}
|
|
514
|
+
async createConversationMessage(input, signal) {
|
|
515
|
+
const response = await (0, agent_endpoints_1.fetchCreateConversationMessage)((path, options) => this.fetchPublicJson(path, options), {
|
|
516
|
+
input,
|
|
517
|
+
idempotencyKey: requireIdempotencyKey("createConversationMessage", input.idempotencyKey),
|
|
518
|
+
}, signal);
|
|
519
|
+
if (!response.success) {
|
|
520
|
+
throw new Error("[ando-sdk] createConversationMessage returned an empty response");
|
|
521
|
+
}
|
|
522
|
+
return response.data;
|
|
523
|
+
}
|
|
134
524
|
async getOptionalConversationMessage(messageId, signal) {
|
|
135
525
|
try {
|
|
136
526
|
return await this.getConversationMessage(messageId, signal);
|
|
@@ -165,6 +555,40 @@ class AndoClient {
|
|
|
165
555
|
},
|
|
166
556
|
});
|
|
167
557
|
}
|
|
558
|
+
async getOptionalConversationMessages(conversationId, query, signal) {
|
|
559
|
+
try {
|
|
560
|
+
return await this.getConversationMessages(conversationId, query, signal);
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
if (isOptionalContextHistoryError(error)) {
|
|
564
|
+
return {
|
|
565
|
+
items: [],
|
|
566
|
+
pageInfo: {
|
|
567
|
+
hasNextPage: false,
|
|
568
|
+
hasPreviousPage: false,
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async getOptionalThreadReplies(messageId, query, signal) {
|
|
576
|
+
try {
|
|
577
|
+
return await this.getThreadReplies(messageId, query, signal);
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
if (isOptionalContextHistoryError(error)) {
|
|
581
|
+
return {
|
|
582
|
+
items: [],
|
|
583
|
+
pageInfo: {
|
|
584
|
+
hasNextPage: false,
|
|
585
|
+
hasPreviousPage: false,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
168
592
|
async getMe(signal) {
|
|
169
593
|
const { data } = await this.fetchJson("/auth/me", {
|
|
170
594
|
signal,
|
|
@@ -183,15 +607,31 @@ class AndoClient {
|
|
|
183
607
|
},
|
|
184
608
|
});
|
|
185
609
|
}
|
|
186
|
-
async
|
|
610
|
+
async getConversation(conversationId, signal) {
|
|
611
|
+
let cursor;
|
|
612
|
+
do {
|
|
613
|
+
const response = await this.getMyMemberships({
|
|
614
|
+
cursor,
|
|
615
|
+
limit: 200,
|
|
616
|
+
signal,
|
|
617
|
+
});
|
|
618
|
+
const match = response.items.find((membership) => membership.conversation.id === conversationId);
|
|
619
|
+
if (match) {
|
|
620
|
+
return match.conversation;
|
|
621
|
+
}
|
|
622
|
+
cursor = response.pageInfo.hasNextPage ? response.pageInfo.nextCursor : undefined;
|
|
623
|
+
} while (cursor);
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
async getMessageContext(messageId, signal, options = {}) {
|
|
187
627
|
const message = await this.getConversationMessage(messageId, signal);
|
|
188
628
|
const currentText = buildMessageText(message);
|
|
189
|
-
const
|
|
190
|
-
const
|
|
629
|
+
const replyThreadRootId = options.threadRootId ?? message.thread_root_id ?? message.id;
|
|
630
|
+
const isThreadReply = replyThreadRootId !== message.id;
|
|
191
631
|
if (isThreadReply) {
|
|
192
632
|
const [threadRootMessage, threadReplyHistory] = await Promise.all([
|
|
193
633
|
this.getOptionalConversationMessage(replyThreadRootId, signal),
|
|
194
|
-
this.
|
|
634
|
+
this.getOptionalThreadReplies(replyThreadRootId, {
|
|
195
635
|
limit: DEFAULT_THREAD_HISTORY_LIMIT,
|
|
196
636
|
before: this.encodeMessageCursor(message),
|
|
197
637
|
}, signal),
|
|
@@ -213,12 +653,12 @@ class AndoClient {
|
|
|
213
653
|
const [conversationHistory, threadReplyHistory] = await Promise.all([
|
|
214
654
|
// Conversation pagination is fetched newest-first under the hood and then
|
|
215
655
|
// reversed for display, so `after` pages further back in history here.
|
|
216
|
-
this.
|
|
656
|
+
this.getOptionalConversationMessages(message.conversation_id, {
|
|
217
657
|
limit: DEFAULT_HISTORY_LIMIT,
|
|
218
658
|
after: this.encodeMessageCursor(message),
|
|
219
659
|
}, signal),
|
|
220
660
|
message.replies_count > 0
|
|
221
|
-
? this.
|
|
661
|
+
? this.getOptionalThreadReplies(message.id, {
|
|
222
662
|
limit: DEFAULT_THREAD_HISTORY_LIMIT,
|
|
223
663
|
}, signal)
|
|
224
664
|
: Promise.resolve(null),
|
|
@@ -237,12 +677,23 @@ class AndoClient {
|
|
|
237
677
|
: null,
|
|
238
678
|
};
|
|
239
679
|
}
|
|
680
|
+
async openRealtimeConnection(request, signal) {
|
|
681
|
+
return this.fetchJson(publicApiPathForConfiguredBase(this.baseUrl, "/v1/realtime/connections"), {
|
|
682
|
+
method: "POST",
|
|
683
|
+
signal,
|
|
684
|
+
body: request,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
240
687
|
async postMessage(input) {
|
|
241
688
|
const response = await this.fetchJson(`/conversations/${input.conversationId}/messages`, {
|
|
242
689
|
method: "POST",
|
|
690
|
+
headers: {
|
|
691
|
+
"Idempotency-Key": requireIdempotencyKey("postMessage", input.idempotencyKey),
|
|
692
|
+
},
|
|
243
693
|
body: {
|
|
244
694
|
markdown_content: input.markdownContent,
|
|
245
695
|
thread_root_id: input.threadRootId ?? null,
|
|
696
|
+
replied_to_message_id: input.repliedToMessageId ?? null,
|
|
246
697
|
call_root_id: input.callRootId ?? null,
|
|
247
698
|
},
|
|
248
699
|
});
|
|
@@ -251,6 +702,15 @@ class AndoClient {
|
|
|
251
702
|
}
|
|
252
703
|
return response.data;
|
|
253
704
|
}
|
|
705
|
+
async addReaction(messageId, emojiText, signal) {
|
|
706
|
+
return this.fetchJson(`/conversation-messages/${encodeURIComponent(messageId)}/reactions`, {
|
|
707
|
+
method: "POST",
|
|
708
|
+
signal,
|
|
709
|
+
body: {
|
|
710
|
+
emoji_text: emojiText,
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
}
|
|
254
714
|
async getMemberGroupIdByMemberId(memberId, signal) {
|
|
255
715
|
let cursor;
|
|
256
716
|
do {
|
|
@@ -265,21 +725,29 @@ class AndoClient {
|
|
|
265
725
|
if (match) {
|
|
266
726
|
return match.id;
|
|
267
727
|
}
|
|
268
|
-
cursor = response.pageInfo.hasNextPage
|
|
269
|
-
? response.pageInfo.nextCursor
|
|
270
|
-
: undefined;
|
|
728
|
+
cursor = response.pageInfo.hasNextPage ? response.pageInfo.nextCursor : undefined;
|
|
271
729
|
} while (cursor);
|
|
272
730
|
return null;
|
|
273
731
|
}
|
|
274
|
-
async createWebSocketToken(room, signal) {
|
|
275
|
-
return this.fetchJson("/tokens/ws", {
|
|
276
|
-
method: "POST",
|
|
277
|
-
signal,
|
|
278
|
-
body: {
|
|
279
|
-
room,
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
732
|
}
|
|
284
733
|
exports.AndoClient = AndoClient;
|
|
734
|
+
exports.ANDO_PUBLIC_API_CLIENT_METHODS = {
|
|
735
|
+
createConversationMessage: "createConversationMessage",
|
|
736
|
+
getCall: "getCall",
|
|
737
|
+
getCallTranscript: "getCallTranscript",
|
|
738
|
+
getClipboard: "getClipboard",
|
|
739
|
+
getConversationMessage: "getConversationMessageResult",
|
|
740
|
+
getMember: "getMember",
|
|
741
|
+
getTask: "getTask",
|
|
742
|
+
listConversationMessages: "listConversationMessages",
|
|
743
|
+
listThreadReplies: "listThreadReplies",
|
|
744
|
+
openRealtimeConnection: "openRealtimeConnection",
|
|
745
|
+
recordTaskUpdate: "recordTaskUpdate",
|
|
746
|
+
searchCalls: "searchCalls",
|
|
747
|
+
searchClipboards: "searchClipboards",
|
|
748
|
+
searchConversations: "searchConversations",
|
|
749
|
+
searchMembers: "searchMembers",
|
|
750
|
+
searchMessages: "searchMessages",
|
|
751
|
+
searchTasks: "searchTasks",
|
|
752
|
+
};
|
|
285
753
|
//# sourceMappingURL=client.js.map
|