@craftedxp/sdk-node 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/CONSUMING.md +119 -0
- package/DEVELOPING.md +82 -0
- package/README.md +151 -0
- package/dist/index.d.mts +434 -0
- package/dist/index.d.ts +434 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +438 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +46 -0
package/CONSUMING.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Consuming @craftedxp/sdk-node
|
|
2
|
+
|
|
3
|
+
Three install paths, fastest → production. Pick one.
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# In YOUR Node.js / TypeScript project
|
|
9
|
+
npm install /path/to/craftedxp-sdk-node-0.2.0.tgz # or git+ssh, or (eventually) @craftedxp/sdk-node
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { PlatformClient } from '@craftedxp/sdk-node'
|
|
14
|
+
|
|
15
|
+
const client = new PlatformClient({ apiKey: process.env.VOICE_AGENT_SK! })
|
|
16
|
+
const me = await client.me.get()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Node 18+. No peer deps, no native modules, no `pod install`. Pure JS.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Path 1 — Local tarball (fastest)
|
|
24
|
+
|
|
25
|
+
In the SDK repo:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd voice-assistant/sdk/node
|
|
29
|
+
npm install
|
|
30
|
+
npm pack # emits craftedxp-sdk-node-0.2.0.tgz
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
In your app:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install /abs/path/to/voice-assistant/sdk/node/craftedxp-sdk-node-0.2.0.tgz
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`npm pack` runs the `prepare` hook, which rebuilds `dist/`, so the tarball ships `dist/index.js` (CJS) + `dist/index.mjs` (ESM) + `dist/index.d.ts` + sourcemaps — and nothing else.
|
|
40
|
+
|
|
41
|
+
When the SDK changes, re-run `npm pack` and re-install.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Path 2 — Git dependency
|
|
46
|
+
|
|
47
|
+
Works once you've subtree-split the SDK into its own repo:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# one-time, in the main repo
|
|
51
|
+
git subtree split --prefix=sdk/node -b sdk-node
|
|
52
|
+
git push git@github.com:your-org/sdk-node.git sdk-node:main
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then in your app:
|
|
56
|
+
|
|
57
|
+
```jsonc
|
|
58
|
+
{
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@craftedxp/sdk-node": "git+ssh://git@github.com:your-org/sdk-node.git#v0.1.0",
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
npm clones + runs `prepare`, which rebuilds `dist/`. Tag releases (`git tag v0.1.0`) so your pins are stable.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Path 3 — npm registry (production)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd voice-assistant/sdk/node
|
|
73
|
+
npm login
|
|
74
|
+
npm publish --access public
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Consumer side:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install @craftedxp/sdk-node
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For private distribution, see GitHub Packages / Verdaccio / npm Enterprise — same pattern as the sibling React Native SDK.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Runtime requirements
|
|
88
|
+
|
|
89
|
+
- **Node ≥ 18** — needed for native `fetch`, `FormData`, `Blob`, `AbortController`. These were stabilised in 18.x; older Nodes would need polyfills we don't want to carry.
|
|
90
|
+
- **Bundler-friendly** — emits proper `"exports"` with conditional CJS + ESM + types. Works in Next.js (server), Fastify, Express, plain scripts, tests (Vitest / Jest), Cloud Functions, Cloud Run.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Environment
|
|
95
|
+
|
|
96
|
+
Set your API key via environment variable — never hard-code an `sk_` value:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# .env
|
|
100
|
+
VOICE_AGENT_SK=sk_your_key_here
|
|
101
|
+
VOICE_AGENT_BASE_URL=http://localhost:8080 # for local dev
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const client = new PlatformClient({
|
|
106
|
+
apiKey: process.env.VOICE_AGENT_SK!,
|
|
107
|
+
baseUrl: process.env.VOICE_AGENT_BASE_URL,
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Troubleshooting
|
|
114
|
+
|
|
115
|
+
- **`TypeError: fetch is not a function`** — Node version is below 18. Upgrade, or pass a `fetch` implementation explicitly via `new PlatformClient({ fetch: ... })`.
|
|
116
|
+
- **`PlatformError: unauthorized` on every call** — API key missing or wrong. `client.me.get()` is the cleanest validation.
|
|
117
|
+
- **`PlatformError: rate_limited`** — you hit the per-org rate limit (10/min for `POST /v1/calls`, 60/min for `POST /v1/call-tokens`, 100/min everywhere else). The SDK auto-retries with backoff up to `maxRetries` (default 2); raise it or space out calls if you're doing bulk operations.
|
|
118
|
+
- **`PlatformError: payment_required`** — org ran out of credit. `client.credits.getBalance()` + top up via the dashboard.
|
|
119
|
+
- **File uploads timing out** — `uploadFile` already sets a 5-minute timeout; if you're uploading huge PDFs, override via... well, uploads above a few MB are better split into smaller documents anyway. Let me know if that's a real pinch point and we'll plumb a `timeoutMs` override on the upload method.
|
package/DEVELOPING.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Developing `@craftedxp/sdk-node`
|
|
2
|
+
|
|
3
|
+
Pure JS, zero-dep, runs in Node 18+. Fast iteration — no native compilation, no platform quirks.
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd voice-assistant/sdk/node
|
|
9
|
+
npm install
|
|
10
|
+
npm run dev # tsup --watch → rebuilds dist/ on save
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Point a consumer at the local build:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# In the consumer project:
|
|
17
|
+
npm install /abs/path/to/voice-assistant/sdk/node # file: dep
|
|
18
|
+
# or
|
|
19
|
+
yalc add @craftedxp/sdk-node
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
After each rebuild the consumer picks up changes on next `require` (server restart or nodemon). For Next.js API routes, the dev server auto-reloads.
|
|
23
|
+
|
|
24
|
+
## Local testing against the platform
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Terminal 1 — run the platform server
|
|
28
|
+
cd voice-assistant/server && npm run dev
|
|
29
|
+
|
|
30
|
+
# Terminal 2 — run the SDK in watch mode
|
|
31
|
+
cd voice-assistant/sdk/node && npm run dev
|
|
32
|
+
|
|
33
|
+
# Terminal 3 — a scratch consumer
|
|
34
|
+
cd /tmp && mkdir sdk-smoke && cd sdk-smoke
|
|
35
|
+
npm init -y
|
|
36
|
+
npm install /path/to/voice-assistant/sdk/node
|
|
37
|
+
cat > smoke.mjs <<'EOF'
|
|
38
|
+
import { PlatformClient } from '@craftedxp/sdk-node'
|
|
39
|
+
const client = new PlatformClient({
|
|
40
|
+
apiKey: process.env.VOICE_AGENT_SK,
|
|
41
|
+
baseUrl: 'http://localhost:8080',
|
|
42
|
+
})
|
|
43
|
+
console.log(await client.me.get())
|
|
44
|
+
console.log((await client.agents.list()).map(a => ({ id: a.agentId, name: a.name })))
|
|
45
|
+
EOF
|
|
46
|
+
VOICE_AGENT_SK=sk_... node smoke.mjs
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Adding a new resource
|
|
50
|
+
|
|
51
|
+
Say we add `/v1/phone-numbers` server-side. The SDK pattern:
|
|
52
|
+
|
|
53
|
+
1. Add types to `src/types.ts` (`PhoneNumber`, `PhoneNumberCreateInput`, etc.)
|
|
54
|
+
2. Create `src/resources/phoneNumbers.ts` following the shape of existing resources (factory taking `HttpClient`, returning `{ create, list, get, delete, ... }`)
|
|
55
|
+
3. Wire it onto `PlatformClient` in `src/PlatformClient.ts`
|
|
56
|
+
4. Re-export the resource type from `src/index.ts`
|
|
57
|
+
5. Add a section to `README.md` under "API surface"
|
|
58
|
+
6. `npm run build` + `npm run typecheck`
|
|
59
|
+
|
|
60
|
+
Each resource is ~30–80 lines. The HTTP + error handling is centralised in `src/http.ts` — resources never call `fetch` directly.
|
|
61
|
+
|
|
62
|
+
## Style
|
|
63
|
+
|
|
64
|
+
- **No top-level state.** Resources are factories keyed on the HTTP client, never module-scoped singletons. Consumers that instantiate multiple clients (e.g. in a test harness spanning multiple orgs) need isolation.
|
|
65
|
+
- **Dates as `number` (ms).** Matches the server's Firestore Timestamp serialisation. Consumers who want Date objects can wrap.
|
|
66
|
+
- **No mutation of inputs.** Every resource method JSON-serialises inputs via `JSON.stringify` — pass whatever you want, we don't touch it.
|
|
67
|
+
- **Errors always `PlatformError`.** Network errors get wrapped with `code: 'unknown'` + `status: 0`, but the class is consistent so `catch (err)` / `instanceof` works across all failures.
|
|
68
|
+
|
|
69
|
+
## Before releasing
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm run build # emits dist/
|
|
73
|
+
npm run typecheck
|
|
74
|
+
npm pack --dry-run # verify tarball contents — should be dist/ + docs + package.json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then bump `version` in `package.json`, commit, tag, publish.
|
|
78
|
+
|
|
79
|
+
## Related
|
|
80
|
+
|
|
81
|
+
- [CONSUMING.md](./CONSUMING.md) — external install paths
|
|
82
|
+
- `../web/DEVELOPING.md` + `../react-native/DEVELOPING.md` — sibling SDKs
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @craftedxp/sdk-node
|
|
2
|
+
|
|
3
|
+
Node.js / TypeScript SDK for the voice agent platform. Use it from your **backend** to mint short-lived call tokens, manage agents, query calls, and upload knowledge-base docs.
|
|
4
|
+
|
|
5
|
+
> Pairs with `@craftedxp/voice-rn` (the React Native client SDK). Your app calls `fetchToken` → your backend uses this SDK → the platform mints a `ct_` token → returned to the app. The `sk_` API key only ever lives on your backend.
|
|
6
|
+
|
|
7
|
+
Zero runtime dependencies. Node 18+.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @craftedxp/sdk-node
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start — minting a call token
|
|
16
|
+
|
|
17
|
+
The most common use:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { PlatformClient } from '@craftedxp/sdk-node'
|
|
21
|
+
|
|
22
|
+
const client = new PlatformClient({
|
|
23
|
+
apiKey: process.env.VOICE_AGENT_SK!, // never ship this to a client
|
|
24
|
+
baseUrl: 'https://api.your-server.com', // or http://localhost:8080 for dev
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// In your "/voice-token" route handler — called by the RN SDK's fetchToken:
|
|
28
|
+
async function mintForCall({ agentId, userId, context, metadata }) {
|
|
29
|
+
const { token } = await client.callTokens.mint({
|
|
30
|
+
agentId,
|
|
31
|
+
ttlSeconds: 600, // 10 min default; up to 3600 (60 min)
|
|
32
|
+
contactId: userId, // optional → cross-call memory
|
|
33
|
+
context, // optional arbitrary JSON, lowered into agent's system prompt
|
|
34
|
+
metadata, // optional opaque keys round-tripped on call.ended webhook (≤1 KB)
|
|
35
|
+
})
|
|
36
|
+
return { token } // hand to the RN SDK
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The full call-token surface:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
client.callTokens.mint({ agentId, ttlSeconds, context, metadata, contactId, vars, allowedOrigins })
|
|
44
|
+
client.callTokens.list({ includeRevoked, includeExpired, limit })
|
|
45
|
+
client.callTokens.revoke(tokenId)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Other resources
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
client.me.get() // the org behind the sk_
|
|
52
|
+
|
|
53
|
+
client.agents.create({ name, systemPrompt, ... }) // CRUD on agents
|
|
54
|
+
client.agents.list({ limit, cursor })
|
|
55
|
+
client.agents.get(agentId)
|
|
56
|
+
client.agents.update(agentId, patch)
|
|
57
|
+
client.agents.delete(agentId)
|
|
58
|
+
client.agents.webhooks(agentId).create({ url, secret, events })
|
|
59
|
+
client.agents.webhooks(agentId).list()
|
|
60
|
+
// ...
|
|
61
|
+
|
|
62
|
+
client.calls.list({ agentId, status, limit, cursor }) // call records
|
|
63
|
+
client.calls.listAll({ agentId }) // async iterator across pages
|
|
64
|
+
client.calls.get(callId)
|
|
65
|
+
|
|
66
|
+
client.knowledgeBases.create({ name }) // KB CRUD + uploads
|
|
67
|
+
client.knowledgeBases.list()
|
|
68
|
+
client.knowledgeBases.uploadFile(kbId, filePath)
|
|
69
|
+
client.knowledgeBases.delete(kbId)
|
|
70
|
+
|
|
71
|
+
client.credits.getBalance() // billing
|
|
72
|
+
client.credits.getLedger({ limit, cursor })
|
|
73
|
+
|
|
74
|
+
client.webhooks.deliveries({ agentId, callId, webhookId }) // org-wide delivery log
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Webhook signature verification
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import express from 'express'
|
|
81
|
+
import { verifyWebhookSignature } from '@craftedxp/sdk-node'
|
|
82
|
+
|
|
83
|
+
const app = express()
|
|
84
|
+
|
|
85
|
+
app.post(
|
|
86
|
+
'/webhooks/voice-agent',
|
|
87
|
+
// Raw body — we need the exact bytes to recompute the HMAC.
|
|
88
|
+
express.raw({ type: 'application/json' }),
|
|
89
|
+
(req, res) => {
|
|
90
|
+
const sig = req.header('X-Platform-Signature-256') ?? ''
|
|
91
|
+
if (!verifyWebhookSignature(req.body, sig, process.env.VOICE_AGENT_WEBHOOK_SECRET!)) {
|
|
92
|
+
return res.status(401).send('invalid signature')
|
|
93
|
+
}
|
|
94
|
+
const event = JSON.parse(req.body.toString('utf8'))
|
|
95
|
+
// ...handle event
|
|
96
|
+
res.status(200).end()
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The function is timing-safe; copy it into your middleware verbatim. No PlatformClient instance required — `verifyWebhookSignature` is a standalone helper so any framework (Koa, Next.js route handlers, Hono, etc.) can use it.
|
|
102
|
+
|
|
103
|
+
## Errors
|
|
104
|
+
|
|
105
|
+
All HTTP errors throw a typed `PlatformError`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { PlatformError } from '@craftedxp/sdk-node'
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await client.callTokens.mint({ agentId })
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof PlatformError) {
|
|
114
|
+
console.error(err.code, err.status, err.message, err.field, err.docsUrl)
|
|
115
|
+
}
|
|
116
|
+
throw err
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The HTTP layer auto-retries 429 (honouring `Retry-After`) and 5xx responses with exponential backoff. Default `maxRetries = 2` (3 total attempts); pass `0` to `new PlatformClient({ maxRetries: 0 })` to disable.
|
|
121
|
+
|
|
122
|
+
## Pagination
|
|
123
|
+
|
|
124
|
+
Cursor-based. Use the `listAll` async iterator if you want every page without managing the cursor manually:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
for await (const call of client.calls.listAll({ agentId })) {
|
|
128
|
+
// process each call across all pages
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## What's NOT in this SDK
|
|
133
|
+
|
|
134
|
+
- Voice / WebSocket / audio streaming. Use `@craftedxp/voice-rn` (React Native) for the call path itself.
|
|
135
|
+
- Browser-side use. This SDK assumes a server-side environment with native `fetch`. The `sk_` API key must never reach a browser.
|
|
136
|
+
- Phone numbers, outbound dialling. Telephony is on the platform roadmap; the SDK will surface it when the server endpoints land.
|
|
137
|
+
|
|
138
|
+
## Migration from `@voxline/node@0.1.0`
|
|
139
|
+
|
|
140
|
+
`@voxline/node` was a pre-launch internal name. This package replaces it under the `@craftedxp` org. API surface is unchanged except for the Phase 12 additions (`context` / `metadata` / `contactId` on `callTokens.mint`).
|
|
141
|
+
|
|
142
|
+
```diff
|
|
143
|
+
- import { PlatformClient } from '@voxline/node'
|
|
144
|
+
+ import { PlatformClient } from '@craftedxp/sdk-node'
|
|
145
|
+
|
|
146
|
+
const client = new PlatformClient({ apiKey })
|
|
147
|
+
- await client.callTokens.mint({ agentId, vars: { name: 'Arun' } })
|
|
148
|
+
+ await client.callTokens.mint({ agentId, context: { name: 'Arun', orders: [/* nested ok */] } })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`vars` is still accepted server-side for backward compatibility.
|