@andocorp/sdk 0.1.0 → 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/README.md +26 -4
- 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 +131 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +523 -57
- package/dist/client.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 +23 -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 -160
- package/dist/realtime-events.js.map +1 -1
- package/dist/realtime-runtime.d.ts +5 -17
- package/dist/realtime-runtime.d.ts.map +1 -1
- package/dist/realtime-runtime.js +41 -67
- package/dist/realtime-runtime.js.map +1 -1
- package/dist/realtime-types.d.ts +58 -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 +281 -145
- 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 -23
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -6
package/dist/client.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AndoClient = exports.DEFAULT_ANDO_REALTIME_HOST = exports.DEFAULT_ANDO_BASE_URL = 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");
|
|
4
7
|
exports.DEFAULT_ANDO_BASE_URL = "https://api.ando.so";
|
|
5
|
-
exports.
|
|
8
|
+
exports.DEFAULT_ANDO_PUBLIC_API_BASE_URL = "https://api.ando.so/v1";
|
|
9
|
+
exports.DEFAULT_ANDO_REALTIME_HOST = "realtime.ando.so";
|
|
6
10
|
class AndoApiError extends Error {
|
|
7
|
-
constructor(
|
|
8
|
-
|
|
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;
|
|
9
20
|
this.name = "AndoApiError";
|
|
10
|
-
this.status = status;
|
|
21
|
+
this.status = params.status;
|
|
11
22
|
}
|
|
12
23
|
}
|
|
24
|
+
exports.AndoApiError = AndoApiError;
|
|
13
25
|
const DEFAULT_HISTORY_LIMIT = 8;
|
|
14
26
|
const DEFAULT_THREAD_HISTORY_LIMIT = 12;
|
|
27
|
+
const TRANSIENT_API_RETRY_DELAYS_MS = [100, 250];
|
|
15
28
|
function toIsoString(value) {
|
|
16
29
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
17
30
|
}
|
|
@@ -20,33 +33,271 @@ function isNullConversationMessageError(error) {
|
|
|
20
33
|
error.message.includes("/conversation-messages/") &&
|
|
21
34
|
error.message.includes("returned null"));
|
|
22
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
|
+
}
|
|
23
150
|
function isDefined(value) {
|
|
24
151
|
return value != null;
|
|
25
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
|
+
}
|
|
26
251
|
function buildMessageText(message) {
|
|
27
252
|
// Context generation prefers one canonical body plus lightweight
|
|
28
253
|
// placeholders for non-text inputs.
|
|
29
|
-
const body = message.markdown_content?.trim();
|
|
254
|
+
const body = (message.markdown_content ?? message.content)?.trim();
|
|
30
255
|
if (body) {
|
|
31
256
|
return body;
|
|
32
257
|
}
|
|
33
258
|
const imageCount = message.image_urls?.length ?? 0;
|
|
34
259
|
if (imageCount > 0) {
|
|
35
|
-
return imageCount === 1
|
|
36
|
-
? "[image attachment]"
|
|
37
|
-
: `[${imageCount} image attachments]`;
|
|
260
|
+
return imageCount === 1 ? "[image attachment]" : `[${imageCount} image attachments]`;
|
|
38
261
|
}
|
|
39
262
|
const fileCount = message.files?.length ?? 0;
|
|
40
263
|
if (fileCount > 0) {
|
|
41
|
-
return fileCount === 1
|
|
42
|
-
? "[file attachment]"
|
|
43
|
-
: `[${fileCount} file attachments]`;
|
|
264
|
+
return fileCount === 1 ? "[file attachment]" : `[${fileCount} file attachments]`;
|
|
44
265
|
}
|
|
45
266
|
if (message.call_id != null) {
|
|
46
267
|
return "[Jam started]";
|
|
47
268
|
}
|
|
48
269
|
return null;
|
|
49
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
|
+
}
|
|
50
301
|
function buildHistoryEntry(message) {
|
|
51
302
|
const body = buildMessageText(message);
|
|
52
303
|
if (!body) {
|
|
@@ -54,8 +305,8 @@ function buildHistoryEntry(message) {
|
|
|
54
305
|
}
|
|
55
306
|
return {
|
|
56
307
|
message_id: message.id,
|
|
57
|
-
author_id: message
|
|
58
|
-
author_name: message
|
|
308
|
+
author_id: messageAuthorId(message),
|
|
309
|
+
author_name: messageAuthorName(message),
|
|
59
310
|
body,
|
|
60
311
|
created_at: toIsoString(message.created_at),
|
|
61
312
|
};
|
|
@@ -67,56 +318,127 @@ function joinBodies(messages) {
|
|
|
67
318
|
if (!body) {
|
|
68
319
|
return null;
|
|
69
320
|
}
|
|
70
|
-
return `${message
|
|
321
|
+
return `${messageAuthorName(message) ?? messageAuthorId(message)}: ${body}`;
|
|
71
322
|
})
|
|
72
323
|
.filter(Boolean);
|
|
73
324
|
return lines.length > 0 ? lines.join("\n\n") : null;
|
|
74
325
|
}
|
|
75
326
|
class AndoClient {
|
|
76
327
|
constructor(options) {
|
|
77
|
-
this.baseUrl =
|
|
78
|
-
|
|
79
|
-
this.realtimeHost =
|
|
80
|
-
options.realtimeHost?.replace(/\/+$/, "") || exports.DEFAULT_ANDO_REALTIME_HOST;
|
|
328
|
+
this.baseUrl = normalizeConfiguredHost(options.baseUrl, exports.DEFAULT_ANDO_BASE_URL);
|
|
329
|
+
this.realtimeHost = normalizeConfiguredHost(options.realtimeHost, exports.DEFAULT_ANDO_REALTIME_HOST);
|
|
81
330
|
this.authHeaderValue = `Bearer ${options.auth.apiKey}`;
|
|
331
|
+
this.diagnostics = options.diagnostics;
|
|
82
332
|
this.fetchImpl = options.fetch ?? fetch;
|
|
333
|
+
this.requestTimeoutMs = options.requestTimeoutMs;
|
|
83
334
|
this.realtime = {
|
|
84
335
|
subscribeMember: async (realtimeOptions) => {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
93
347
|
});
|
|
94
|
-
return realtimeClient.subscribeMember(realtimeOptions);
|
|
95
348
|
},
|
|
96
349
|
};
|
|
97
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
|
+
}
|
|
98
360
|
async fetchJson(path, options = {}) {
|
|
99
361
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
362
|
+
const method = options.method ?? "GET";
|
|
100
363
|
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
101
364
|
if (value == null || value === "") {
|
|
102
365
|
continue;
|
|
103
366
|
}
|
|
104
367
|
url.searchParams.set(key, String(value));
|
|
105
368
|
}
|
|
106
|
-
const
|
|
107
|
-
method: options.method ?? "GET",
|
|
369
|
+
const requestSignal = createRequestSignal({
|
|
108
370
|
signal: options.signal,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"Content-Type": "application/json",
|
|
112
|
-
},
|
|
113
|
-
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
371
|
+
timeoutMs: this.requestTimeoutMs,
|
|
372
|
+
timeoutMessage: `[ando-sdk] ${method} ${path} timed out after ${this.requestTimeoutMs}ms`,
|
|
114
373
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
}
|
|
435
|
+
}
|
|
436
|
+
finally {
|
|
437
|
+
requestSignal.cleanup();
|
|
118
438
|
}
|
|
119
|
-
|
|
439
|
+
}
|
|
440
|
+
fetchPublicJson(path, options = {}) {
|
|
441
|
+
return this.fetchJson(publicApiPathForConfiguredBase(this.baseUrl, path), options);
|
|
120
442
|
}
|
|
121
443
|
encodeMessageCursor(message) {
|
|
122
444
|
const cursor = {
|
|
@@ -132,6 +454,73 @@ class AndoClient {
|
|
|
132
454
|
}
|
|
133
455
|
return data;
|
|
134
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
|
+
}
|
|
135
524
|
async getOptionalConversationMessage(messageId, signal) {
|
|
136
525
|
try {
|
|
137
526
|
return await this.getConversationMessage(messageId, signal);
|
|
@@ -166,6 +555,40 @@ class AndoClient {
|
|
|
166
555
|
},
|
|
167
556
|
});
|
|
168
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
|
+
}
|
|
169
592
|
async getMe(signal) {
|
|
170
593
|
const { data } = await this.fetchJson("/auth/me", {
|
|
171
594
|
signal,
|
|
@@ -184,15 +607,31 @@ class AndoClient {
|
|
|
184
607
|
},
|
|
185
608
|
});
|
|
186
609
|
}
|
|
187
|
-
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 = {}) {
|
|
188
627
|
const message = await this.getConversationMessage(messageId, signal);
|
|
189
628
|
const currentText = buildMessageText(message);
|
|
190
|
-
const
|
|
191
|
-
const
|
|
629
|
+
const replyThreadRootId = options.threadRootId ?? message.thread_root_id ?? message.id;
|
|
630
|
+
const isThreadReply = replyThreadRootId !== message.id;
|
|
192
631
|
if (isThreadReply) {
|
|
193
632
|
const [threadRootMessage, threadReplyHistory] = await Promise.all([
|
|
194
633
|
this.getOptionalConversationMessage(replyThreadRootId, signal),
|
|
195
|
-
this.
|
|
634
|
+
this.getOptionalThreadReplies(replyThreadRootId, {
|
|
196
635
|
limit: DEFAULT_THREAD_HISTORY_LIMIT,
|
|
197
636
|
before: this.encodeMessageCursor(message),
|
|
198
637
|
}, signal),
|
|
@@ -214,12 +653,12 @@ class AndoClient {
|
|
|
214
653
|
const [conversationHistory, threadReplyHistory] = await Promise.all([
|
|
215
654
|
// Conversation pagination is fetched newest-first under the hood and then
|
|
216
655
|
// reversed for display, so `after` pages further back in history here.
|
|
217
|
-
this.
|
|
656
|
+
this.getOptionalConversationMessages(message.conversation_id, {
|
|
218
657
|
limit: DEFAULT_HISTORY_LIMIT,
|
|
219
658
|
after: this.encodeMessageCursor(message),
|
|
220
659
|
}, signal),
|
|
221
660
|
message.replies_count > 0
|
|
222
|
-
? this.
|
|
661
|
+
? this.getOptionalThreadReplies(message.id, {
|
|
223
662
|
limit: DEFAULT_THREAD_HISTORY_LIMIT,
|
|
224
663
|
}, signal)
|
|
225
664
|
: Promise.resolve(null),
|
|
@@ -238,9 +677,19 @@ class AndoClient {
|
|
|
238
677
|
: null,
|
|
239
678
|
};
|
|
240
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
|
+
}
|
|
241
687
|
async postMessage(input) {
|
|
242
688
|
const response = await this.fetchJson(`/conversations/${input.conversationId}/messages`, {
|
|
243
689
|
method: "POST",
|
|
690
|
+
headers: {
|
|
691
|
+
"Idempotency-Key": requireIdempotencyKey("postMessage", input.idempotencyKey),
|
|
692
|
+
},
|
|
244
693
|
body: {
|
|
245
694
|
markdown_content: input.markdownContent,
|
|
246
695
|
thread_root_id: input.threadRootId ?? null,
|
|
@@ -253,6 +702,15 @@ class AndoClient {
|
|
|
253
702
|
}
|
|
254
703
|
return response.data;
|
|
255
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
|
+
}
|
|
256
714
|
async getMemberGroupIdByMemberId(memberId, signal) {
|
|
257
715
|
let cursor;
|
|
258
716
|
do {
|
|
@@ -267,21 +725,29 @@ class AndoClient {
|
|
|
267
725
|
if (match) {
|
|
268
726
|
return match.id;
|
|
269
727
|
}
|
|
270
|
-
cursor = response.pageInfo.hasNextPage
|
|
271
|
-
? response.pageInfo.nextCursor
|
|
272
|
-
: undefined;
|
|
728
|
+
cursor = response.pageInfo.hasNextPage ? response.pageInfo.nextCursor : undefined;
|
|
273
729
|
} while (cursor);
|
|
274
730
|
return null;
|
|
275
731
|
}
|
|
276
|
-
async createWebSocketToken(room, signal) {
|
|
277
|
-
return this.fetchJson("/tokens/ws", {
|
|
278
|
-
method: "POST",
|
|
279
|
-
signal,
|
|
280
|
-
body: {
|
|
281
|
-
room,
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
732
|
}
|
|
286
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
|
+
};
|
|
287
753
|
//# sourceMappingURL=client.js.map
|