@dialgem/sdk 0.1.0-beta.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/CHANGELOG.md +16 -0
- package/LICENSE +202 -0
- package/README.md +257 -0
- package/dist/index.cjs +712 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2240 -0
- package/dist/index.d.ts +2240 -0
- package/dist/index.js +688 -0
- package/dist/index.js.map +1 -0
- package/examples/export-calls.ts +37 -0
- package/examples/outbound-call.ts +36 -0
- package/examples/provision-agent.ts +61 -0
- package/examples/twilio-register.ts +44 -0
- package/examples/webhook-express.ts +39 -0
- package/examples/webhook-fastify.ts +31 -0
- package/examples/webhook-nextjs-route.ts +24 -0
- package/package.json +80 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/errors.ts
|
|
4
|
+
var DialgemError = class extends Error {
|
|
5
|
+
name = "DialgemError";
|
|
6
|
+
};
|
|
7
|
+
var APIError = class extends DialgemError {
|
|
8
|
+
name = "APIError";
|
|
9
|
+
/** HTTP status code. */
|
|
10
|
+
status;
|
|
11
|
+
/** The service's `err_code`, e.g. `flow_invalid` or `HTTP_404`. Undefined if the body was not the envelope. */
|
|
12
|
+
errCode;
|
|
13
|
+
/** The service's `request_id`. Quote it when contacting support. */
|
|
14
|
+
requestId;
|
|
15
|
+
/** Field/graph diagnostics, present on validation failures. */
|
|
16
|
+
issues;
|
|
17
|
+
/** Parsed JSON body, or the raw text if the body was not JSON. */
|
|
18
|
+
body;
|
|
19
|
+
headers;
|
|
20
|
+
constructor(status, body, message, headers) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.body = body;
|
|
24
|
+
this.headers = headers;
|
|
25
|
+
const envelope = isErrorBody(body) ? body : void 0;
|
|
26
|
+
this.errCode = envelope?.err_code;
|
|
27
|
+
this.requestId = envelope?.request_id ?? headers.get("x-request-id") ?? void 0;
|
|
28
|
+
this.issues = envelope?.issues ?? [];
|
|
29
|
+
}
|
|
30
|
+
/** Build the subclass matching `status`. */
|
|
31
|
+
static from(status, body, statusText, headers) {
|
|
32
|
+
const envelope = isErrorBody(body) ? body : void 0;
|
|
33
|
+
const detail = envelope?.err_msg || (typeof body === "string" && body.trim() ? body.trim().slice(0, 500) : "");
|
|
34
|
+
const message = detail ? `${status} ${detail}` : `${status} ${statusText || "status code (no body)"}`;
|
|
35
|
+
const Ctor = errorClassFor(status);
|
|
36
|
+
return new Ctor(status, body, message, headers);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var BadRequestError = class extends APIError {
|
|
40
|
+
name = "BadRequestError";
|
|
41
|
+
};
|
|
42
|
+
var AuthenticationError = class extends APIError {
|
|
43
|
+
name = "AuthenticationError";
|
|
44
|
+
};
|
|
45
|
+
var PermissionDeniedError = class extends APIError {
|
|
46
|
+
name = "PermissionDeniedError";
|
|
47
|
+
};
|
|
48
|
+
var NotFoundError = class extends APIError {
|
|
49
|
+
name = "NotFoundError";
|
|
50
|
+
};
|
|
51
|
+
var ConflictError = class extends APIError {
|
|
52
|
+
name = "ConflictError";
|
|
53
|
+
};
|
|
54
|
+
var UnprocessableEntityError = class extends APIError {
|
|
55
|
+
name = "UnprocessableEntityError";
|
|
56
|
+
};
|
|
57
|
+
var RateLimitError = class extends APIError {
|
|
58
|
+
name = "RateLimitError";
|
|
59
|
+
};
|
|
60
|
+
var InternalServerError = class extends APIError {
|
|
61
|
+
name = "InternalServerError";
|
|
62
|
+
};
|
|
63
|
+
var APIConnectionError = class extends DialgemError {
|
|
64
|
+
name = "APIConnectionError";
|
|
65
|
+
constructor(message = "Connection error.", options) {
|
|
66
|
+
super(message, options);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var APIUserAbortError = class extends DialgemError {
|
|
70
|
+
name = "APIUserAbortError";
|
|
71
|
+
constructor(message = "Request was aborted.") {
|
|
72
|
+
super(message);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var APIConnectionTimeoutError = class extends APIConnectionError {
|
|
76
|
+
name = "APIConnectionTimeoutError";
|
|
77
|
+
constructor(message = "Request timed out.") {
|
|
78
|
+
super(message);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var WebhookVerificationError = class extends DialgemError {
|
|
82
|
+
name = "WebhookVerificationError";
|
|
83
|
+
};
|
|
84
|
+
function errorClassFor(status) {
|
|
85
|
+
switch (status) {
|
|
86
|
+
case 400:
|
|
87
|
+
return BadRequestError;
|
|
88
|
+
case 401:
|
|
89
|
+
return AuthenticationError;
|
|
90
|
+
case 403:
|
|
91
|
+
return PermissionDeniedError;
|
|
92
|
+
case 404:
|
|
93
|
+
return NotFoundError;
|
|
94
|
+
case 409:
|
|
95
|
+
return ConflictError;
|
|
96
|
+
case 422:
|
|
97
|
+
return UnprocessableEntityError;
|
|
98
|
+
case 429:
|
|
99
|
+
return RateLimitError;
|
|
100
|
+
default:
|
|
101
|
+
return status >= 500 ? InternalServerError : APIError;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function isErrorBody(body) {
|
|
105
|
+
return typeof body === "object" && body !== null && typeof body.err_code === "string";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/version.ts
|
|
109
|
+
var VERSION = "0.1.0-beta.0";
|
|
110
|
+
|
|
111
|
+
// src/core/http.ts
|
|
112
|
+
var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "PATCH", "DELETE"]);
|
|
113
|
+
var MAX_RETRY_DELAY_MS = 6e4;
|
|
114
|
+
var HttpClient = class {
|
|
115
|
+
baseURL;
|
|
116
|
+
#apiKey;
|
|
117
|
+
#timeout;
|
|
118
|
+
#maxRetries;
|
|
119
|
+
#fetch;
|
|
120
|
+
#defaultHeaders;
|
|
121
|
+
constructor(options) {
|
|
122
|
+
this.baseURL = options.baseURL.replace(/\/+$/, "");
|
|
123
|
+
this.#apiKey = options.apiKey;
|
|
124
|
+
this.#timeout = options.timeout;
|
|
125
|
+
this.#maxRetries = options.maxRetries;
|
|
126
|
+
this.#fetch = options.fetch;
|
|
127
|
+
this.#defaultHeaders = options.defaultHeaders;
|
|
128
|
+
}
|
|
129
|
+
get(path2, query, options) {
|
|
130
|
+
return this.request("GET", path2, { query, options });
|
|
131
|
+
}
|
|
132
|
+
post(path2, body, options) {
|
|
133
|
+
return this.request("POST", path2, { body, options });
|
|
134
|
+
}
|
|
135
|
+
patch(path2, body, options) {
|
|
136
|
+
return this.request("PATCH", path2, { body, options });
|
|
137
|
+
}
|
|
138
|
+
delete(path2, options) {
|
|
139
|
+
return this.request("DELETE", path2, { options });
|
|
140
|
+
}
|
|
141
|
+
async request(method, path2, { query, body, options = {} }) {
|
|
142
|
+
const url = this.buildURL(path2, query);
|
|
143
|
+
const headers = this.buildHeaders(body !== void 0, options.headers);
|
|
144
|
+
const payload = body === void 0 ? void 0 : JSON.stringify(body);
|
|
145
|
+
const timeout = options.timeout ?? this.#timeout;
|
|
146
|
+
const maxRetries = options.maxRetries ?? (RETRYABLE_METHODS.has(method) ? this.#maxRetries : 0);
|
|
147
|
+
assertTimeout(timeout);
|
|
148
|
+
assertMaxRetries(maxRetries);
|
|
149
|
+
for (let attempt = 0; ; attempt++) {
|
|
150
|
+
const retriesLeft = maxRetries - attempt;
|
|
151
|
+
if (options.signal?.aborted) throw new APIUserAbortError();
|
|
152
|
+
const outcome = await this.#attempt(url, method, headers, payload, timeout, options.signal);
|
|
153
|
+
if (outcome.kind === "response") {
|
|
154
|
+
const { status, statusText, headers: responseHeaders, text } = outcome;
|
|
155
|
+
if (status >= 200 && status < 300) return parseSuccess(text, status);
|
|
156
|
+
if (retriesLeft > 0 && isRetryableStatus(status)) {
|
|
157
|
+
await sleep(retryAfterMs(responseHeaders) ?? backoffMs(attempt), options.signal);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
throw APIError.from(status, parseErrorBody(text), statusText, responseHeaders);
|
|
161
|
+
}
|
|
162
|
+
if (options.signal?.aborted) throw new APIUserAbortError();
|
|
163
|
+
if (retriesLeft > 0) {
|
|
164
|
+
await sleep(backoffMs(attempt), options.signal);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
throw outcome.error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** One HTTP attempt, with its own timeout. Never throws; timers and listeners are released before it returns. */
|
|
171
|
+
async #attempt(url, method, headers, payload, timeout, signal) {
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
let timedOut = false;
|
|
174
|
+
const timer = setTimeout(() => {
|
|
175
|
+
timedOut = true;
|
|
176
|
+
controller.abort();
|
|
177
|
+
}, timeout);
|
|
178
|
+
const onUserAbort = () => controller.abort();
|
|
179
|
+
signal?.addEventListener("abort", onUserAbort, { once: true });
|
|
180
|
+
try {
|
|
181
|
+
const init = { method, headers, signal: controller.signal, redirect: "manual" };
|
|
182
|
+
if (payload !== void 0) init.body = payload;
|
|
183
|
+
const response = await this.#fetch(url, init);
|
|
184
|
+
const text = await response.text();
|
|
185
|
+
return { kind: "response", status: response.status, statusText: response.statusText, headers: response.headers, text };
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const error = timedOut ? new APIConnectionTimeoutError(`Request timed out after ${timeout}ms.`) : new APIConnectionError(`Connection error: ${describe(err)}`, { cause: err });
|
|
188
|
+
return { kind: "failure", error };
|
|
189
|
+
} finally {
|
|
190
|
+
clearTimeout(timer);
|
|
191
|
+
signal?.removeEventListener("abort", onUserAbort);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
buildURL(path2, query) {
|
|
195
|
+
const url = `${this.baseURL}${path2.startsWith("/") ? path2 : `/${path2}`}`;
|
|
196
|
+
if (!query) return url;
|
|
197
|
+
const params = new URLSearchParams();
|
|
198
|
+
for (const [key, value] of Object.entries(query)) {
|
|
199
|
+
if (value === void 0 || value === null) continue;
|
|
200
|
+
params.append(key, String(value));
|
|
201
|
+
}
|
|
202
|
+
const qs = params.toString();
|
|
203
|
+
return qs ? `${url}?${qs}` : url;
|
|
204
|
+
}
|
|
205
|
+
buildHeaders(hasBody, extra) {
|
|
206
|
+
const merged = /* @__PURE__ */ new Map();
|
|
207
|
+
const layer = (headers) => {
|
|
208
|
+
for (const [name, value] of Object.entries(headers ?? {})) merged.set(name.toLowerCase(), [name, value]);
|
|
209
|
+
};
|
|
210
|
+
layer({
|
|
211
|
+
Accept: "application/json",
|
|
212
|
+
// `X-API-Key`, not `Authorization: Bearer`: the API only tries a
|
|
213
|
+
// Bearer value as a session JWT, so an org key sent that way is a 401.
|
|
214
|
+
"X-API-Key": this.#apiKey,
|
|
215
|
+
"User-Agent": userAgent()
|
|
216
|
+
});
|
|
217
|
+
layer(this.#defaultHeaders);
|
|
218
|
+
layer(extra);
|
|
219
|
+
if (hasBody) layer({ "Content-Type": "application/json" });
|
|
220
|
+
return Object.fromEntries(merged.values());
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
function assertTimeout(value) {
|
|
224
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
225
|
+
throw new DialgemError(`\`timeout\` must be a positive integer of milliseconds, got ${String(value)}.`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function assertMaxRetries(value) {
|
|
229
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
230
|
+
throw new DialgemError(`\`maxRetries\` must be a non-negative integer, got ${String(value)}.`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function parseSuccess(text, status) {
|
|
234
|
+
if (status === 204 || text.trim() === "") return void 0;
|
|
235
|
+
try {
|
|
236
|
+
return JSON.parse(text);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
throw new DialgemError(`Expected a JSON response (status ${status}) but could not parse it: ${describe(err)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function parseErrorBody(text) {
|
|
242
|
+
if (!text) return void 0;
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(text);
|
|
245
|
+
} catch {
|
|
246
|
+
return text;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function isRetryableStatus(status) {
|
|
250
|
+
return status === 408 || status === 429 || status >= 500;
|
|
251
|
+
}
|
|
252
|
+
function backoffMs(attempt) {
|
|
253
|
+
const base = Math.min(500 * 2 ** attempt, 8e3);
|
|
254
|
+
return base * (1 - Math.random() * 0.25);
|
|
255
|
+
}
|
|
256
|
+
function retryAfterMs(headers) {
|
|
257
|
+
const ms = Number(headers.get("retry-after-ms"));
|
|
258
|
+
if (Number.isFinite(ms) && ms > 0) return Math.min(ms, MAX_RETRY_DELAY_MS);
|
|
259
|
+
const raw = headers.get("retry-after");
|
|
260
|
+
if (!raw) return void 0;
|
|
261
|
+
const seconds = Number(raw);
|
|
262
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, MAX_RETRY_DELAY_MS);
|
|
263
|
+
const date = Date.parse(raw);
|
|
264
|
+
if (Number.isFinite(date)) return Math.min(Math.max(date - Date.now(), 0), MAX_RETRY_DELAY_MS);
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
267
|
+
function sleep(ms, signal) {
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
if (signal?.aborted) return reject(new APIUserAbortError());
|
|
270
|
+
const onAbort = () => {
|
|
271
|
+
clearTimeout(timer);
|
|
272
|
+
reject(new APIUserAbortError());
|
|
273
|
+
};
|
|
274
|
+
const timer = setTimeout(() => {
|
|
275
|
+
signal?.removeEventListener("abort", onAbort);
|
|
276
|
+
resolve();
|
|
277
|
+
}, ms);
|
|
278
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function userAgent() {
|
|
282
|
+
const proc = globalThis.process;
|
|
283
|
+
return proc?.version ? `dialgem-sdk-node/${VERSION} node/${proc.version}` : `dialgem-sdk-node/${VERSION}`;
|
|
284
|
+
}
|
|
285
|
+
function describe(err) {
|
|
286
|
+
if (err instanceof Error) {
|
|
287
|
+
const cause = err.cause;
|
|
288
|
+
return cause instanceof Error ? `${err.message} (${cause.message})` : err.message;
|
|
289
|
+
}
|
|
290
|
+
return String(err);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/resources/resource.ts
|
|
294
|
+
var APIResource = class {
|
|
295
|
+
_client;
|
|
296
|
+
constructor(client) {
|
|
297
|
+
this._client = client;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
function path(strings, ...ids) {
|
|
301
|
+
let out = strings[0] ?? "";
|
|
302
|
+
ids.forEach((id, i) => {
|
|
303
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
304
|
+
throw new DialgemError(`Expected a non-empty id in ${strings.join("{id}")}, got ${JSON.stringify(id)}.`);
|
|
305
|
+
}
|
|
306
|
+
out += encodeURIComponent(id) + (strings[i + 1] ?? "");
|
|
307
|
+
});
|
|
308
|
+
return out;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/resources/agents.ts
|
|
312
|
+
var Agents = class extends APIResource {
|
|
313
|
+
/** Tools (HTTP functions and built-ins like `end_call`) the agent can call. */
|
|
314
|
+
functions;
|
|
315
|
+
/** Text documents the agent retrieves from during the call. */
|
|
316
|
+
knowledge;
|
|
317
|
+
/** Remote MCP servers whose tools are exposed to the agent. */
|
|
318
|
+
mcpServers;
|
|
319
|
+
constructor(client) {
|
|
320
|
+
super(client);
|
|
321
|
+
this.functions = new AgentFunctions(client);
|
|
322
|
+
this.knowledge = new AgentKnowledge(client);
|
|
323
|
+
this.mcpServers = new AgentMCPServers(client);
|
|
324
|
+
}
|
|
325
|
+
/** Create an agent. Only `name` is required; everything else has a server default. */
|
|
326
|
+
create(body, options) {
|
|
327
|
+
return this._client.post("/v1/agents", body, options);
|
|
328
|
+
}
|
|
329
|
+
retrieve(agentId, options) {
|
|
330
|
+
return this._client.get(path`/v1/agents/${agentId}`, void 0, options);
|
|
331
|
+
}
|
|
332
|
+
/** Every agent in the organization. `webhook_signing_secret` is `null` in this list. */
|
|
333
|
+
list(options) {
|
|
334
|
+
return this._client.get("/v1/agents", void 0, options);
|
|
335
|
+
}
|
|
336
|
+
/** Partial update: only the fields you send change. */
|
|
337
|
+
update(agentId, body, options) {
|
|
338
|
+
return this._client.patch(path`/v1/agents/${agentId}`, body, options);
|
|
339
|
+
}
|
|
340
|
+
delete(agentId, options) {
|
|
341
|
+
return this._client.delete(path`/v1/agents/${agentId}`, options);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
var AgentFunctions = class extends APIResource {
|
|
345
|
+
list(agentId, options) {
|
|
346
|
+
return this._client.get(path`/v1/agents/${agentId}/functions`, void 0, options);
|
|
347
|
+
}
|
|
348
|
+
create(agentId, body, options) {
|
|
349
|
+
return this._client.post(path`/v1/agents/${agentId}/functions`, body, options);
|
|
350
|
+
}
|
|
351
|
+
update(agentId, functionId, body, options) {
|
|
352
|
+
return this._client.patch(path`/v1/agents/${agentId}/functions/${functionId}`, body, options);
|
|
353
|
+
}
|
|
354
|
+
delete(agentId, functionId, options) {
|
|
355
|
+
return this._client.delete(path`/v1/agents/${agentId}/functions/${functionId}`, options);
|
|
356
|
+
}
|
|
357
|
+
/** Set the order functions are offered to the LLM. `order` is function ids, first to last. */
|
|
358
|
+
reorder(agentId, body, options) {
|
|
359
|
+
return this._client.post(path`/v1/agents/${agentId}/functions/reorder`, body, options);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
var AgentKnowledge = class extends APIResource {
|
|
363
|
+
list(agentId, options) {
|
|
364
|
+
return this._client.get(path`/v1/agents/${agentId}/knowledge`, void 0, options);
|
|
365
|
+
}
|
|
366
|
+
/** Add a plain-text document (up to 500,000 characters). It is chunked and embedded server-side. */
|
|
367
|
+
create(agentId, body, options) {
|
|
368
|
+
return this._client.post(path`/v1/agents/${agentId}/knowledge`, body, options);
|
|
369
|
+
}
|
|
370
|
+
delete(agentId, sourceId, options) {
|
|
371
|
+
return this._client.delete(path`/v1/agents/${agentId}/knowledge/${sourceId}`, options);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
var AgentMCPServers = class extends APIResource {
|
|
375
|
+
list(agentId, options) {
|
|
376
|
+
return this._client.get(path`/v1/agents/${agentId}/mcp-servers`, void 0, options);
|
|
377
|
+
}
|
|
378
|
+
create(agentId, body, options) {
|
|
379
|
+
return this._client.post(path`/v1/agents/${agentId}/mcp-servers`, body, options);
|
|
380
|
+
}
|
|
381
|
+
update(agentId, serverId, body, options) {
|
|
382
|
+
return this._client.patch(path`/v1/agents/${agentId}/mcp-servers/${serverId}`, body, options);
|
|
383
|
+
}
|
|
384
|
+
delete(agentId, serverId, options) {
|
|
385
|
+
return this._client.delete(path`/v1/agents/${agentId}/mcp-servers/${serverId}`, options);
|
|
386
|
+
}
|
|
387
|
+
/** Connect to the server now, list its tools, and store the result as `last_status`. */
|
|
388
|
+
test(agentId, serverId, options) {
|
|
389
|
+
return this._client.post(path`/v1/agents/${agentId}/mcp-servers/${serverId}/test`, void 0, options);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// src/core/pagination.ts
|
|
394
|
+
var Page = class _Page {
|
|
395
|
+
data;
|
|
396
|
+
total;
|
|
397
|
+
page;
|
|
398
|
+
page_size;
|
|
399
|
+
#fetchPage;
|
|
400
|
+
constructor(data, fetchPage) {
|
|
401
|
+
this.data = data.data;
|
|
402
|
+
this.total = data.total;
|
|
403
|
+
this.page = data.page;
|
|
404
|
+
this.page_size = data.page_size;
|
|
405
|
+
this.#fetchPage = fetchPage;
|
|
406
|
+
}
|
|
407
|
+
hasNextPage() {
|
|
408
|
+
return this.data.length > 0 && this.page * this.page_size < this.total;
|
|
409
|
+
}
|
|
410
|
+
async getNextPage() {
|
|
411
|
+
if (!this.hasNextPage()) throw new DialgemError("No next page; check hasNextPage() first.");
|
|
412
|
+
return new _Page(await this.#fetchPage(this.page + 1), this.#fetchPage);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
var PagePromise = class {
|
|
416
|
+
#fetchPage;
|
|
417
|
+
#startPage;
|
|
418
|
+
#first;
|
|
419
|
+
constructor(fetchPage, startPage) {
|
|
420
|
+
this.#fetchPage = fetchPage;
|
|
421
|
+
this.#startPage = startPage;
|
|
422
|
+
}
|
|
423
|
+
#firstPage() {
|
|
424
|
+
this.#first ??= this.#fetchPage(this.#startPage).then((data) => new Page(data, this.#fetchPage));
|
|
425
|
+
return this.#first;
|
|
426
|
+
}
|
|
427
|
+
then(onfulfilled, onrejected) {
|
|
428
|
+
return this.#firstPage().then(onfulfilled, onrejected);
|
|
429
|
+
}
|
|
430
|
+
catch(onrejected) {
|
|
431
|
+
return this.#firstPage().catch(onrejected);
|
|
432
|
+
}
|
|
433
|
+
finally(onfinally) {
|
|
434
|
+
return this.#firstPage().finally(onfinally);
|
|
435
|
+
}
|
|
436
|
+
async *[Symbol.asyncIterator]() {
|
|
437
|
+
let page = await this.#firstPage();
|
|
438
|
+
for (; ; ) {
|
|
439
|
+
yield* page.data;
|
|
440
|
+
if (!page.hasNextPage()) return;
|
|
441
|
+
page = await page.getNextPage();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// src/resources/calls.ts
|
|
447
|
+
var Calls = class extends APIResource {
|
|
448
|
+
/**
|
|
449
|
+
* Calls, newest first. Await for one page, or iterate for every call:
|
|
450
|
+
*
|
|
451
|
+
* const page = await dialgem.calls.list({ page_size: 50 })
|
|
452
|
+
* for await (const call of dialgem.calls.list({ status: "completed" })) {
|
|
453
|
+
* if (call.started_at < cutoff) break // newest first: everything after is older
|
|
454
|
+
* }
|
|
455
|
+
*/
|
|
456
|
+
list(params = {}, options) {
|
|
457
|
+
const fetchPage = async (page) => {
|
|
458
|
+
const res = await this._client.get("/v1/calls", { ...params, page }, options);
|
|
459
|
+
return { data: res.calls, total: res.total, page: res.page, page_size: res.page_size };
|
|
460
|
+
};
|
|
461
|
+
return new PagePromise(fetchPage, params.page ?? 1);
|
|
462
|
+
}
|
|
463
|
+
/** One call, including its transcript, analysis and recording URL. */
|
|
464
|
+
retrieve(callId, options) {
|
|
465
|
+
return this._client.get(path`/v1/calls/${callId}`, void 0, options);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Dial `to` from one of your organization's numbers, with `agent_id` (or the
|
|
469
|
+
* number's outbound agent) on the line.
|
|
470
|
+
*
|
|
471
|
+
* Not retried by default: a retry after an ambiguous failure can place a
|
|
472
|
+
* second call. Follow progress through webhooks or `retrieve(call_id)`.
|
|
473
|
+
*/
|
|
474
|
+
createPhoneCall(body, options) {
|
|
475
|
+
return this._client.post("/v1/calls/outbound", body, options);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Bring-your-own-Twilio: call this from your Twilio voice webhook, then answer
|
|
479
|
+
* Twilio with `<Response><Connect><Stream url="{stream_url}"/></Connect></Response>`.
|
|
480
|
+
* No Twilio credentials are shared with Dialgem.
|
|
481
|
+
*/
|
|
482
|
+
register(body, options) {
|
|
483
|
+
return this._client.post("/v1/calls/register", body, options);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/resources/catalog.ts
|
|
488
|
+
var Voices = class extends APIResource {
|
|
489
|
+
/** Voices available to agents, by name. Put a voice's `provider_voice_id` in an agent's `voice_id`. */
|
|
490
|
+
list(params = {}, options) {
|
|
491
|
+
return this._client.get("/v1/voices", params, options);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var FlowTemplates = class extends APIResource {
|
|
495
|
+
/** Built-in conversation-flow templates (summaries, without `flow_config`). */
|
|
496
|
+
list(params = {}, options) {
|
|
497
|
+
return this._client.get("/v1/flow-templates", params, options);
|
|
498
|
+
}
|
|
499
|
+
retrieve(templateId, options) {
|
|
500
|
+
return this._client.get(path`/v1/flow-templates/${templateId}`, void 0, options);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
var PromptTemplates = class extends APIResource {
|
|
504
|
+
/** Built-in prompt templates (summaries, without the prompt text). */
|
|
505
|
+
list(params = {}, options) {
|
|
506
|
+
return this._client.get("/v1/prompt-templates", params, options);
|
|
507
|
+
}
|
|
508
|
+
retrieve(templateId, options) {
|
|
509
|
+
return this._client.get(path`/v1/prompt-templates/${templateId}`, void 0, options);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// src/resources/phone-numbers.ts
|
|
514
|
+
var PhoneNumbers = class extends APIResource {
|
|
515
|
+
/** Register a number you own (`phone_number` in E.164) with Dialgem. */
|
|
516
|
+
create(body, options) {
|
|
517
|
+
return this._client.post("/v1/phone-numbers", body, options);
|
|
518
|
+
}
|
|
519
|
+
/** Every active (not released) number in the organization, newest first. */
|
|
520
|
+
list(options) {
|
|
521
|
+
return this._client.get("/v1/phone-numbers", void 0, options);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Partial update.
|
|
525
|
+
*
|
|
526
|
+
* `inbound_agent_id` is **ignored unless `set_inbound_agent: true`** is sent with
|
|
527
|
+
* it, both to set an agent and to clear one (`null`). Same for the outbound pair.
|
|
528
|
+
* `inbound_agents` / `outbound_agents` (weighted routing), when sent, take
|
|
529
|
+
* precedence over the single-agent fields.
|
|
530
|
+
*/
|
|
531
|
+
update(phoneNumberId, body, options) {
|
|
532
|
+
return this._client.patch(path`/v1/phone-numbers/${phoneNumberId}`, body, options);
|
|
533
|
+
}
|
|
534
|
+
/** Set the number's **inbound** agent, or pass `agent_id: null` to clear it. For outbound, use `update`. */
|
|
535
|
+
assign(phoneNumberId, body, options) {
|
|
536
|
+
return this._client.patch(path`/v1/phone-numbers/${phoneNumberId}/assign`, body, options);
|
|
537
|
+
}
|
|
538
|
+
/** Release the number from Dialgem. It stops routing calls and disappears from `list()`; your carrier account is untouched. */
|
|
539
|
+
delete(phoneNumberId, options) {
|
|
540
|
+
return this._client.delete(path`/v1/phone-numbers/${phoneNumberId}`, options);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/webhooks.ts
|
|
545
|
+
var WEBHOOK_SIGNATURE_HEADER = "x-dialgem-signature";
|
|
546
|
+
var WEBHOOK_EVENT_HEADER = "x-dialgem-event";
|
|
547
|
+
var encoder = new TextEncoder();
|
|
548
|
+
var decoder = new TextDecoder();
|
|
549
|
+
var Webhooks = class {
|
|
550
|
+
/**
|
|
551
|
+
* Returns whether `signature` is the HMAC-SHA256 of `payload` under `secret`.
|
|
552
|
+
* Constant-time. Returns `false` for a missing or malformed signature; throws only
|
|
553
|
+
* if `secret` is empty, which is a configuration error rather than a bad request.
|
|
554
|
+
*/
|
|
555
|
+
async verify(payload, signature, secret) {
|
|
556
|
+
if (!secret) {
|
|
557
|
+
throw new DialgemError(
|
|
558
|
+
"Webhook secret is empty. Set the agent's `webhook_signing_secret` and pass the same value here."
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
const received = firstHeader(signature)?.trim().toLowerCase();
|
|
562
|
+
if (!received || !/^[0-9a-f]{64}$/.test(received)) return false;
|
|
563
|
+
const expected = await hmacSha256Hex(secret, toBytes(payload));
|
|
564
|
+
return timingSafeEqual(expected, received);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Verifies the signature, then parses the body.
|
|
568
|
+
*
|
|
569
|
+
* @throws WebhookVerificationError if the signature is missing or does not match.
|
|
570
|
+
*/
|
|
571
|
+
async constructEvent(payload, signature, secret) {
|
|
572
|
+
if (!firstHeader(signature)) {
|
|
573
|
+
throw new WebhookVerificationError(
|
|
574
|
+
`Missing ${WEBHOOK_SIGNATURE_HEADER} header. Is a webhook_signing_secret set on the agent?`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
if (!await this.verify(payload, signature, secret)) {
|
|
578
|
+
throw new WebhookVerificationError(
|
|
579
|
+
"Webhook signature does not match the body. Check the secret, and that the body was not re-serialized before verification."
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
const text = typeof payload === "string" ? payload : decoder.decode(toBytes(payload));
|
|
583
|
+
let parsed;
|
|
584
|
+
try {
|
|
585
|
+
parsed = JSON.parse(text);
|
|
586
|
+
} catch {
|
|
587
|
+
throw new WebhookVerificationError("Webhook body is signed but is not valid JSON.");
|
|
588
|
+
}
|
|
589
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.event !== "string") {
|
|
590
|
+
throw new WebhookVerificationError("Webhook body has no `event` field.");
|
|
591
|
+
}
|
|
592
|
+
return parsed;
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
var webhooks = new Webhooks();
|
|
596
|
+
function firstHeader(value) {
|
|
597
|
+
const v = Array.isArray(value) ? value[0] : value;
|
|
598
|
+
return v ?? void 0;
|
|
599
|
+
}
|
|
600
|
+
function toBytes(payload) {
|
|
601
|
+
if (typeof payload === "string") return encoder.encode(payload);
|
|
602
|
+
if (payload instanceof Uint8Array) return payload;
|
|
603
|
+
return new Uint8Array(payload);
|
|
604
|
+
}
|
|
605
|
+
async function hmacSha256Hex(secret, data) {
|
|
606
|
+
const subtle = globalThis.crypto?.subtle;
|
|
607
|
+
if (!subtle) {
|
|
608
|
+
throw new DialgemError("Web Crypto (globalThis.crypto.subtle) is unavailable. Node 20 or newer is required.");
|
|
609
|
+
}
|
|
610
|
+
const key = await subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
611
|
+
const signature = await subtle.sign("HMAC", key, new Uint8Array(data));
|
|
612
|
+
return Array.from(new Uint8Array(signature), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
613
|
+
}
|
|
614
|
+
function timingSafeEqual(a, b) {
|
|
615
|
+
if (a.length !== b.length) return false;
|
|
616
|
+
let diff = 0;
|
|
617
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
618
|
+
return diff === 0;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/client.ts
|
|
622
|
+
var DEFAULT_BASE_URL = "https://api.dialgem.com";
|
|
623
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
624
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
625
|
+
var Dialgem = class {
|
|
626
|
+
static DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
627
|
+
/** Webhook verification without a client instance: `Dialgem.webhooks.constructEvent(...)`. */
|
|
628
|
+
static webhooks = webhooks;
|
|
629
|
+
agents;
|
|
630
|
+
calls;
|
|
631
|
+
phoneNumbers;
|
|
632
|
+
voices;
|
|
633
|
+
flowTemplates;
|
|
634
|
+
promptTemplates;
|
|
635
|
+
webhooks = webhooks;
|
|
636
|
+
baseURL;
|
|
637
|
+
constructor(options = {}) {
|
|
638
|
+
if (isBrowser() && !options.dangerouslyAllowBrowser) {
|
|
639
|
+
throw new DialgemError(
|
|
640
|
+
"The Dialgem SDK is for server-side use: running it in a browser exposes your API key to every visitor. Call Dialgem from your backend instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk."
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
const apiKey = options.apiKey ?? readEnv("DIALGEM_API_KEY");
|
|
644
|
+
if (!apiKey) {
|
|
645
|
+
throw new DialgemError(
|
|
646
|
+
"Missing API key. Pass `apiKey` to `new Dialgem({ apiKey })` or set the DIALGEM_API_KEY environment variable."
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
650
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
651
|
+
assertTimeout(timeout);
|
|
652
|
+
assertMaxRetries(maxRetries);
|
|
653
|
+
const fetchImpl = options.fetch ?? defaultFetch();
|
|
654
|
+
this.baseURL = (options.baseURL ?? readEnv("DIALGEM_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
655
|
+
const http = new HttpClient({
|
|
656
|
+
apiKey,
|
|
657
|
+
baseURL: this.baseURL,
|
|
658
|
+
timeout,
|
|
659
|
+
maxRetries,
|
|
660
|
+
fetch: fetchImpl,
|
|
661
|
+
defaultHeaders: options.defaultHeaders ?? {}
|
|
662
|
+
});
|
|
663
|
+
this.agents = new Agents(http);
|
|
664
|
+
this.calls = new Calls(http);
|
|
665
|
+
this.phoneNumbers = new PhoneNumbers(http);
|
|
666
|
+
this.voices = new Voices(http);
|
|
667
|
+
this.flowTemplates = new FlowTemplates(http);
|
|
668
|
+
this.promptTemplates = new PromptTemplates(http);
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
function readEnv(name) {
|
|
672
|
+
const proc = globalThis.process;
|
|
673
|
+
const value = proc?.env?.[name]?.trim();
|
|
674
|
+
return value ? value : void 0;
|
|
675
|
+
}
|
|
676
|
+
function isBrowser() {
|
|
677
|
+
const g = globalThis;
|
|
678
|
+
return typeof g.window !== "undefined" && typeof g.document !== "undefined";
|
|
679
|
+
}
|
|
680
|
+
function defaultFetch() {
|
|
681
|
+
const f = globalThis.fetch;
|
|
682
|
+
if (typeof f !== "function") {
|
|
683
|
+
throw new DialgemError("No global `fetch` found. Use Node 20 or newer, or pass `fetch` in the client options.");
|
|
684
|
+
}
|
|
685
|
+
return (url, init) => f.call(globalThis, url, init);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
exports.APIConnectionError = APIConnectionError;
|
|
689
|
+
exports.APIConnectionTimeoutError = APIConnectionTimeoutError;
|
|
690
|
+
exports.APIError = APIError;
|
|
691
|
+
exports.APIUserAbortError = APIUserAbortError;
|
|
692
|
+
exports.AuthenticationError = AuthenticationError;
|
|
693
|
+
exports.BadRequestError = BadRequestError;
|
|
694
|
+
exports.ConflictError = ConflictError;
|
|
695
|
+
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
696
|
+
exports.Dialgem = Dialgem;
|
|
697
|
+
exports.DialgemError = DialgemError;
|
|
698
|
+
exports.InternalServerError = InternalServerError;
|
|
699
|
+
exports.NotFoundError = NotFoundError;
|
|
700
|
+
exports.Page = Page;
|
|
701
|
+
exports.PagePromise = PagePromise;
|
|
702
|
+
exports.PermissionDeniedError = PermissionDeniedError;
|
|
703
|
+
exports.RateLimitError = RateLimitError;
|
|
704
|
+
exports.UnprocessableEntityError = UnprocessableEntityError;
|
|
705
|
+
exports.VERSION = VERSION;
|
|
706
|
+
exports.WEBHOOK_EVENT_HEADER = WEBHOOK_EVENT_HEADER;
|
|
707
|
+
exports.WEBHOOK_SIGNATURE_HEADER = WEBHOOK_SIGNATURE_HEADER;
|
|
708
|
+
exports.WebhookVerificationError = WebhookVerificationError;
|
|
709
|
+
exports.Webhooks = Webhooks;
|
|
710
|
+
exports.webhooks = webhooks;
|
|
711
|
+
//# sourceMappingURL=index.cjs.map
|
|
712
|
+
//# sourceMappingURL=index.cjs.map
|