@osmapi/osmtalk-sdk 0.2.0 → 0.3.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/dist/index.d.mts +85 -12
- package/dist/index.d.ts +85 -12
- package/dist/index.js +215 -36
- package/dist/index.mjs +213 -36
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -10,12 +10,45 @@
|
|
|
10
10
|
* dynamicVariables: { first_name: "Arjun" },
|
|
11
11
|
* });
|
|
12
12
|
*/
|
|
13
|
+
/** Bumped on every release — surfaced in the User-Agent header. */
|
|
14
|
+
declare const SDK_VERSION = "0.3.0";
|
|
13
15
|
interface OsmtalkOptions {
|
|
14
16
|
apiKey: string;
|
|
15
17
|
baseUrl?: string;
|
|
18
|
+
/** Per-request timeout. Default 30s. Set `0` to disable. */
|
|
16
19
|
timeoutMs?: number;
|
|
17
20
|
/** Custom fetch implementation (e.g. for testing). */
|
|
18
21
|
fetch?: typeof fetch;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum auto-retries on transient failures (5xx, 429, network
|
|
24
|
+
* errors). Default 2. Set to 0 to disable. Mutating requests (POST,
|
|
25
|
+
* PUT, DELETE) are only retried when an `idempotencyKey` is set on
|
|
26
|
+
* the call — otherwise a retry could double-charge or double-place
|
|
27
|
+
* a call.
|
|
28
|
+
*/
|
|
29
|
+
maxRetries?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Initial retry delay in ms. Doubled on each subsequent retry.
|
|
32
|
+
* Default 250ms → 500 → 1000. Server `Retry-After` headers take
|
|
33
|
+
* precedence when present.
|
|
34
|
+
*/
|
|
35
|
+
retryInitialDelayMs?: number;
|
|
36
|
+
/** Extra headers added to every request (e.g. observability IDs). */
|
|
37
|
+
defaultHeaders?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* Org ID to send as `X-Organization-Id`. For users in multiple orgs;
|
|
40
|
+
* defaults to the API key's primary org if omitted.
|
|
41
|
+
*/
|
|
42
|
+
organizationId?: string;
|
|
43
|
+
}
|
|
44
|
+
interface RequestOptions {
|
|
45
|
+
idempotencyKey?: string;
|
|
46
|
+
/** AbortSignal for caller-side cancellation. Combined with the SDK's timeout signal. */
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
/** Per-call override of the global timeout. */
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
/** Per-call override of the org ID. */
|
|
51
|
+
organizationId?: string;
|
|
19
52
|
}
|
|
20
53
|
interface DynamicVariables {
|
|
21
54
|
[key: string]: string | number | boolean;
|
|
@@ -141,15 +174,36 @@ interface OsmtalkErrorBody {
|
|
|
141
174
|
declare class OsmtalkError extends Error {
|
|
142
175
|
readonly status: number;
|
|
143
176
|
readonly body: OsmtalkErrorBody;
|
|
144
|
-
|
|
177
|
+
/** Number of retry attempts the SDK made before giving up. */
|
|
178
|
+
readonly retryAttempts: number;
|
|
179
|
+
constructor(status: number, body: OsmtalkErrorBody, retryAttempts?: number);
|
|
180
|
+
/** True for status codes the server might recover from on retry. */
|
|
181
|
+
get isRetryable(): boolean;
|
|
182
|
+
/** True for client mistakes (bad input, bad auth). */
|
|
183
|
+
get isClientError(): boolean;
|
|
145
184
|
}
|
|
146
185
|
declare class HttpClient {
|
|
147
186
|
private readonly baseUrl;
|
|
148
187
|
private readonly apiKey;
|
|
149
188
|
private readonly timeoutMs;
|
|
189
|
+
private readonly maxRetries;
|
|
190
|
+
private readonly retryInitialDelayMs;
|
|
150
191
|
private readonly fetchImpl;
|
|
192
|
+
private readonly defaultHeaders;
|
|
193
|
+
private readonly organizationId;
|
|
194
|
+
private readonly userAgent;
|
|
151
195
|
constructor(opts: OsmtalkOptions);
|
|
152
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Combine the caller's AbortSignal with the SDK's per-request timeout
|
|
198
|
+
* signal so EITHER firing cancels the fetch. We can't use
|
|
199
|
+
* `AbortSignal.any` (Node 18 doesn't have it), so we wire it manually.
|
|
200
|
+
*/
|
|
201
|
+
private buildAbortSignal;
|
|
202
|
+
/** Parse the body once for both success and error paths. */
|
|
203
|
+
private static parseBody;
|
|
204
|
+
/** How long to wait before the next retry. Honors Retry-After. */
|
|
205
|
+
private retryDelay;
|
|
206
|
+
request<T>(method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>, idempotencyKey?: string, options?: RequestOptions): Promise<T>;
|
|
153
207
|
}
|
|
154
208
|
declare class AgentsResource {
|
|
155
209
|
private readonly http;
|
|
@@ -199,27 +253,46 @@ declare class AgentsResource {
|
|
|
199
253
|
callId: string;
|
|
200
254
|
}>;
|
|
201
255
|
}
|
|
256
|
+
/** Call.status values that mean "no more state changes are coming". */
|
|
257
|
+
declare const TERMINAL_CALL_STATUSES: readonly ["completed", "failed", "ended", "cancelled"];
|
|
202
258
|
declare class CallsResource {
|
|
203
259
|
private readonly http;
|
|
204
260
|
constructor(http: HttpClient);
|
|
205
|
-
list(): Promise<CallRecord[]>;
|
|
206
|
-
get(id: string): Promise<CallRecord>;
|
|
261
|
+
list(opts?: RequestOptions): Promise<CallRecord[]>;
|
|
262
|
+
get(id: string, opts?: RequestOptions): Promise<CallRecord>;
|
|
207
263
|
/**
|
|
208
|
-
* Place an outbound call.
|
|
209
|
-
*
|
|
264
|
+
* Place an outbound call. Pass `idempotencyKey` so a retry within 24h
|
|
265
|
+
* returns the same response instead of placing a duplicate call —
|
|
266
|
+
* required if you want this call to be retried on transient failures.
|
|
210
267
|
*/
|
|
211
|
-
outbound(input: CallStartRequest, opts?: {
|
|
212
|
-
idempotencyKey?: string;
|
|
213
|
-
}): Promise<{
|
|
268
|
+
outbound(input: CallStartRequest, opts?: RequestOptions): Promise<{
|
|
214
269
|
callId: string;
|
|
215
270
|
roomName: string;
|
|
216
271
|
}>;
|
|
217
|
-
end(id: string): Promise<{
|
|
272
|
+
end(id: string, opts?: RequestOptions): Promise<{
|
|
218
273
|
success: true;
|
|
219
274
|
}>;
|
|
220
|
-
transfer(id: string, destination: string, summary?: string): Promise<{
|
|
275
|
+
transfer(id: string, destination: string, summary?: string, opts?: RequestOptions): Promise<{
|
|
221
276
|
success: true;
|
|
222
277
|
}>;
|
|
278
|
+
/**
|
|
279
|
+
* Poll a call until it reaches a terminal status (`completed`,
|
|
280
|
+
* `failed`, `ended`, `cancelled`) and return the final record.
|
|
281
|
+
*
|
|
282
|
+
* Saves consumers from writing the same loop in every script. Use
|
|
283
|
+
* webhooks instead in production — this is fine for scripts, demos,
|
|
284
|
+
* and one-off jobs but consumes API quota on every poll.
|
|
285
|
+
*
|
|
286
|
+
* @param opts.pollIntervalMs - default 5s
|
|
287
|
+
* @param opts.timeoutMs - default 30min (rejects with
|
|
288
|
+
* `OsmtalkError` 408 on timeout)
|
|
289
|
+
* @param opts.signal - abort externally
|
|
290
|
+
*/
|
|
291
|
+
waitUntilEnded(id: string, opts?: {
|
|
292
|
+
pollIntervalMs?: number;
|
|
293
|
+
timeoutMs?: number;
|
|
294
|
+
signal?: AbortSignal;
|
|
295
|
+
}): Promise<CallRecord>;
|
|
223
296
|
}
|
|
224
297
|
declare class PlatformResource {
|
|
225
298
|
private readonly http;
|
|
@@ -517,4 +590,4 @@ declare class Osmtalk {
|
|
|
517
590
|
constructor(opts: OsmtalkOptions);
|
|
518
591
|
}
|
|
519
592
|
|
|
520
|
-
export { type AgentRecord, type AgentTemplateResult, type AssistantOverride, type CallRecord, type CallStartRequest, type CampaignCreateRequest, type CampaignRecord, type DynamicVariables, type LeadRow, Osmtalk, OsmtalkError, type OsmtalkErrorBody, type OsmtalkOptions, type PresetCostEstimate, type PresetWithCost, type ProviderHealth, Osmtalk as default, verifyWebhookSignature, verifyWebhookSignatureAsync };
|
|
593
|
+
export { type AgentRecord, type AgentTemplateResult, type AssistantOverride, type CallRecord, type CallStartRequest, type CampaignCreateRequest, type CampaignRecord, type DynamicVariables, type LeadRow, Osmtalk, OsmtalkError, type OsmtalkErrorBody, type OsmtalkOptions, type PresetCostEstimate, type PresetWithCost, type ProviderHealth, type RequestOptions, SDK_VERSION, TERMINAL_CALL_STATUSES, Osmtalk as default, verifyWebhookSignature, verifyWebhookSignatureAsync };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,12 +10,45 @@
|
|
|
10
10
|
* dynamicVariables: { first_name: "Arjun" },
|
|
11
11
|
* });
|
|
12
12
|
*/
|
|
13
|
+
/** Bumped on every release — surfaced in the User-Agent header. */
|
|
14
|
+
declare const SDK_VERSION = "0.3.0";
|
|
13
15
|
interface OsmtalkOptions {
|
|
14
16
|
apiKey: string;
|
|
15
17
|
baseUrl?: string;
|
|
18
|
+
/** Per-request timeout. Default 30s. Set `0` to disable. */
|
|
16
19
|
timeoutMs?: number;
|
|
17
20
|
/** Custom fetch implementation (e.g. for testing). */
|
|
18
21
|
fetch?: typeof fetch;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum auto-retries on transient failures (5xx, 429, network
|
|
24
|
+
* errors). Default 2. Set to 0 to disable. Mutating requests (POST,
|
|
25
|
+
* PUT, DELETE) are only retried when an `idempotencyKey` is set on
|
|
26
|
+
* the call — otherwise a retry could double-charge or double-place
|
|
27
|
+
* a call.
|
|
28
|
+
*/
|
|
29
|
+
maxRetries?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Initial retry delay in ms. Doubled on each subsequent retry.
|
|
32
|
+
* Default 250ms → 500 → 1000. Server `Retry-After` headers take
|
|
33
|
+
* precedence when present.
|
|
34
|
+
*/
|
|
35
|
+
retryInitialDelayMs?: number;
|
|
36
|
+
/** Extra headers added to every request (e.g. observability IDs). */
|
|
37
|
+
defaultHeaders?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* Org ID to send as `X-Organization-Id`. For users in multiple orgs;
|
|
40
|
+
* defaults to the API key's primary org if omitted.
|
|
41
|
+
*/
|
|
42
|
+
organizationId?: string;
|
|
43
|
+
}
|
|
44
|
+
interface RequestOptions {
|
|
45
|
+
idempotencyKey?: string;
|
|
46
|
+
/** AbortSignal for caller-side cancellation. Combined with the SDK's timeout signal. */
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
/** Per-call override of the global timeout. */
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
/** Per-call override of the org ID. */
|
|
51
|
+
organizationId?: string;
|
|
19
52
|
}
|
|
20
53
|
interface DynamicVariables {
|
|
21
54
|
[key: string]: string | number | boolean;
|
|
@@ -141,15 +174,36 @@ interface OsmtalkErrorBody {
|
|
|
141
174
|
declare class OsmtalkError extends Error {
|
|
142
175
|
readonly status: number;
|
|
143
176
|
readonly body: OsmtalkErrorBody;
|
|
144
|
-
|
|
177
|
+
/** Number of retry attempts the SDK made before giving up. */
|
|
178
|
+
readonly retryAttempts: number;
|
|
179
|
+
constructor(status: number, body: OsmtalkErrorBody, retryAttempts?: number);
|
|
180
|
+
/** True for status codes the server might recover from on retry. */
|
|
181
|
+
get isRetryable(): boolean;
|
|
182
|
+
/** True for client mistakes (bad input, bad auth). */
|
|
183
|
+
get isClientError(): boolean;
|
|
145
184
|
}
|
|
146
185
|
declare class HttpClient {
|
|
147
186
|
private readonly baseUrl;
|
|
148
187
|
private readonly apiKey;
|
|
149
188
|
private readonly timeoutMs;
|
|
189
|
+
private readonly maxRetries;
|
|
190
|
+
private readonly retryInitialDelayMs;
|
|
150
191
|
private readonly fetchImpl;
|
|
192
|
+
private readonly defaultHeaders;
|
|
193
|
+
private readonly organizationId;
|
|
194
|
+
private readonly userAgent;
|
|
151
195
|
constructor(opts: OsmtalkOptions);
|
|
152
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Combine the caller's AbortSignal with the SDK's per-request timeout
|
|
198
|
+
* signal so EITHER firing cancels the fetch. We can't use
|
|
199
|
+
* `AbortSignal.any` (Node 18 doesn't have it), so we wire it manually.
|
|
200
|
+
*/
|
|
201
|
+
private buildAbortSignal;
|
|
202
|
+
/** Parse the body once for both success and error paths. */
|
|
203
|
+
private static parseBody;
|
|
204
|
+
/** How long to wait before the next retry. Honors Retry-After. */
|
|
205
|
+
private retryDelay;
|
|
206
|
+
request<T>(method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>, idempotencyKey?: string, options?: RequestOptions): Promise<T>;
|
|
153
207
|
}
|
|
154
208
|
declare class AgentsResource {
|
|
155
209
|
private readonly http;
|
|
@@ -199,27 +253,46 @@ declare class AgentsResource {
|
|
|
199
253
|
callId: string;
|
|
200
254
|
}>;
|
|
201
255
|
}
|
|
256
|
+
/** Call.status values that mean "no more state changes are coming". */
|
|
257
|
+
declare const TERMINAL_CALL_STATUSES: readonly ["completed", "failed", "ended", "cancelled"];
|
|
202
258
|
declare class CallsResource {
|
|
203
259
|
private readonly http;
|
|
204
260
|
constructor(http: HttpClient);
|
|
205
|
-
list(): Promise<CallRecord[]>;
|
|
206
|
-
get(id: string): Promise<CallRecord>;
|
|
261
|
+
list(opts?: RequestOptions): Promise<CallRecord[]>;
|
|
262
|
+
get(id: string, opts?: RequestOptions): Promise<CallRecord>;
|
|
207
263
|
/**
|
|
208
|
-
* Place an outbound call.
|
|
209
|
-
*
|
|
264
|
+
* Place an outbound call. Pass `idempotencyKey` so a retry within 24h
|
|
265
|
+
* returns the same response instead of placing a duplicate call —
|
|
266
|
+
* required if you want this call to be retried on transient failures.
|
|
210
267
|
*/
|
|
211
|
-
outbound(input: CallStartRequest, opts?: {
|
|
212
|
-
idempotencyKey?: string;
|
|
213
|
-
}): Promise<{
|
|
268
|
+
outbound(input: CallStartRequest, opts?: RequestOptions): Promise<{
|
|
214
269
|
callId: string;
|
|
215
270
|
roomName: string;
|
|
216
271
|
}>;
|
|
217
|
-
end(id: string): Promise<{
|
|
272
|
+
end(id: string, opts?: RequestOptions): Promise<{
|
|
218
273
|
success: true;
|
|
219
274
|
}>;
|
|
220
|
-
transfer(id: string, destination: string, summary?: string): Promise<{
|
|
275
|
+
transfer(id: string, destination: string, summary?: string, opts?: RequestOptions): Promise<{
|
|
221
276
|
success: true;
|
|
222
277
|
}>;
|
|
278
|
+
/**
|
|
279
|
+
* Poll a call until it reaches a terminal status (`completed`,
|
|
280
|
+
* `failed`, `ended`, `cancelled`) and return the final record.
|
|
281
|
+
*
|
|
282
|
+
* Saves consumers from writing the same loop in every script. Use
|
|
283
|
+
* webhooks instead in production — this is fine for scripts, demos,
|
|
284
|
+
* and one-off jobs but consumes API quota on every poll.
|
|
285
|
+
*
|
|
286
|
+
* @param opts.pollIntervalMs - default 5s
|
|
287
|
+
* @param opts.timeoutMs - default 30min (rejects with
|
|
288
|
+
* `OsmtalkError` 408 on timeout)
|
|
289
|
+
* @param opts.signal - abort externally
|
|
290
|
+
*/
|
|
291
|
+
waitUntilEnded(id: string, opts?: {
|
|
292
|
+
pollIntervalMs?: number;
|
|
293
|
+
timeoutMs?: number;
|
|
294
|
+
signal?: AbortSignal;
|
|
295
|
+
}): Promise<CallRecord>;
|
|
223
296
|
}
|
|
224
297
|
declare class PlatformResource {
|
|
225
298
|
private readonly http;
|
|
@@ -517,4 +590,4 @@ declare class Osmtalk {
|
|
|
517
590
|
constructor(opts: OsmtalkOptions);
|
|
518
591
|
}
|
|
519
592
|
|
|
520
|
-
export { type AgentRecord, type AgentTemplateResult, type AssistantOverride, type CallRecord, type CallStartRequest, type CampaignCreateRequest, type CampaignRecord, type DynamicVariables, type LeadRow, Osmtalk, OsmtalkError, type OsmtalkErrorBody, type OsmtalkOptions, type PresetCostEstimate, type PresetWithCost, type ProviderHealth, Osmtalk as default, verifyWebhookSignature, verifyWebhookSignatureAsync };
|
|
593
|
+
export { type AgentRecord, type AgentTemplateResult, type AssistantOverride, type CallRecord, type CallStartRequest, type CampaignCreateRequest, type CampaignRecord, type DynamicVariables, type LeadRow, Osmtalk, OsmtalkError, type OsmtalkErrorBody, type OsmtalkOptions, type PresetCostEstimate, type PresetWithCost, type ProviderHealth, type RequestOptions, SDK_VERSION, TERMINAL_CALL_STATUSES, Osmtalk as default, verifyWebhookSignature, verifyWebhookSignatureAsync };
|
package/dist/index.js
CHANGED
|
@@ -22,68 +22,196 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Osmtalk: () => Osmtalk,
|
|
24
24
|
OsmtalkError: () => OsmtalkError,
|
|
25
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
26
|
+
TERMINAL_CALL_STATUSES: () => TERMINAL_CALL_STATUSES,
|
|
25
27
|
default: () => index_default,
|
|
26
28
|
verifyWebhookSignature: () => verifyWebhookSignature,
|
|
27
29
|
verifyWebhookSignatureAsync: () => verifyWebhookSignatureAsync
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
var SDK_VERSION = "0.3.0";
|
|
30
33
|
var OsmtalkError = class extends Error {
|
|
31
34
|
status;
|
|
32
35
|
body;
|
|
33
|
-
|
|
36
|
+
/** Number of retry attempts the SDK made before giving up. */
|
|
37
|
+
retryAttempts;
|
|
38
|
+
constructor(status, body, retryAttempts = 0) {
|
|
34
39
|
super(body?.error || body?.message || `osmTalk API error: ${status}`);
|
|
35
40
|
this.name = "OsmtalkError";
|
|
36
41
|
this.status = status;
|
|
37
42
|
this.body = body;
|
|
43
|
+
this.retryAttempts = retryAttempts;
|
|
38
44
|
}
|
|
45
|
+
/** True for status codes the server might recover from on retry. */
|
|
46
|
+
get isRetryable() {
|
|
47
|
+
return this.status === 408 || this.status === 429 || this.status >= 500;
|
|
48
|
+
}
|
|
49
|
+
/** True for client mistakes (bad input, bad auth). */
|
|
50
|
+
get isClientError() {
|
|
51
|
+
return this.status >= 400 && this.status < 500;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function runtimeTag() {
|
|
55
|
+
const g = globalThis;
|
|
56
|
+
if (g.Deno?.version?.deno) return `deno/${g.Deno.version.deno}`;
|
|
57
|
+
if (g.Bun?.version) return `bun/${g.Bun.version}`;
|
|
58
|
+
if (g.process?.versions?.node) return `node/${g.process.versions.node}`;
|
|
59
|
+
if (typeof navigator !== "undefined" && navigator.userAgent) return "browser";
|
|
60
|
+
return "unknown";
|
|
61
|
+
}
|
|
62
|
+
var STATUS_TEXT = {
|
|
63
|
+
408: "Request Timeout",
|
|
64
|
+
429: "Too Many Requests",
|
|
65
|
+
500: "Internal Server Error",
|
|
66
|
+
502: "Bad Gateway",
|
|
67
|
+
503: "Service Unavailable",
|
|
68
|
+
504: "Gateway Timeout"
|
|
39
69
|
};
|
|
40
|
-
var
|
|
70
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
71
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
72
|
+
var HttpClient = class _HttpClient {
|
|
41
73
|
baseUrl;
|
|
42
74
|
apiKey;
|
|
43
75
|
timeoutMs;
|
|
76
|
+
maxRetries;
|
|
77
|
+
retryInitialDelayMs;
|
|
44
78
|
fetchImpl;
|
|
79
|
+
defaultHeaders;
|
|
80
|
+
organizationId;
|
|
81
|
+
userAgent;
|
|
45
82
|
constructor(opts) {
|
|
46
83
|
this.apiKey = opts.apiKey;
|
|
47
84
|
this.baseUrl = (opts.baseUrl ?? "https://api.osmtalk.com").replace(/\/$/, "");
|
|
48
85
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
86
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
|
|
87
|
+
this.retryInitialDelayMs = Math.max(0, opts.retryInitialDelayMs ?? 250);
|
|
88
|
+
this.defaultHeaders = opts.defaultHeaders ?? {};
|
|
89
|
+
this.organizationId = opts.organizationId;
|
|
90
|
+
this.userAgent = `@osmapi/osmtalk-sdk/${SDK_VERSION} ${runtimeTag()}`;
|
|
49
91
|
this.fetchImpl = opts.fetch ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : void 0);
|
|
50
|
-
if (!this.fetchImpl)
|
|
92
|
+
if (!this.fetchImpl) {
|
|
93
|
+
throw new Error("No fetch implementation available \u2014 provide one in OsmtalkOptions.fetch");
|
|
94
|
+
}
|
|
51
95
|
if (!opts.apiKey) throw new Error("apiKey is required");
|
|
52
96
|
}
|
|
53
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Combine the caller's AbortSignal with the SDK's per-request timeout
|
|
99
|
+
* signal so EITHER firing cancels the fetch. We can't use
|
|
100
|
+
* `AbortSignal.any` (Node 18 doesn't have it), so we wire it manually.
|
|
101
|
+
*/
|
|
102
|
+
buildAbortSignal(externalSignal, timeoutMs) {
|
|
54
103
|
const ctrl = new AbortController();
|
|
55
|
-
const
|
|
104
|
+
const onAbort = () => ctrl.abort();
|
|
105
|
+
if (externalSignal) {
|
|
106
|
+
if (externalSignal.aborted) ctrl.abort();
|
|
107
|
+
else externalSignal.addEventListener("abort", onAbort, { once: true });
|
|
108
|
+
}
|
|
109
|
+
const timer = timeoutMs > 0 ? setTimeout(() => ctrl.abort(new Error("Request timed out")), timeoutMs) : null;
|
|
110
|
+
return {
|
|
111
|
+
signal: ctrl.signal,
|
|
112
|
+
cancel: () => {
|
|
113
|
+
if (timer) clearTimeout(timer);
|
|
114
|
+
if (externalSignal) externalSignal.removeEventListener("abort", onAbort);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Parse the body once for both success and error paths. */
|
|
119
|
+
static async parseBody(res) {
|
|
120
|
+
const text = await res.text();
|
|
121
|
+
if (!text) return null;
|
|
122
|
+
try {
|
|
123
|
+
return JSON.parse(text);
|
|
124
|
+
} catch {
|
|
125
|
+
return text;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** How long to wait before the next retry. Honors Retry-After. */
|
|
129
|
+
retryDelay(attempt, res) {
|
|
130
|
+
if (res) {
|
|
131
|
+
const retryAfter = res.headers.get("retry-after");
|
|
132
|
+
if (retryAfter) {
|
|
133
|
+
const secs = Number(retryAfter);
|
|
134
|
+
if (Number.isFinite(secs) && secs >= 0) return Math.min(secs * 1e3, 3e4);
|
|
135
|
+
const when = Date.parse(retryAfter);
|
|
136
|
+
if (!Number.isNaN(when)) return Math.max(0, Math.min(when - Date.now(), 3e4));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const base = this.retryInitialDelayMs * 2 ** attempt;
|
|
140
|
+
const jitter = Math.floor(Math.random() * (base / 4));
|
|
141
|
+
return Math.min(base + jitter, 3e4);
|
|
142
|
+
}
|
|
143
|
+
async request(method, path, body, extraHeaders, idempotencyKey, options) {
|
|
144
|
+
const reqOrgId = options?.organizationId ?? this.organizationId;
|
|
145
|
+
const reqTimeout = options?.timeoutMs ?? this.timeoutMs;
|
|
56
146
|
const headers = {
|
|
57
147
|
Authorization: `Bearer ${this.apiKey}`,
|
|
148
|
+
"User-Agent": this.userAgent,
|
|
149
|
+
Accept: "application/json",
|
|
58
150
|
...body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
59
151
|
...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {},
|
|
152
|
+
...reqOrgId ? { "X-Organization-Id": reqOrgId } : {},
|
|
153
|
+
...this.defaultHeaders,
|
|
60
154
|
...extraHeaders ?? {}
|
|
61
155
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
clearTimeout(timer);
|
|
72
|
-
}
|
|
73
|
-
const text = await res.text();
|
|
74
|
-
const parsed = text ? (() => {
|
|
156
|
+
const serializedBody = body === void 0 ? void 0 : typeof body === "string" ? body : JSON.stringify(body);
|
|
157
|
+
const canRetryMutation = SAFE_METHODS.has(method.toUpperCase()) || Boolean(idempotencyKey);
|
|
158
|
+
const url = `${this.baseUrl}${path}`;
|
|
159
|
+
let lastErr;
|
|
160
|
+
let lastRes = null;
|
|
161
|
+
const maxAttempts = this.maxRetries + 1;
|
|
162
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
163
|
+
const { signal, cancel } = this.buildAbortSignal(options?.signal, reqTimeout);
|
|
164
|
+
let res = null;
|
|
75
165
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
166
|
+
res = await this.fetchImpl(url, {
|
|
167
|
+
method,
|
|
168
|
+
headers,
|
|
169
|
+
body: serializedBody,
|
|
170
|
+
signal
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
lastErr = err;
|
|
174
|
+
cancel();
|
|
175
|
+
if (options?.signal?.aborted) throw err;
|
|
176
|
+
if (attempt < maxAttempts - 1 && canRetryMutation) {
|
|
177
|
+
await sleep(this.retryDelay(attempt, null));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
cancel();
|
|
183
|
+
if (res.ok) {
|
|
184
|
+
return await _HttpClient.parseBody(res);
|
|
79
185
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
186
|
+
const retryable = RETRYABLE_STATUSES.has(res.status);
|
|
187
|
+
if (retryable && canRetryMutation && attempt < maxAttempts - 1) {
|
|
188
|
+
try {
|
|
189
|
+
await res.text();
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
await sleep(this.retryDelay(attempt, res));
|
|
193
|
+
lastRes = res;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const parsed = await _HttpClient.parseBody(res);
|
|
197
|
+
throw new OsmtalkError(
|
|
198
|
+
res.status,
|
|
199
|
+
parsed ?? { error: STATUS_TEXT[res.status] ?? `HTTP ${res.status}` },
|
|
200
|
+
attempt
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (lastRes) {
|
|
204
|
+
const parsed = await _HttpClient.parseBody(lastRes);
|
|
205
|
+
throw new OsmtalkError(
|
|
206
|
+
lastRes.status,
|
|
207
|
+
parsed ?? { error: STATUS_TEXT[lastRes.status] ?? "Server error" },
|
|
208
|
+
maxAttempts - 1
|
|
209
|
+
);
|
|
83
210
|
}
|
|
84
|
-
|
|
211
|
+
throw lastErr ?? new Error("osmTalk SDK: request failed without details");
|
|
85
212
|
}
|
|
86
213
|
};
|
|
214
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
87
215
|
var AgentsResource = class {
|
|
88
216
|
constructor(http) {
|
|
89
217
|
this.http = http;
|
|
@@ -139,20 +267,22 @@ var AgentsResource = class {
|
|
|
139
267
|
);
|
|
140
268
|
}
|
|
141
269
|
};
|
|
270
|
+
var TERMINAL_CALL_STATUSES = ["completed", "failed", "ended", "cancelled"];
|
|
142
271
|
var CallsResource = class {
|
|
143
272
|
constructor(http) {
|
|
144
273
|
this.http = http;
|
|
145
274
|
}
|
|
146
275
|
http;
|
|
147
|
-
list() {
|
|
148
|
-
return this.http.request("GET", "/api/calls");
|
|
276
|
+
list(opts) {
|
|
277
|
+
return this.http.request("GET", "/api/calls", void 0, void 0, void 0, opts);
|
|
149
278
|
}
|
|
150
|
-
get(id) {
|
|
151
|
-
return this.http.request("GET", `/api/calls/${id}
|
|
279
|
+
get(id, opts) {
|
|
280
|
+
return this.http.request("GET", `/api/calls/${id}`, void 0, void 0, void 0, opts);
|
|
152
281
|
}
|
|
153
282
|
/**
|
|
154
|
-
* Place an outbound call.
|
|
155
|
-
*
|
|
283
|
+
* Place an outbound call. Pass `idempotencyKey` so a retry within 24h
|
|
284
|
+
* returns the same response instead of placing a duplicate call —
|
|
285
|
+
* required if you want this call to be retried on transient failures.
|
|
156
286
|
*/
|
|
157
287
|
outbound(input, opts) {
|
|
158
288
|
return this.http.request(
|
|
@@ -160,14 +290,61 @@ var CallsResource = class {
|
|
|
160
290
|
"/api/calls/outbound",
|
|
161
291
|
input,
|
|
162
292
|
void 0,
|
|
163
|
-
opts?.idempotencyKey
|
|
293
|
+
opts?.idempotencyKey,
|
|
294
|
+
opts
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
end(id, opts) {
|
|
298
|
+
return this.http.request(
|
|
299
|
+
"POST",
|
|
300
|
+
`/api/calls/${id}/end`,
|
|
301
|
+
void 0,
|
|
302
|
+
void 0,
|
|
303
|
+
opts?.idempotencyKey,
|
|
304
|
+
opts
|
|
164
305
|
);
|
|
165
306
|
}
|
|
166
|
-
|
|
167
|
-
return this.http.request(
|
|
307
|
+
transfer(id, destination, summary, opts) {
|
|
308
|
+
return this.http.request(
|
|
309
|
+
"POST",
|
|
310
|
+
`/api/calls/${id}/transfer`,
|
|
311
|
+
{ destination, summary },
|
|
312
|
+
void 0,
|
|
313
|
+
opts?.idempotencyKey,
|
|
314
|
+
opts
|
|
315
|
+
);
|
|
168
316
|
}
|
|
169
|
-
|
|
170
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Poll a call until it reaches a terminal status (`completed`,
|
|
319
|
+
* `failed`, `ended`, `cancelled`) and return the final record.
|
|
320
|
+
*
|
|
321
|
+
* Saves consumers from writing the same loop in every script. Use
|
|
322
|
+
* webhooks instead in production — this is fine for scripts, demos,
|
|
323
|
+
* and one-off jobs but consumes API quota on every poll.
|
|
324
|
+
*
|
|
325
|
+
* @param opts.pollIntervalMs - default 5s
|
|
326
|
+
* @param opts.timeoutMs - default 30min (rejects with
|
|
327
|
+
* `OsmtalkError` 408 on timeout)
|
|
328
|
+
* @param opts.signal - abort externally
|
|
329
|
+
*/
|
|
330
|
+
async waitUntilEnded(id, opts) {
|
|
331
|
+
const pollInterval = Math.max(1e3, opts?.pollIntervalMs ?? 5e3);
|
|
332
|
+
const totalTimeout = opts?.timeoutMs ?? 30 * 60 * 1e3;
|
|
333
|
+
const deadline = Date.now() + totalTimeout;
|
|
334
|
+
const terminal = new Set(TERMINAL_CALL_STATUSES);
|
|
335
|
+
while (true) {
|
|
336
|
+
if (opts?.signal?.aborted) {
|
|
337
|
+
throw new OsmtalkError(0, { error: "Aborted by caller" });
|
|
338
|
+
}
|
|
339
|
+
const call = await this.get(id, { signal: opts?.signal });
|
|
340
|
+
if (terminal.has(call.status)) return call;
|
|
341
|
+
if (Date.now() >= deadline) {
|
|
342
|
+
throw new OsmtalkError(408, {
|
|
343
|
+
error: `waitUntilEnded: call ${id} did not reach a terminal state within ${totalTimeout}ms (last status: ${call.status})`
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
await sleep(Math.min(pollInterval, Math.max(0, deadline - Date.now())));
|
|
347
|
+
}
|
|
171
348
|
}
|
|
172
349
|
};
|
|
173
350
|
var PlatformResource = class {
|
|
@@ -445,6 +622,8 @@ var index_default = Osmtalk;
|
|
|
445
622
|
0 && (module.exports = {
|
|
446
623
|
Osmtalk,
|
|
447
624
|
OsmtalkError,
|
|
625
|
+
SDK_VERSION,
|
|
626
|
+
TERMINAL_CALL_STATUSES,
|
|
448
627
|
verifyWebhookSignature,
|
|
449
628
|
verifyWebhookSignatureAsync
|
|
450
629
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,61 +1,187 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
var SDK_VERSION = "0.3.0";
|
|
2
3
|
var OsmtalkError = class extends Error {
|
|
3
4
|
status;
|
|
4
5
|
body;
|
|
5
|
-
|
|
6
|
+
/** Number of retry attempts the SDK made before giving up. */
|
|
7
|
+
retryAttempts;
|
|
8
|
+
constructor(status, body, retryAttempts = 0) {
|
|
6
9
|
super(body?.error || body?.message || `osmTalk API error: ${status}`);
|
|
7
10
|
this.name = "OsmtalkError";
|
|
8
11
|
this.status = status;
|
|
9
12
|
this.body = body;
|
|
13
|
+
this.retryAttempts = retryAttempts;
|
|
10
14
|
}
|
|
15
|
+
/** True for status codes the server might recover from on retry. */
|
|
16
|
+
get isRetryable() {
|
|
17
|
+
return this.status === 408 || this.status === 429 || this.status >= 500;
|
|
18
|
+
}
|
|
19
|
+
/** True for client mistakes (bad input, bad auth). */
|
|
20
|
+
get isClientError() {
|
|
21
|
+
return this.status >= 400 && this.status < 500;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function runtimeTag() {
|
|
25
|
+
const g = globalThis;
|
|
26
|
+
if (g.Deno?.version?.deno) return `deno/${g.Deno.version.deno}`;
|
|
27
|
+
if (g.Bun?.version) return `bun/${g.Bun.version}`;
|
|
28
|
+
if (g.process?.versions?.node) return `node/${g.process.versions.node}`;
|
|
29
|
+
if (typeof navigator !== "undefined" && navigator.userAgent) return "browser";
|
|
30
|
+
return "unknown";
|
|
31
|
+
}
|
|
32
|
+
var STATUS_TEXT = {
|
|
33
|
+
408: "Request Timeout",
|
|
34
|
+
429: "Too Many Requests",
|
|
35
|
+
500: "Internal Server Error",
|
|
36
|
+
502: "Bad Gateway",
|
|
37
|
+
503: "Service Unavailable",
|
|
38
|
+
504: "Gateway Timeout"
|
|
11
39
|
};
|
|
12
|
-
var
|
|
40
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
41
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
42
|
+
var HttpClient = class _HttpClient {
|
|
13
43
|
baseUrl;
|
|
14
44
|
apiKey;
|
|
15
45
|
timeoutMs;
|
|
46
|
+
maxRetries;
|
|
47
|
+
retryInitialDelayMs;
|
|
16
48
|
fetchImpl;
|
|
49
|
+
defaultHeaders;
|
|
50
|
+
organizationId;
|
|
51
|
+
userAgent;
|
|
17
52
|
constructor(opts) {
|
|
18
53
|
this.apiKey = opts.apiKey;
|
|
19
54
|
this.baseUrl = (opts.baseUrl ?? "https://api.osmtalk.com").replace(/\/$/, "");
|
|
20
55
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
56
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
|
|
57
|
+
this.retryInitialDelayMs = Math.max(0, opts.retryInitialDelayMs ?? 250);
|
|
58
|
+
this.defaultHeaders = opts.defaultHeaders ?? {};
|
|
59
|
+
this.organizationId = opts.organizationId;
|
|
60
|
+
this.userAgent = `@osmapi/osmtalk-sdk/${SDK_VERSION} ${runtimeTag()}`;
|
|
21
61
|
this.fetchImpl = opts.fetch ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : void 0);
|
|
22
|
-
if (!this.fetchImpl)
|
|
62
|
+
if (!this.fetchImpl) {
|
|
63
|
+
throw new Error("No fetch implementation available \u2014 provide one in OsmtalkOptions.fetch");
|
|
64
|
+
}
|
|
23
65
|
if (!opts.apiKey) throw new Error("apiKey is required");
|
|
24
66
|
}
|
|
25
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Combine the caller's AbortSignal with the SDK's per-request timeout
|
|
69
|
+
* signal so EITHER firing cancels the fetch. We can't use
|
|
70
|
+
* `AbortSignal.any` (Node 18 doesn't have it), so we wire it manually.
|
|
71
|
+
*/
|
|
72
|
+
buildAbortSignal(externalSignal, timeoutMs) {
|
|
26
73
|
const ctrl = new AbortController();
|
|
27
|
-
const
|
|
74
|
+
const onAbort = () => ctrl.abort();
|
|
75
|
+
if (externalSignal) {
|
|
76
|
+
if (externalSignal.aborted) ctrl.abort();
|
|
77
|
+
else externalSignal.addEventListener("abort", onAbort, { once: true });
|
|
78
|
+
}
|
|
79
|
+
const timer = timeoutMs > 0 ? setTimeout(() => ctrl.abort(new Error("Request timed out")), timeoutMs) : null;
|
|
80
|
+
return {
|
|
81
|
+
signal: ctrl.signal,
|
|
82
|
+
cancel: () => {
|
|
83
|
+
if (timer) clearTimeout(timer);
|
|
84
|
+
if (externalSignal) externalSignal.removeEventListener("abort", onAbort);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Parse the body once for both success and error paths. */
|
|
89
|
+
static async parseBody(res) {
|
|
90
|
+
const text = await res.text();
|
|
91
|
+
if (!text) return null;
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(text);
|
|
94
|
+
} catch {
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** How long to wait before the next retry. Honors Retry-After. */
|
|
99
|
+
retryDelay(attempt, res) {
|
|
100
|
+
if (res) {
|
|
101
|
+
const retryAfter = res.headers.get("retry-after");
|
|
102
|
+
if (retryAfter) {
|
|
103
|
+
const secs = Number(retryAfter);
|
|
104
|
+
if (Number.isFinite(secs) && secs >= 0) return Math.min(secs * 1e3, 3e4);
|
|
105
|
+
const when = Date.parse(retryAfter);
|
|
106
|
+
if (!Number.isNaN(when)) return Math.max(0, Math.min(when - Date.now(), 3e4));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const base = this.retryInitialDelayMs * 2 ** attempt;
|
|
110
|
+
const jitter = Math.floor(Math.random() * (base / 4));
|
|
111
|
+
return Math.min(base + jitter, 3e4);
|
|
112
|
+
}
|
|
113
|
+
async request(method, path, body, extraHeaders, idempotencyKey, options) {
|
|
114
|
+
const reqOrgId = options?.organizationId ?? this.organizationId;
|
|
115
|
+
const reqTimeout = options?.timeoutMs ?? this.timeoutMs;
|
|
28
116
|
const headers = {
|
|
29
117
|
Authorization: `Bearer ${this.apiKey}`,
|
|
118
|
+
"User-Agent": this.userAgent,
|
|
119
|
+
Accept: "application/json",
|
|
30
120
|
...body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
31
121
|
...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {},
|
|
122
|
+
...reqOrgId ? { "X-Organization-Id": reqOrgId } : {},
|
|
123
|
+
...this.defaultHeaders,
|
|
32
124
|
...extraHeaders ?? {}
|
|
33
125
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
clearTimeout(timer);
|
|
44
|
-
}
|
|
45
|
-
const text = await res.text();
|
|
46
|
-
const parsed = text ? (() => {
|
|
126
|
+
const serializedBody = body === void 0 ? void 0 : typeof body === "string" ? body : JSON.stringify(body);
|
|
127
|
+
const canRetryMutation = SAFE_METHODS.has(method.toUpperCase()) || Boolean(idempotencyKey);
|
|
128
|
+
const url = `${this.baseUrl}${path}`;
|
|
129
|
+
let lastErr;
|
|
130
|
+
let lastRes = null;
|
|
131
|
+
const maxAttempts = this.maxRetries + 1;
|
|
132
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
133
|
+
const { signal, cancel } = this.buildAbortSignal(options?.signal, reqTimeout);
|
|
134
|
+
let res = null;
|
|
47
135
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
136
|
+
res = await this.fetchImpl(url, {
|
|
137
|
+
method,
|
|
138
|
+
headers,
|
|
139
|
+
body: serializedBody,
|
|
140
|
+
signal
|
|
141
|
+
});
|
|
142
|
+
} catch (err) {
|
|
143
|
+
lastErr = err;
|
|
144
|
+
cancel();
|
|
145
|
+
if (options?.signal?.aborted) throw err;
|
|
146
|
+
if (attempt < maxAttempts - 1 && canRetryMutation) {
|
|
147
|
+
await sleep(this.retryDelay(attempt, null));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
cancel();
|
|
153
|
+
if (res.ok) {
|
|
154
|
+
return await _HttpClient.parseBody(res);
|
|
51
155
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
156
|
+
const retryable = RETRYABLE_STATUSES.has(res.status);
|
|
157
|
+
if (retryable && canRetryMutation && attempt < maxAttempts - 1) {
|
|
158
|
+
try {
|
|
159
|
+
await res.text();
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
await sleep(this.retryDelay(attempt, res));
|
|
163
|
+
lastRes = res;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const parsed = await _HttpClient.parseBody(res);
|
|
167
|
+
throw new OsmtalkError(
|
|
168
|
+
res.status,
|
|
169
|
+
parsed ?? { error: STATUS_TEXT[res.status] ?? `HTTP ${res.status}` },
|
|
170
|
+
attempt
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (lastRes) {
|
|
174
|
+
const parsed = await _HttpClient.parseBody(lastRes);
|
|
175
|
+
throw new OsmtalkError(
|
|
176
|
+
lastRes.status,
|
|
177
|
+
parsed ?? { error: STATUS_TEXT[lastRes.status] ?? "Server error" },
|
|
178
|
+
maxAttempts - 1
|
|
179
|
+
);
|
|
55
180
|
}
|
|
56
|
-
|
|
181
|
+
throw lastErr ?? new Error("osmTalk SDK: request failed without details");
|
|
57
182
|
}
|
|
58
183
|
};
|
|
184
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
59
185
|
var AgentsResource = class {
|
|
60
186
|
constructor(http) {
|
|
61
187
|
this.http = http;
|
|
@@ -111,20 +237,22 @@ var AgentsResource = class {
|
|
|
111
237
|
);
|
|
112
238
|
}
|
|
113
239
|
};
|
|
240
|
+
var TERMINAL_CALL_STATUSES = ["completed", "failed", "ended", "cancelled"];
|
|
114
241
|
var CallsResource = class {
|
|
115
242
|
constructor(http) {
|
|
116
243
|
this.http = http;
|
|
117
244
|
}
|
|
118
245
|
http;
|
|
119
|
-
list() {
|
|
120
|
-
return this.http.request("GET", "/api/calls");
|
|
246
|
+
list(opts) {
|
|
247
|
+
return this.http.request("GET", "/api/calls", void 0, void 0, void 0, opts);
|
|
121
248
|
}
|
|
122
|
-
get(id) {
|
|
123
|
-
return this.http.request("GET", `/api/calls/${id}
|
|
249
|
+
get(id, opts) {
|
|
250
|
+
return this.http.request("GET", `/api/calls/${id}`, void 0, void 0, void 0, opts);
|
|
124
251
|
}
|
|
125
252
|
/**
|
|
126
|
-
* Place an outbound call.
|
|
127
|
-
*
|
|
253
|
+
* Place an outbound call. Pass `idempotencyKey` so a retry within 24h
|
|
254
|
+
* returns the same response instead of placing a duplicate call —
|
|
255
|
+
* required if you want this call to be retried on transient failures.
|
|
128
256
|
*/
|
|
129
257
|
outbound(input, opts) {
|
|
130
258
|
return this.http.request(
|
|
@@ -132,14 +260,61 @@ var CallsResource = class {
|
|
|
132
260
|
"/api/calls/outbound",
|
|
133
261
|
input,
|
|
134
262
|
void 0,
|
|
135
|
-
opts?.idempotencyKey
|
|
263
|
+
opts?.idempotencyKey,
|
|
264
|
+
opts
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
end(id, opts) {
|
|
268
|
+
return this.http.request(
|
|
269
|
+
"POST",
|
|
270
|
+
`/api/calls/${id}/end`,
|
|
271
|
+
void 0,
|
|
272
|
+
void 0,
|
|
273
|
+
opts?.idempotencyKey,
|
|
274
|
+
opts
|
|
136
275
|
);
|
|
137
276
|
}
|
|
138
|
-
|
|
139
|
-
return this.http.request(
|
|
277
|
+
transfer(id, destination, summary, opts) {
|
|
278
|
+
return this.http.request(
|
|
279
|
+
"POST",
|
|
280
|
+
`/api/calls/${id}/transfer`,
|
|
281
|
+
{ destination, summary },
|
|
282
|
+
void 0,
|
|
283
|
+
opts?.idempotencyKey,
|
|
284
|
+
opts
|
|
285
|
+
);
|
|
140
286
|
}
|
|
141
|
-
|
|
142
|
-
|
|
287
|
+
/**
|
|
288
|
+
* Poll a call until it reaches a terminal status (`completed`,
|
|
289
|
+
* `failed`, `ended`, `cancelled`) and return the final record.
|
|
290
|
+
*
|
|
291
|
+
* Saves consumers from writing the same loop in every script. Use
|
|
292
|
+
* webhooks instead in production — this is fine for scripts, demos,
|
|
293
|
+
* and one-off jobs but consumes API quota on every poll.
|
|
294
|
+
*
|
|
295
|
+
* @param opts.pollIntervalMs - default 5s
|
|
296
|
+
* @param opts.timeoutMs - default 30min (rejects with
|
|
297
|
+
* `OsmtalkError` 408 on timeout)
|
|
298
|
+
* @param opts.signal - abort externally
|
|
299
|
+
*/
|
|
300
|
+
async waitUntilEnded(id, opts) {
|
|
301
|
+
const pollInterval = Math.max(1e3, opts?.pollIntervalMs ?? 5e3);
|
|
302
|
+
const totalTimeout = opts?.timeoutMs ?? 30 * 60 * 1e3;
|
|
303
|
+
const deadline = Date.now() + totalTimeout;
|
|
304
|
+
const terminal = new Set(TERMINAL_CALL_STATUSES);
|
|
305
|
+
while (true) {
|
|
306
|
+
if (opts?.signal?.aborted) {
|
|
307
|
+
throw new OsmtalkError(0, { error: "Aborted by caller" });
|
|
308
|
+
}
|
|
309
|
+
const call = await this.get(id, { signal: opts?.signal });
|
|
310
|
+
if (terminal.has(call.status)) return call;
|
|
311
|
+
if (Date.now() >= deadline) {
|
|
312
|
+
throw new OsmtalkError(408, {
|
|
313
|
+
error: `waitUntilEnded: call ${id} did not reach a terminal state within ${totalTimeout}ms (last status: ${call.status})`
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
await sleep(Math.min(pollInterval, Math.max(0, deadline - Date.now())));
|
|
317
|
+
}
|
|
143
318
|
}
|
|
144
319
|
};
|
|
145
320
|
var PlatformResource = class {
|
|
@@ -416,6 +591,8 @@ var index_default = Osmtalk;
|
|
|
416
591
|
export {
|
|
417
592
|
Osmtalk,
|
|
418
593
|
OsmtalkError,
|
|
594
|
+
SDK_VERSION,
|
|
595
|
+
TERMINAL_CALL_STATUSES,
|
|
419
596
|
index_default as default,
|
|
420
597
|
verifyWebhookSignature,
|
|
421
598
|
verifyWebhookSignatureAsync
|