@osmapi/osmtalk-sdk 0.3.0 → 0.3.1

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 CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  Official TypeScript / JavaScript SDK for the [osmTalk](https://osmtalk.com) voice AI platform.
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/@osmapi/osmtalk-sdk.svg)](https://www.npmjs.com/package/@osmapi/osmtalk-sdk)
6
+
5
7
  ```bash
6
8
  npm install @osmapi/osmtalk-sdk
7
9
  # or: pnpm add @osmapi/osmtalk-sdk
8
10
  ```
9
11
 
12
+ Works in **Node 18+, Deno, Bun, Cloudflare Workers, and browsers**.
13
+
10
14
  ## Quick start
11
15
 
12
16
  ```ts
@@ -34,11 +38,25 @@ console.log("Call started:", call.callId);
34
38
  | Resource | Operations |
35
39
  |---|---|
36
40
  | `client.agents` | `list`, `get`, `create`, `update`, `delete`, `connect`, `publishVersion`, `listVersions`, `getVersion`, `rollbackToVersion` |
37
- | `client.calls` | `list`, `get`, `outbound`, `end`, `transfer` |
41
+ | `client.calls` | `list`, `get`, `outbound`, `end`, `transfer`, **`waitUntilEnded`** |
38
42
  | `client.campaigns` | `list`, `get`, `create`, `update`, `delete`, `start`, `pause`, `resume`, `stop`, `report`, `uploadLeadsCsv`, `uploadLeads`, `listLeads` |
39
43
  | `client.dnc` | `list`, `add`, `bulkAdd`, `remove` |
40
44
  | `client.eval` | `simulate`, `createTestCase`, `listTestCases`, `runTestCase`, `runAll`, `listRuns`, `getRun` |
41
45
  | `client.settings` | `get`, `getStorage`, `updateStorage`, `getWebhook`, `updateWebhook`, `getCompliance`, `updateCompliance` |
46
+ | `client.platform` | `getRates`, `listProviders`, `getPresets`, `getModelHealth`, `getTemplates`, `getTemplate`, `saveTemplate`, `deleteTemplate` |
47
+
48
+ Plus the standalone helpers `verifyWebhookSignature` / `verifyWebhookSignatureAsync` (see below).
49
+
50
+ ## What's new in 0.3.0
51
+
52
+ - **Auto-retry** on 5xx, 429, and network errors with `Retry-After` honored and exponential backoff. No more hand-rolling retry wrappers.
53
+ - **`client.calls.waitUntilEnded(callId)`** — one-line polling helper for the "place call → wait → get result" pattern.
54
+ - **`AbortSignal`** support on every method via `RequestOptions.signal`.
55
+ - **`User-Agent`** header sent automatically.
56
+ - **Per-org request override** via `RequestOptions.organizationId`.
57
+ - **`OsmtalkError.isRetryable` / `.isClientError` / `.retryAttempts`** for cleaner error branching.
58
+
59
+ Full version history: [CHANGELOG.md](./CHANGELOG.md).
42
60
 
43
61
  ## Examples
44
62
 
@@ -68,6 +86,45 @@ const report = await client.campaigns.report(camp.id);
68
86
  console.log(report.counts.byStatus);
69
87
  ```
70
88
 
89
+ ### Wait for a call to finish (without writing a poll loop)
90
+
91
+ ```ts
92
+ const { callId } = await client.calls.outbound({
93
+ agentId: "agent_xxx",
94
+ phoneNumberId: "pn_xxx",
95
+ destination: "+919876543210",
96
+ });
97
+
98
+ // Default: poll every 5s, give up after 30 minutes. All configurable.
99
+ const final = await client.calls.waitUntilEnded(callId, {
100
+ pollIntervalMs: 5_000,
101
+ timeoutMs: 15 * 60 * 1000,
102
+ });
103
+
104
+ console.log("Final status:", final.status);
105
+ console.log("Duration: ", final.durationSeconds, "s");
106
+ console.log("Disposition: ", final.disposition);
107
+ console.log("Recording: ", final.recordingUrl);
108
+ ```
109
+
110
+ For production, prefer webhooks — see the receiver example below.
111
+
112
+ ### Cancel an in-flight request
113
+
114
+ ```ts
115
+ const ctrl = new AbortController();
116
+ setTimeout(() => ctrl.abort(), 2_000);
117
+
118
+ try {
119
+ await client.agents.list({ signal: ctrl.signal });
120
+ } catch (err) {
121
+ if (ctrl.signal.aborted) console.log("Cancelled by us");
122
+ else throw err;
123
+ }
124
+ ```
125
+
126
+ `signal`, `timeoutMs`, and `organizationId` are accepted on every method via the trailing `RequestOptions` argument.
127
+
71
128
  ### Publish a new agent version and A/B test
72
129
 
73
130
  ```ts
@@ -75,7 +132,12 @@ console.log(report.counts.byStatus);
75
132
  const v2 = await client.agents.publishVersion("agent_xxx", { label: "Tighter qualifier" });
76
133
 
77
134
  // Call with v2 explicitly
78
- await client.calls.outbound({ agentId: "agent_xxx", phoneNumberId: "pn_xxx", destination: "+919876543210", agentVersion: v2.version });
135
+ await client.calls.outbound({
136
+ agentId: "agent_xxx",
137
+ phoneNumberId: "pn_xxx",
138
+ destination: "+919876543210",
139
+ agentVersion: v2.version,
140
+ });
79
141
  ```
80
142
 
81
143
  ### Simulate before going live
@@ -91,33 +153,54 @@ for (const turn of sim.transcript) {
91
153
  }
92
154
  ```
93
155
 
94
- ### Handle the webhook in Node.js
156
+ ### Verify webhooks (Node, sync)
95
157
 
96
158
  ```ts
97
- import crypto from "node:crypto";
98
159
  import express from "express";
160
+ import { verifyWebhookSignature } from "@osmapi/osmtalk-sdk";
99
161
 
100
162
  const app = express();
101
- app.use(express.raw({ type: "application/json" }));
163
+ // IMPORTANT: use express.raw() NOT express.json(). The signature was
164
+ // computed over the exact bytes; re-serialized JSON will not match.
165
+ app.use("/webhooks/osmtalk", express.raw({ type: "application/json" }));
102
166
 
103
167
  app.post("/webhooks/osmtalk", (req, res) => {
104
- const sig = req.header("X-OsmTalk-Signature") ?? "";
105
- const [algo, hex] = sig.split("=");
106
- const expected = crypto
107
- .createHmac("sha256", process.env.OSMTALK_WEBHOOK_SECRET!)
108
- .update(req.body)
109
- .digest("hex");
110
- if (algo !== "sha256" || !crypto.timingSafeEqual(Buffer.from(hex, "hex"), Buffer.from(expected, "hex"))) {
111
- return res.sendStatus(401);
112
- }
168
+ const ok = verifyWebhookSignature(
169
+ req.body,
170
+ req.header("x-osmtalk-signature"),
171
+ process.env.OSMTALK_WEBHOOK_SECRET!,
172
+ );
173
+ if (!ok) return res.status(401).end();
174
+
113
175
  const event = JSON.parse(req.body.toString());
114
- if (event.event === "campaign.lead_completed" && event.lead.disposition === "qualified") {
115
- console.log("Hot lead:", event.lead.variables.first_name);
176
+ if (event.event === "call.completed") {
177
+ console.log("Call ended:", event.call.id, event.analysis?.disposition);
116
178
  }
117
179
  res.json({ ok: true });
118
180
  });
119
181
  ```
120
182
 
183
+ ### Verify webhooks in Workers / Deno / Bun (async, WebCrypto)
184
+
185
+ ```ts
186
+ import { verifyWebhookSignatureAsync } from "@osmapi/osmtalk-sdk";
187
+
188
+ export default {
189
+ async fetch(req: Request) {
190
+ const raw = await req.text();
191
+ const ok = await verifyWebhookSignatureAsync(
192
+ raw,
193
+ req.headers.get("x-osmtalk-signature"),
194
+ env.OSMTALK_WEBHOOK_SECRET,
195
+ );
196
+ if (!ok) return new Response("invalid signature", { status: 401 });
197
+ const event = JSON.parse(raw);
198
+ // …handle event
199
+ return new Response("ok");
200
+ },
201
+ };
202
+ ```
203
+
121
204
  ## Error handling
122
205
 
123
206
  ```ts
@@ -128,32 +211,61 @@ try {
128
211
  } catch (err) {
129
212
  if (err instanceof OsmtalkError) {
130
213
  console.log("HTTP", err.status, err.body);
214
+ console.log("Retries attempted:", err.retryAttempts);
215
+ if (err.isRetryable) console.log("Server might recover — try again later.");
216
+ if (err.isClientError) console.log("Bad input — check err.body.details.");
131
217
  } else {
132
218
  throw err;
133
219
  }
134
220
  }
135
221
  ```
136
222
 
137
- | Status | Meaning |
138
- |---|---|
139
- | 400 | Validation — `err.body.details` has zod field errors |
140
- | 401 | Bad API key |
141
- | 402 | Insufficient credits |
142
- | 404 | Resource not found |
143
- | 429 | Concurrency or rate limit |
144
- | 5xx | Server error / provider outage |
223
+ | Status | Meaning | `OsmtalkError` flag |
224
+ |---|---|---|
225
+ | 400 | Validation — `err.body.details` has zod field errors | `isClientError` |
226
+ | 401 | Bad API key | `isClientError` |
227
+ | 402 | Insufficient credits | `isClientError` |
228
+ | 404 | Resource not found | `isClientError` |
229
+ | 408 | Request timeout | `isRetryable` |
230
+ | 429 | Concurrency or rate limit | `isRetryable` |
231
+ | 5xx | Server error / provider outage | `isRetryable` |
232
+
233
+ The SDK already auto-retries 408/429/5xx and network errors twice by default. Mutating requests (POST/PUT/DELETE) are only retried when you pass `idempotencyKey` so the SDK never silently double-charges.
145
234
 
146
235
  ## Options
147
236
 
148
237
  ```ts
149
238
  new Osmtalk({
150
- apiKey: "...",
151
- baseUrl: "https://api.osmtalk.com", // optional override
152
- timeoutMs: 30_000, // per-request timeout
153
- fetch: customFetch, // optional (Node 18+ has global fetch)
239
+ apiKey: "osm_live_…",
240
+ baseUrl: "https://api.osmtalk.com", // default
241
+ timeoutMs: 30_000, // per-request, 0 to disable
242
+ maxRetries: 2, // auto-retry count for 5xx/429
243
+ retryInitialDelayMs: 250, // doubles per retry, jittered
244
+ organizationId: "org_xxx", // for multi-org accounts
245
+ defaultHeaders: { "X-Trace-Id": "…" },// added to every request
246
+ fetch: customFetch, // optional, defaults to global fetch
247
+ });
248
+ ```
249
+
250
+ Per-request overrides:
251
+
252
+ ```ts
253
+ await client.calls.outbound(input, {
254
+ idempotencyKey: `dest-${destination}-${date}`,
255
+ signal: controller.signal,
256
+ timeoutMs: 60_000,
257
+ organizationId: "org_yyy",
154
258
  });
155
259
  ```
156
260
 
261
+ ## Runnable examples
262
+
263
+ See [github.com/osm-API/osmtalk-examples](https://github.com/osm-API/osmtalk-examples) for three end-to-end projects:
264
+
265
+ 1. **Personalized outbound call** — dynamic per-user prompts
266
+ 2. **Bulk campaign from CSV** — scale to thousands
267
+ 3. **Verified webhook receiver** — close the loop with `verifyWebhookSignature`
268
+
157
269
  ## License
158
270
 
159
271
  MIT
package/dist/index.d.mts CHANGED
@@ -10,8 +10,12 @@
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
+ /**
14
+ * Current SDK version, sent in the User-Agent header and useful for
15
+ * runtime version checks (e.g. logging which SDK build is talking to the
16
+ * API, or feature-detecting against minimum versions in shared code).
17
+ */
18
+ declare const SDK_VERSION = "0.3.1";
15
19
  interface OsmtalkOptions {
16
20
  apiKey: string;
17
21
  baseUrl?: string;
@@ -253,7 +257,15 @@ declare class AgentsResource {
253
257
  callId: string;
254
258
  }>;
255
259
  }
256
- /** Call.status values that mean "no more state changes are coming". */
260
+ /**
261
+ * Call statuses that mean "no more state changes are coming" — the
262
+ * record is final and safe to consume. `waitUntilEnded()` polls until
263
+ * the call's status matches one of these.
264
+ *
265
+ * Exported so downstream code can mirror the SDK's definition of "done"
266
+ * without copy-pasting strings (e.g. when reacting to webhook events or
267
+ * filtering call lists in a dashboard).
268
+ */
257
269
  declare const TERMINAL_CALL_STATUSES: readonly ["completed", "failed", "ended", "cancelled"];
258
270
  declare class CallsResource {
259
271
  private readonly http;
package/dist/index.d.ts CHANGED
@@ -10,8 +10,12 @@
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
+ /**
14
+ * Current SDK version, sent in the User-Agent header and useful for
15
+ * runtime version checks (e.g. logging which SDK build is talking to the
16
+ * API, or feature-detecting against minimum versions in shared code).
17
+ */
18
+ declare const SDK_VERSION = "0.3.1";
15
19
  interface OsmtalkOptions {
16
20
  apiKey: string;
17
21
  baseUrl?: string;
@@ -253,7 +257,15 @@ declare class AgentsResource {
253
257
  callId: string;
254
258
  }>;
255
259
  }
256
- /** Call.status values that mean "no more state changes are coming". */
260
+ /**
261
+ * Call statuses that mean "no more state changes are coming" — the
262
+ * record is final and safe to consume. `waitUntilEnded()` polls until
263
+ * the call's status matches one of these.
264
+ *
265
+ * Exported so downstream code can mirror the SDK's definition of "done"
266
+ * without copy-pasting strings (e.g. when reacting to webhook events or
267
+ * filtering call lists in a dashboard).
268
+ */
257
269
  declare const TERMINAL_CALL_STATUSES: readonly ["completed", "failed", "ended", "cancelled"];
258
270
  declare class CallsResource {
259
271
  private readonly http;
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ __export(index_exports, {
29
29
  verifyWebhookSignatureAsync: () => verifyWebhookSignatureAsync
30
30
  });
31
31
  module.exports = __toCommonJS(index_exports);
32
- var SDK_VERSION = "0.3.0";
32
+ var SDK_VERSION = "0.3.1";
33
33
  var OsmtalkError = class extends Error {
34
34
  status;
35
35
  body;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var SDK_VERSION = "0.3.0";
2
+ var SDK_VERSION = "0.3.1";
3
3
  var OsmtalkError = class extends Error {
4
4
  status;
5
5
  body;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osmapi/osmtalk-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Official TypeScript SDK for the osmTalk voice AI platform",
5
5
  "homepage": "https://docs.osmtalk.com",
6
6
  "repository": {