@craftedxp/sdk-node 0.3.0 → 0.5.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 CHANGED
@@ -86,16 +86,20 @@ interface Agent {
86
86
  recording?: AgentRecording;
87
87
  structuredDataSchema?: Record<string, unknown>;
88
88
  /**
89
- * Free-form persona tags the consumer catalog filters on. Lowercased on
89
+ * End-user authorisation gate. Lists every user tag that's allowed to
90
+ * call this agent. Match rule is intersection: the call-token's
91
+ * `userTags` must contain at least one string from this list. Empty /
92
+ * unset means anyone with a valid `sk_` can mint a token. Lowercased on
90
93
  * save (regex `[a-zA-Z0-9_-]+`).
91
94
  */
92
- tags?: string[];
95
+ allowedUserTags?: string[];
93
96
  /**
94
- * End-user authorisation gate. When non-empty, the call-token mint must
95
- * include `userTags` that intersect this list (else 403). Empty / unset
96
- * means anyone with a valid `sk_` can mint a token for this agent.
97
+ * Public GCS URL of the agent's avatar image, if uploaded. Set by the
98
+ * server when an upload to `POST /v1/agents/{agentId}/avatar` succeeds;
99
+ * cleared by `DELETE` on the same path. Includes a `?v={timestamp}`
100
+ * cache-buster so browser/CDN caches refresh on replace.
97
101
  */
98
- allowedUserTags?: string[];
102
+ avatarUrl?: string;
99
103
  createdAt: number;
100
104
  updatedAt: number;
101
105
  }
@@ -113,8 +117,14 @@ interface AgentCreateInput {
113
117
  knowledgeBaseId?: string;
114
118
  recording?: AgentRecording;
115
119
  structuredDataSchema?: Record<string, unknown>;
116
- tags?: string[];
117
120
  allowedUserTags?: string[];
121
+ /**
122
+ * Server-managed. Don't set this directly via create/update — use
123
+ * `agents.uploadAvatar(agentId, file)` instead. Sending it raw is
124
+ * accepted but the upload endpoint is the only way the GCS object
125
+ * actually changes.
126
+ */
127
+ avatarUrl?: string;
118
128
  }
119
129
  type AgentUpdateInput = Partial<AgentCreateInput>;
120
130
  /**
@@ -127,11 +137,16 @@ type AgentUpdateInput = Partial<AgentCreateInput>;
127
137
  interface CatalogAgent {
128
138
  agentId: string;
129
139
  name: string;
130
- tags: string[];
131
140
  voice: {
132
141
  provider: string;
133
142
  voiceId?: string;
134
143
  };
144
+ /**
145
+ * Public URL of the agent's avatar image, if one has been uploaded.
146
+ * Suitable to set directly as `<img src=>` on web / `Image source` on
147
+ * RN. Absent when the agent has no avatar.
148
+ */
149
+ avatarUrl?: string;
135
150
  }
136
151
  interface CatalogListInput {
137
152
  /**
@@ -139,11 +154,6 @@ interface CatalogListInput {
139
154
  * server returns 403 otherwise.
140
155
  */
141
156
  orgId: string;
142
- /**
143
- * OR-filter on persona tags. Empty / undefined returns all agents in
144
- * the org.
145
- */
146
- tags?: string[];
147
157
  /**
148
158
  * End-user entitlement tags. When supplied, the catalog hides agents
149
159
  * whose `allowedUserTags` doesn't intersect with these. Omit to get the
@@ -377,6 +387,26 @@ declare const createAgentsResource: (http: HttpClient) => {
377
387
  get: (agentId: string) => Promise<Agent>;
378
388
  update: (agentId: string, patch: AgentUpdateInput) => Promise<Agent>;
379
389
  delete: (agentId: string) => Promise<void>;
390
+ /**
391
+ * Upload an avatar image for the agent. Server re-encodes to a 512×512
392
+ * WebP and stores it in the public-read avatars bucket; the returned
393
+ * `Agent` has `avatarUrl` set to the canonical public URL with a
394
+ * `?v=` cache-buster.
395
+ *
396
+ * `file` accepts a Node Buffer / Uint8Array / Blob / typed-array.
397
+ * `filename` and `contentType` are optional but help the server log
398
+ * meaningful errors when something rejects.
399
+ */
400
+ uploadAvatar: (agentId: string, file: Buffer | Uint8Array | Blob | ArrayBuffer, opts?: {
401
+ filename?: string;
402
+ contentType?: string;
403
+ }) => Promise<Agent>;
404
+ /**
405
+ * Remove the agent's avatar — both the GCS object and the `avatarUrl`
406
+ * field. Idempotent: calling on an agent without an avatar still
407
+ * returns the agent.
408
+ */
409
+ removeAvatar: (agentId: string) => Promise<Agent>;
380
410
  webhooks: (agentId: string) => AgentWebhooksResource;
381
411
  };
382
412
  type AgentsResource = ReturnType<typeof createAgentsResource>;
@@ -457,11 +487,12 @@ declare const createOrgsResource: (http: HttpClient) => {
457
487
  * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`
458
488
  * with the same `sk_` if you need the full admin shape.
459
489
  *
460
- * Filters:
461
- * - `tags`: OR-match on persona tags. Empty = all.
462
- * - `userTags`: tier gate. Hides agents whose `allowedUserTags` is
463
- * non-empty and doesn't intersect. Omit to skip the gate filter
464
- * (admin / dashboard usage).
490
+ * `userTags`: end-user entitlement tags. When supplied, hides agents
491
+ * whose `allowedUserTags` is non-empty and doesn't intersect with the
492
+ * supplied list. Omit to get the unfiltered admin view.
493
+ *
494
+ * Persona / category filtering is intentionally not a server param —
495
+ * filter the returned list client-side over `name`s if you need it.
465
496
  *
466
497
  * Throws `403 forbidden` if `orgId` doesn't match the key's org.
467
498
  */
package/dist/index.d.ts CHANGED
@@ -86,16 +86,20 @@ interface Agent {
86
86
  recording?: AgentRecording;
87
87
  structuredDataSchema?: Record<string, unknown>;
88
88
  /**
89
- * Free-form persona tags the consumer catalog filters on. Lowercased on
89
+ * End-user authorisation gate. Lists every user tag that's allowed to
90
+ * call this agent. Match rule is intersection: the call-token's
91
+ * `userTags` must contain at least one string from this list. Empty /
92
+ * unset means anyone with a valid `sk_` can mint a token. Lowercased on
90
93
  * save (regex `[a-zA-Z0-9_-]+`).
91
94
  */
92
- tags?: string[];
95
+ allowedUserTags?: string[];
93
96
  /**
94
- * End-user authorisation gate. When non-empty, the call-token mint must
95
- * include `userTags` that intersect this list (else 403). Empty / unset
96
- * means anyone with a valid `sk_` can mint a token for this agent.
97
+ * Public GCS URL of the agent's avatar image, if uploaded. Set by the
98
+ * server when an upload to `POST /v1/agents/{agentId}/avatar` succeeds;
99
+ * cleared by `DELETE` on the same path. Includes a `?v={timestamp}`
100
+ * cache-buster so browser/CDN caches refresh on replace.
97
101
  */
98
- allowedUserTags?: string[];
102
+ avatarUrl?: string;
99
103
  createdAt: number;
100
104
  updatedAt: number;
101
105
  }
@@ -113,8 +117,14 @@ interface AgentCreateInput {
113
117
  knowledgeBaseId?: string;
114
118
  recording?: AgentRecording;
115
119
  structuredDataSchema?: Record<string, unknown>;
116
- tags?: string[];
117
120
  allowedUserTags?: string[];
121
+ /**
122
+ * Server-managed. Don't set this directly via create/update — use
123
+ * `agents.uploadAvatar(agentId, file)` instead. Sending it raw is
124
+ * accepted but the upload endpoint is the only way the GCS object
125
+ * actually changes.
126
+ */
127
+ avatarUrl?: string;
118
128
  }
119
129
  type AgentUpdateInput = Partial<AgentCreateInput>;
120
130
  /**
@@ -127,11 +137,16 @@ type AgentUpdateInput = Partial<AgentCreateInput>;
127
137
  interface CatalogAgent {
128
138
  agentId: string;
129
139
  name: string;
130
- tags: string[];
131
140
  voice: {
132
141
  provider: string;
133
142
  voiceId?: string;
134
143
  };
144
+ /**
145
+ * Public URL of the agent's avatar image, if one has been uploaded.
146
+ * Suitable to set directly as `<img src=>` on web / `Image source` on
147
+ * RN. Absent when the agent has no avatar.
148
+ */
149
+ avatarUrl?: string;
135
150
  }
136
151
  interface CatalogListInput {
137
152
  /**
@@ -139,11 +154,6 @@ interface CatalogListInput {
139
154
  * server returns 403 otherwise.
140
155
  */
141
156
  orgId: string;
142
- /**
143
- * OR-filter on persona tags. Empty / undefined returns all agents in
144
- * the org.
145
- */
146
- tags?: string[];
147
157
  /**
148
158
  * End-user entitlement tags. When supplied, the catalog hides agents
149
159
  * whose `allowedUserTags` doesn't intersect with these. Omit to get the
@@ -377,6 +387,26 @@ declare const createAgentsResource: (http: HttpClient) => {
377
387
  get: (agentId: string) => Promise<Agent>;
378
388
  update: (agentId: string, patch: AgentUpdateInput) => Promise<Agent>;
379
389
  delete: (agentId: string) => Promise<void>;
390
+ /**
391
+ * Upload an avatar image for the agent. Server re-encodes to a 512×512
392
+ * WebP and stores it in the public-read avatars bucket; the returned
393
+ * `Agent` has `avatarUrl` set to the canonical public URL with a
394
+ * `?v=` cache-buster.
395
+ *
396
+ * `file` accepts a Node Buffer / Uint8Array / Blob / typed-array.
397
+ * `filename` and `contentType` are optional but help the server log
398
+ * meaningful errors when something rejects.
399
+ */
400
+ uploadAvatar: (agentId: string, file: Buffer | Uint8Array | Blob | ArrayBuffer, opts?: {
401
+ filename?: string;
402
+ contentType?: string;
403
+ }) => Promise<Agent>;
404
+ /**
405
+ * Remove the agent's avatar — both the GCS object and the `avatarUrl`
406
+ * field. Idempotent: calling on an agent without an avatar still
407
+ * returns the agent.
408
+ */
409
+ removeAvatar: (agentId: string) => Promise<Agent>;
380
410
  webhooks: (agentId: string) => AgentWebhooksResource;
381
411
  };
382
412
  type AgentsResource = ReturnType<typeof createAgentsResource>;
@@ -457,11 +487,12 @@ declare const createOrgsResource: (http: HttpClient) => {
457
487
  * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`
458
488
  * with the same `sk_` if you need the full admin shape.
459
489
  *
460
- * Filters:
461
- * - `tags`: OR-match on persona tags. Empty = all.
462
- * - `userTags`: tier gate. Hides agents whose `allowedUserTags` is
463
- * non-empty and doesn't intersect. Omit to skip the gate filter
464
- * (admin / dashboard usage).
490
+ * `userTags`: end-user entitlement tags. When supplied, hides agents
491
+ * whose `allowedUserTags` is non-empty and doesn't intersect with the
492
+ * supplied list. Omit to get the unfiltered admin view.
493
+ *
494
+ * Persona / category filtering is intentionally not a server param —
495
+ * filter the returned list client-side over `name`s if you need it.
465
496
  *
466
497
  * Throws `403 forbidden` if `orgId` doesn't match the key's org.
467
498
  */
package/dist/index.js CHANGED
@@ -247,6 +247,35 @@ var createAgentsResource = (http) => {
247
247
  get: async (agentId) => http.request({ method: "GET", path: `/v1/agents/${agentId}` }),
248
248
  update: async (agentId, patch) => http.request({ method: "PATCH", path: `/v1/agents/${agentId}`, body: patch }),
249
249
  delete: async (agentId) => http.request({ method: "DELETE", path: `/v1/agents/${agentId}` }),
250
+ /**
251
+ * Upload an avatar image for the agent. Server re-encodes to a 512×512
252
+ * WebP and stores it in the public-read avatars bucket; the returned
253
+ * `Agent` has `avatarUrl` set to the canonical public URL with a
254
+ * `?v=` cache-buster.
255
+ *
256
+ * `file` accepts a Node Buffer / Uint8Array / Blob / typed-array.
257
+ * `filename` and `contentType` are optional but help the server log
258
+ * meaningful errors when something rejects.
259
+ */
260
+ uploadAvatar: async (agentId, file, opts = {}) => {
261
+ const fd = new FormData();
262
+ const blob = file instanceof Blob ? file : (
263
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
+ new Blob([file], { type: opts.contentType ?? "application/octet-stream" })
265
+ );
266
+ fd.append("file", blob, opts.filename ?? "avatar");
267
+ return http.request({
268
+ method: "POST",
269
+ path: `/v1/agents/${agentId}/avatar`,
270
+ formData: fd
271
+ });
272
+ },
273
+ /**
274
+ * Remove the agent's avatar — both the GCS object and the `avatarUrl`
275
+ * field. Idempotent: calling on an agent without an avatar still
276
+ * returns the agent.
277
+ */
278
+ removeAvatar: async (agentId) => http.request({ method: "DELETE", path: `/v1/agents/${agentId}/avatar` }),
250
279
  // Nested resource. Per-agent webhook CRUD lives at
251
280
  // /v1/agents/:agentId/webhooks/* — we expose it as a factory keyed on
252
281
  // agentId so consumers can bind once and reuse:
@@ -432,17 +461,17 @@ var createOrgsResource = (http) => ({
432
461
  * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`
433
462
  * with the same `sk_` if you need the full admin shape.
434
463
  *
435
- * Filters:
436
- * - `tags`: OR-match on persona tags. Empty = all.
437
- * - `userTags`: tier gate. Hides agents whose `allowedUserTags` is
438
- * non-empty and doesn't intersect. Omit to skip the gate filter
439
- * (admin / dashboard usage).
464
+ * `userTags`: end-user entitlement tags. When supplied, hides agents
465
+ * whose `allowedUserTags` is non-empty and doesn't intersect with the
466
+ * supplied list. Omit to get the unfiltered admin view.
467
+ *
468
+ * Persona / category filtering is intentionally not a server param —
469
+ * filter the returned list client-side over `name`s if you need it.
440
470
  *
441
471
  * Throws `403 forbidden` if `orgId` doesn't match the key's org.
442
472
  */
443
473
  listAgents: async (input) => {
444
474
  const query = {};
445
- if (input.tags && input.tags.length > 0) query.tags = input.tags.join(",");
446
475
  if (input.userTags && input.userTags.length > 0) query.userTags = input.userTags.join(",");
447
476
  const res = await http.request({
448
477
  method: "GET",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/resources/me.ts","../src/resources/agentWebhooks.ts","../src/resources/agents.ts","../src/resources/calls.ts","../src/resources/knowledgeBases.ts","../src/resources/credits.ts","../src/resources/callTokens.ts","../src/resources/webhooks.ts","../src/resources/orgs.ts","../src/PlatformClient.ts","../src/verify.ts"],"sourcesContent":["// Public API of @craftedxp/sdk-node.\n\nexport { PlatformClient } from './PlatformClient'\nexport type { PlatformClientOptions } from './PlatformClient'\n\n// Error class for `instanceof` checks in consumer code.\nexport { PlatformError } from './errors'\nexport type { ApiErrorCode } from './errors'\n\n// Webhook signature verification helper — standalone so frameworks\n// (Express, Koa, Next.js route handlers) can use it without instantiating\n// a PlatformClient.\nexport { verifyWebhookSignature } from './verify'\n\n// Re-export DTO types. Consumers often type their own storage models\n// against these — re-exporting avoids `import type` gymnastics.\nexport type * from './types'\n\n// Advanced: expose the resource types for consumers subclassing / wrapping\n// the client. 99% of users don't need these.\nexport type { MeResource } from './resources/me'\nexport type { AgentsResource } from './resources/agents'\nexport type { AgentWebhooksResource } from './resources/agentWebhooks'\nexport type { CallsResource } from './resources/calls'\nexport type { KnowledgeBasesResource } from './resources/knowledgeBases'\nexport type { CreditsResource } from './resources/credits'\nexport type { CallTokensResource } from './resources/callTokens'\nexport type { WebhooksResource } from './resources/webhooks'\nexport type { OrgsResource } from './resources/orgs'\n","// Typed error class mirroring the server's ErrorV1 shape:\n// { error: { code, message, field?, docs_url? } }\n//\n// Consumers do `err instanceof PlatformError` to branch on code without\n// parsing string messages. `status` carries the HTTP code for the 1% of\n// cases where the code field isn't enough (e.g. rate-limit → retry-after\n// header correlation).\n\nexport type ApiErrorCode =\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'bad_request'\n | 'conflict'\n | 'rate_limited'\n | 'payment_required'\n | 'internal_error'\n | 'unknown'\n\nexport class PlatformError extends Error {\n readonly code: ApiErrorCode\n readonly status: number\n readonly field?: string\n readonly docsUrl?: string\n // The raw response body for debugging. Intentionally optional — we clear\n // it on `error.toJSON()` so logging libraries don't dump the whole\n // server response into production logs.\n readonly body?: unknown\n\n constructor(params: {\n code: ApiErrorCode\n message: string\n status: number\n field?: string\n docsUrl?: string\n body?: unknown\n }) {\n super(params.message)\n this.name = 'PlatformError'\n this.code = params.code\n this.status = params.status\n this.field = params.field\n this.docsUrl = params.docsUrl\n this.body = params.body\n // Preserve the stack trace — Node's Error doesn't capture it\n // automatically when subclassing in some older runtimes.\n if (\n typeof (Error as typeof Error & { captureStackTrace?: unknown }).captureStackTrace ===\n 'function'\n ) {\n ;(\n Error as typeof Error & { captureStackTrace: (target: unknown, ctor: unknown) => void }\n ).captureStackTrace(this, PlatformError)\n }\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n status: this.status,\n field: this.field,\n docsUrl: this.docsUrl,\n }\n }\n}\n","import { PlatformError, type ApiErrorCode } from './errors'\n\n// Low-level HTTP wrapper around `fetch` (native in Node 18+). Every resource\n// method routes through here for consistent auth + error handling + retries.\n//\n// v1 scope: JSON-in / JSON-out + multipart for file uploads. No streaming —\n// call WebSockets go through @craftedxp/voice-rn (the React Native client)\n// or a web equivalent, not this server-side SDK.\n\nexport interface HttpClientOptions {\n apiKey: string\n baseUrl: string\n // Default 30s. File uploads can override per-request.\n timeoutMs?: number\n // 429 + 5xx retries. Defaults to 3 attempts (original + 2 retries) with\n // exponential backoff (250ms, 1s). Set to 0 to disable.\n maxRetries?: number\n // Optional — lets consumers swap in a test/mock fetch (or a custom one\n // with instrumentation). Defaults to the global.\n fetch?: typeof fetch\n // Optional — a callback that fires per-request with the final status and\n // duration. Handy for dropping traces into the consumer's observability\n // stack without wrapping each call.\n onRequest?: (info: {\n method: string\n url: string\n status: number\n durationMs: number\n attempt: number\n }) => void\n}\n\nexport interface HttpRequest {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n path: string // starts with `/`, e.g. `/v1/agents/123`\n query?: Record<string, string | number | boolean | undefined>\n body?: unknown // JSON-serialised — for multipart use `formData`\n formData?: FormData\n timeoutMs?: number\n // Exposed for edge cases where a resource wants to tack on extra\n // headers (we don't have any today but leave the hook).\n headers?: Record<string, string>\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000\nconst DEFAULT_MAX_RETRIES = 2 // total attempts = 3\n\nconst buildUrl = (baseUrl: string, path: string, query?: HttpRequest['query']): string => {\n const u = new URL(path, baseUrl)\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue\n u.searchParams.set(k, String(v))\n }\n }\n return u.toString()\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\nconst isRetryable = (status: number): boolean => status === 429 || (status >= 500 && status < 600)\n\n// Parse whatever the server returned into a PlatformError. Falls back to\n// synthesising a sensible error for non-JSON responses.\nconst errorFromResponse = async (res: Response): Promise<PlatformError> => {\n const bodyText = await res.text().catch(() => '')\n let parsed: unknown = undefined\n try {\n parsed = bodyText ? JSON.parse(bodyText) : undefined\n } catch {\n // non-JSON response (e.g. an HTML error page from a proxy). Fall\n // through with parsed = undefined.\n }\n const errObj = (\n parsed as { error?: { code?: string; message?: string; field?: string; docs_url?: string } }\n )?.error\n return new PlatformError({\n code: (errObj?.code as ApiErrorCode) ?? 'unknown',\n message: errObj?.message ?? res.statusText ?? `HTTP ${res.status}`,\n status: res.status,\n field: errObj?.field,\n docsUrl: errObj?.docs_url,\n body: parsed ?? bodyText,\n })\n}\n\nexport const createHttpClient = (opts: HttpClientOptions) => {\n const fetchImpl = opts.fetch ?? globalThis.fetch\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES\n\n if (!fetchImpl) {\n throw new Error('No global fetch available. @craftedxp/sdk-node requires Node >= 18.')\n }\n\n const request = async <T>(req: HttpRequest): Promise<T> => {\n const url = buildUrl(opts.baseUrl, req.path, req.query)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${opts.apiKey}`,\n Accept: 'application/json',\n ...(req.headers ?? {}),\n }\n\n // Body shaping: prefer formData when provided; otherwise JSON.\n // `FormData` sets its own Content-Type with boundary — don't preempt it.\n // Typed as `string | FormData | undefined` (a subset of the global\n // BodyInit) so we don't need DOM lib types in this server-side SDK.\n let body: string | FormData | undefined\n if (req.formData) {\n body = req.formData\n } else if (req.body !== undefined) {\n headers['Content-Type'] = 'application/json'\n body = JSON.stringify(req.body)\n }\n\n const reqTimeout = req.timeoutMs ?? timeoutMs\n let lastErr: unknown\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), reqTimeout)\n const started = Date.now()\n try {\n const res = await fetchImpl(url, {\n method: req.method,\n headers,\n body,\n signal: controller.signal,\n })\n const durationMs = Date.now() - started\n opts.onRequest?.({\n method: req.method,\n url,\n status: res.status,\n durationMs,\n attempt: attempt + 1,\n })\n\n if (res.ok) {\n // 204 No Content — some endpoints (DELETE webhooks) return nothing.\n if (res.status === 204) return undefined as T\n const ct = res.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n return (await res.json()) as T\n }\n // Non-JSON 2xx — rare. Return raw text cast as T.\n return (await res.text()) as unknown as T\n }\n\n // Retryable error: back off + retry up to maxRetries.\n if (isRetryable(res.status) && attempt < maxRetries) {\n const retryAfter = res.headers.get('Retry-After')\n const backoff = retryAfter ? Number(retryAfter) * 1000 : 250 * Math.pow(2, attempt)\n await sleep(backoff)\n continue\n }\n\n throw await errorFromResponse(res)\n } catch (err) {\n if (err instanceof PlatformError) throw err\n // AbortError / network / DNS failures — retry up to maxRetries.\n if (attempt < maxRetries) {\n lastErr = err\n await sleep(250 * Math.pow(2, attempt))\n continue\n }\n const msg = err instanceof Error ? err.message : 'network error'\n throw new PlatformError({\n code: 'unknown',\n message: `Network error: ${msg}`,\n status: 0,\n })\n } finally {\n clearTimeout(timer)\n }\n }\n\n // Should be unreachable — the loop either returns, throws, or continues.\n throw lastErr instanceof Error ? lastErr : new Error('request exhausted retries')\n }\n\n return { request }\n}\n\nexport type HttpClient = ReturnType<typeof createHttpClient>\n","import type { HttpClient } from '../http'\nimport type { MeResponse } from '../types'\n\nexport const createMeResource = (http: HttpClient) => ({\n // Smoke test — confirms the API key is valid and returns the org + balance.\n // Most consumers use this as a ping on startup.\n get: async (): Promise<MeResponse> => http.request<MeResponse>({ method: 'GET', path: '/v1/me' }),\n})\n\nexport type MeResource = ReturnType<typeof createMeResource>\n","import type { HttpClient } from '../http'\nimport type {\n WebhookConfig,\n WebhookCreateInput,\n WebhookDelivery,\n WebhookUpdateInput,\n} from '../types'\n\n// Per-agent webhook resource, scoped at factory time to a specific agentId.\n// All endpoints mirror /v1/agents/:agentId/webhooks[/:id].\n\nexport const createAgentWebhooksResource = (http: HttpClient, agentId: string) => ({\n // Returns the webhook + its signing secret. The secret is only present in\n // this response — persist immediately, subsequent GETs omit it. Use the\n // secret to verify `X-Platform-Signature-256` (sha256=hex HMAC over body).\n create: async (input: WebhookCreateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks`,\n body: input,\n }),\n\n list: async (): Promise<WebhookConfig[]> => {\n const res = await http.request<{ data: WebhookConfig[] }>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks`,\n })\n return res.data\n },\n\n get: async (webhookId: string): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n update: async (webhookId: string, patch: WebhookUpdateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'PATCH',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n body: patch,\n }),\n\n delete: async (webhookId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n // Fires a synthetic call.started against this webhook and returns the\n // full delivery record (including attempt status codes) once the retry\n // sequence has finished. Useful during setup to verify receiver wiring.\n test: async (webhookId: string): Promise<WebhookDelivery> =>\n http.request<WebhookDelivery>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}/test`,\n }),\n})\n\nexport type AgentWebhooksResource = ReturnType<typeof createAgentWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { Agent, AgentCreateInput, AgentUpdateInput } from '../types'\nimport { createAgentWebhooksResource, type AgentWebhooksResource } from './agentWebhooks'\n\n// The server returns Agent with `apiKey` stripped and `hasApiKey: boolean`\n// on the model. We type the happy path but keep our input type permissive\n// so consumers can POST a plaintext `apiKey` that the server encrypts +\n// swaps for `apiKeySecret` on persistence.\n\nexport const createAgentsResource = (http: HttpClient) => {\n const agents = {\n create: async (input: AgentCreateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'POST', path: '/v1/agents', body: input }),\n\n list: async (): Promise<Agent[]> => {\n const res = await http.request<{ data: Agent[] }>({ method: 'GET', path: '/v1/agents' })\n return res.data\n },\n\n // Async iterator for \"give me every agent\" — v1 server returns the full\n // list in one page, so this is just a thin convenience. When the server\n // picks up cursor pagination, this is the method that papers over that\n // migration without consumer changes.\n listAll: async function* (): AsyncIterable<Agent> {\n const page = await agents.list()\n for (const a of page) yield a\n },\n\n get: async (agentId: string): Promise<Agent> =>\n http.request<Agent>({ method: 'GET', path: `/v1/agents/${agentId}` }),\n\n update: async (agentId: string, patch: AgentUpdateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'PATCH', path: `/v1/agents/${agentId}`, body: patch }),\n\n delete: async (agentId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/agents/${agentId}` }),\n\n // Nested resource. Per-agent webhook CRUD lives at\n // /v1/agents/:agentId/webhooks/* — we expose it as a factory keyed on\n // agentId so consumers can bind once and reuse:\n //\n // const hooks = client.agents.webhooks(myAgentId)\n // await hooks.create({ url, events })\n // await hooks.list()\n webhooks: (agentId: string): AgentWebhooksResource =>\n createAgentWebhooksResource(http, agentId),\n }\n return agents\n}\n\nexport type AgentsResource = ReturnType<typeof createAgentsResource>\n","import type { HttpClient } from '../http'\nimport type {\n CallListFilters,\n CallRecord,\n CallRecordingUrlResponse,\n CallSummary,\n TranscriptTurn,\n} from '../types'\n\nexport const createCallsResource = (http: HttpClient) => {\n const calls = {\n list: async (filters: CallListFilters = {}): Promise<CallSummary[]> => {\n const res = await http.request<{ data: CallSummary[] }>({\n method: 'GET',\n path: '/v1/calls',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination helper. v1 server caps at limit (max 200) in a single\n // page — consumers who ask for \"every call since X\" get that page, the\n // iterator ends. This is the shape we'd extend with cursor support\n // without breaking callers.\n listAll: async function* (filters: CallListFilters = {}): AsyncIterable<CallSummary> {\n const page = await calls.list(filters)\n for (const c of page) yield c\n },\n\n get: async (callId: string): Promise<CallRecord> =>\n http.request<CallRecord>({ method: 'GET', path: `/v1/calls/${callId}` }),\n\n transcript: async (callId: string): Promise<{ callId: string; transcript: TranscriptTurn[] }> =>\n http.request<{ callId: string; transcript: TranscriptTurn[] }>({\n method: 'GET',\n path: `/v1/calls/${callId}/transcript`,\n }),\n\n // Returns a V4 signed URL (1-hour TTL). `ready: false` + `artifact:\n // 'caller-raw'` means the async mix job hasn't finished — the URL still\n // points at a playable file (raw caller PCM).\n recording: async (callId: string): Promise<CallRecordingUrlResponse> =>\n http.request<CallRecordingUrlResponse>({\n method: 'GET',\n path: `/v1/calls/${callId}/recording`,\n }),\n\n // Outbound dialling (POST /v1/calls) + in-call control are Phase 1.4.4 /\n // 1.4.5 respectively — blocked on telephony. Surface clear \"not built\n // yet\" errors here if consumers guess those method names, so they don't\n // silently hit a non-existent endpoint and wonder why it 404s.\n }\n return calls\n}\n\nexport type CallsResource = ReturnType<typeof createCallsResource>\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { HttpClient } from '../http'\nimport type { KnowledgeBase, KnowledgeBaseFile } from '../types'\n\nexport const createKnowledgeBasesResource = (http: HttpClient) => ({\n create: async (name: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({\n method: 'POST',\n path: '/v1/knowledge-bases',\n body: { name },\n }),\n\n list: async (): Promise<KnowledgeBase[]> => {\n const res = await http.request<{ data: KnowledgeBase[] }>({\n method: 'GET',\n path: '/v1/knowledge-bases',\n })\n return res.data\n },\n\n get: async (kbId: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({ method: 'GET', path: `/v1/knowledge-bases/${kbId}` }),\n\n delete: async (kbId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/knowledge-bases/${kbId}` }),\n\n // File upload — accepts either a local file path OR raw bytes + filename.\n // The server ingests synchronously today (Phase 3.1.4 Cloud Tasks is a\n // followup), so the returned file has status='ready' in most cases.\n uploadFile: async (\n kbId: string,\n source:\n | { path: string; filename?: string; mimeType?: string }\n | { data: Buffer | Uint8Array; filename: string; mimeType?: string },\n ): Promise<KnowledgeBaseFile> => {\n const form = new FormData()\n let blob: Blob\n let filename: string\n let mime: string\n\n if ('path' in source) {\n const buf = await fs.promises.readFile(source.path)\n filename = source.filename ?? path.basename(source.path)\n mime = source.mimeType ?? 'application/octet-stream'\n blob = new Blob([new Uint8Array(buf)], { type: mime })\n } else {\n filename = source.filename\n mime = source.mimeType ?? 'application/octet-stream'\n const bytes = source.data instanceof Buffer ? new Uint8Array(source.data) : source.data\n blob = new Blob([bytes], { type: mime })\n }\n form.append('file', blob, filename)\n\n // File uploads can take a while (OCR, chunking, embedding) — give them\n // room before timing out. 5 min cap matches the server's own\n // processing budget.\n return http.request<KnowledgeBaseFile>({\n method: 'POST',\n path: `/v1/knowledge-bases/${kbId}/files`,\n formData: form,\n timeoutMs: 5 * 60 * 1000,\n })\n },\n\n listFiles: async (kbId: string): Promise<KnowledgeBaseFile[]> => {\n const res = await http.request<{ data: KnowledgeBaseFile[] }>({\n method: 'GET',\n path: `/v1/knowledge-bases/${kbId}/files`,\n })\n return res.data\n },\n\n deleteFile: async (kbId: string, fileId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/knowledge-bases/${kbId}/files/${fileId}`,\n }),\n})\n\nexport type KnowledgeBasesResource = ReturnType<typeof createKnowledgeBasesResource>\n","import type { HttpClient } from '../http'\nimport type { LedgerEntry } from '../types'\n\nexport const createCreditsResource = (http: HttpClient) => {\n const credits = {\n getBalance: async (): Promise<{ orgId: string; balanceCents: number }> =>\n http.request<{ orgId: string; balanceCents: number }>({\n method: 'GET',\n path: '/v1/credits/balance',\n }),\n\n // Returns ledger entries newest-first. `limit` is capped at 500 server-side.\n getLedger: async (opts: { limit?: number } = {}): Promise<LedgerEntry[]> => {\n const res = await http.request<{ data: LedgerEntry[] }>({\n method: 'GET',\n path: '/v1/credits/ledger',\n query: opts as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination scaffold — v1 server returns a single page up to\n // limit=500. When cursor pagination lands this is where it gets wired.\n getLedgerAll: async function* (opts: { limit?: number } = {}): AsyncIterable<LedgerEntry> {\n const page = await credits.getLedger(opts)\n for (const e of page) yield e\n },\n }\n return credits\n}\n\nexport type CreditsResource = ReturnType<typeof createCreditsResource>\n","import type { HttpClient } from '../http'\nimport type { CallTokenMintInput, CallTokenMintResult, CallTokenSummary } from '../types'\n\n// Short-lived, agent-scoped `ct_` tokens. Mint one per user session on your\n// backend, hand the raw value to the browser; let this SDK handle lifecycle\n// (revoke on sign-out, list active tokens for an admin panel).\n\nexport const createCallTokensResource = (http: HttpClient) => ({\n // Returns the RAW token value once. Don't log it; pass it straight to the\n // browser + discard server-side. `tokenId` is the stable public handle\n // used for revocation later.\n mint: async (input: CallTokenMintInput): Promise<CallTokenMintResult> =>\n http.request<CallTokenMintResult>({\n method: 'POST',\n path: '/v1/call-tokens',\n body: input,\n }),\n\n // Live tokens only by default. Pass includeRevoked/includeExpired when\n // debugging \"why did my token stop working\".\n list: async (\n opts: { includeRevoked?: boolean; includeExpired?: boolean; limit?: number } = {},\n ): Promise<CallTokenSummary[]> => {\n const query: Record<string, string | number | undefined> = {}\n if (opts.includeRevoked) query.includeRevoked = '1'\n if (opts.includeExpired) query.includeExpired = '1'\n if (opts.limit !== undefined) query.limit = opts.limit\n const res = await http.request<{ data: CallTokenSummary[] }>({\n method: 'GET',\n path: '/v1/call-tokens',\n query,\n })\n return res.data\n },\n\n // Idempotent — re-revoking a revoked token returns `alreadyRevoked: true`.\n revoke: async (\n tokenId: string,\n ): Promise<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }> =>\n http.request<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }>({\n method: 'DELETE',\n path: `/v1/call-tokens/${tokenId}`,\n }),\n})\n\nexport type CallTokensResource = ReturnType<typeof createCallTokensResource>\n","import type { HttpClient } from '../http'\nimport type { WebhookDelivery } from '../types'\n\n// Org-wide webhook delivery log. Per-agent CRUD lives on\n// `client.agents.webhooks(agentId)` — this namespace is just the\n// read-only deliveries surface that spans all agents.\n\nexport const createWebhooksResource = (http: HttpClient) => ({\n deliveries: async (\n filters: {\n agentId?: string\n webhookId?: string\n callId?: string\n limit?: number\n } = {},\n ): Promise<WebhookDelivery[]> => {\n const res = await http.request<{ data: WebhookDelivery[] }>({\n method: 'GET',\n path: '/v1/webhooks/deliveries',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n})\n\nexport type WebhooksResource = ReturnType<typeof createWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { CatalogAgent, CatalogListInput } from '../types'\n\n// Org-scoped consumer endpoints. Today this is just the agent catalog —\n// the trimmed shape your mobile app picks an agent from. Lives at\n// `/v1/orgs/:orgId/agents` rather than reusing the admin `/v1/agents/*`\n// tree on purpose: the catalog response intentionally excludes the\n// operator-only fields (system prompt, tools, KB IDs) so a leaked\n// consumer-backend `sk_` can't lift the operator config out of it.\n//\n// Pattern from your backend:\n//\n// const client = new PlatformClient({ apiKey: process.env.SK })\n// const free = await client.orgs.listAgents({\n// orgId: process.env.ORG_ID!,\n// userTags: ['free'], // hide premium agents from free users\n// })\n// res.json(free)\n//\n// Or with persona filtering:\n//\n// const secretaries = await client.orgs.listAgents({\n// orgId: process.env.ORG_ID!,\n// tags: ['secretary'],\n// userTags: ['paying'],\n// })\n\nexport const createOrgsResource = (http: HttpClient) => ({\n /**\n * Fetch the agent catalog for an org. Returns the consumer-trimmed shape;\n * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`\n * with the same `sk_` if you need the full admin shape.\n *\n * Filters:\n * - `tags`: OR-match on persona tags. Empty = all.\n * - `userTags`: tier gate. Hides agents whose `allowedUserTags` is\n * non-empty and doesn't intersect. Omit to skip the gate filter\n * (admin / dashboard usage).\n *\n * Throws `403 forbidden` if `orgId` doesn't match the key's org.\n */\n listAgents: async (input: CatalogListInput): Promise<CatalogAgent[]> => {\n const query: Record<string, string | undefined> = {}\n if (input.tags && input.tags.length > 0) query.tags = input.tags.join(',')\n if (input.userTags && input.userTags.length > 0) query.userTags = input.userTags.join(',')\n const res = await http.request<{ data: CatalogAgent[] }>({\n method: 'GET',\n path: `/v1/orgs/${input.orgId}/agents`,\n query,\n })\n return res.data\n },\n})\n\nexport type OrgsResource = ReturnType<typeof createOrgsResource>\n","import { createHttpClient, type HttpClientOptions } from './http'\nimport { createMeResource, type MeResource } from './resources/me'\nimport { createAgentsResource, type AgentsResource } from './resources/agents'\nimport { createCallsResource, type CallsResource } from './resources/calls'\nimport {\n createKnowledgeBasesResource,\n type KnowledgeBasesResource,\n} from './resources/knowledgeBases'\nimport { createCreditsResource, type CreditsResource } from './resources/credits'\nimport { createCallTokensResource, type CallTokensResource } from './resources/callTokens'\nimport { createWebhooksResource, type WebhooksResource } from './resources/webhooks'\nimport { createOrgsResource, type OrgsResource } from './resources/orgs'\n\nexport interface PlatformClientOptions {\n // Full-org API key minted from the dashboard or bootstrap CLI. Start with\n // `sk_`. Never ship to a browser — use `client.callTokens.mint(...)` to\n // generate a narrow `ct_` token for client-side use instead.\n apiKey: string\n // Defaults to the hosted platform. Point at `http://localhost:8080` for\n // local dev or at a self-hosted deployment.\n baseUrl?: string\n // Passthrough tuning for the HTTP layer.\n timeoutMs?: number\n maxRetries?: number\n fetch?: HttpClientOptions['fetch']\n onRequest?: HttpClientOptions['onRequest']\n}\n\n// Single entry point. All resources are lazy-constructed in the constructor\n// so their refs don't incur any per-call allocation. Shape mirrors Stripe /\n// Twilio / Vapi's `resource.method()` convention for familiarity.\nexport class PlatformClient {\n readonly me: MeResource\n readonly agents: AgentsResource\n readonly calls: CallsResource\n readonly knowledgeBases: KnowledgeBasesResource\n readonly credits: CreditsResource\n readonly callTokens: CallTokensResource\n readonly webhooks: WebhooksResource\n readonly orgs: OrgsResource\n\n constructor(options: PlatformClientOptions) {\n if (!options.apiKey) {\n throw new Error('PlatformClient: `apiKey` is required')\n }\n const http = createHttpClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl ?? 'https://api.example.com',\n timeoutMs: options.timeoutMs,\n maxRetries: options.maxRetries,\n fetch: options.fetch,\n onRequest: options.onRequest,\n })\n\n this.me = createMeResource(http)\n this.agents = createAgentsResource(http)\n this.calls = createCallsResource(http)\n this.knowledgeBases = createKnowledgeBasesResource(http)\n this.credits = createCreditsResource(http)\n this.callTokens = createCallTokensResource(http)\n this.webhooks = createWebhooksResource(http)\n this.orgs = createOrgsResource(http)\n }\n}\n","import crypto from 'node:crypto'\n\n// Helper for consumers receiving webhooks: verify the `X-Platform-Signature-256`\n// header against the raw body + secret. Plain function (not tied to\n// PlatformClient) so Express/Koa/Next.js middleware can use it without\n// instantiating a client.\n//\n// import { verifyWebhookSignature } from '@craftedxp/sdk-node'\n// app.post('/webhooks/voice-agent', express.raw({ type: 'application/json' }), (req, res) => {\n// const sig = req.header('X-Platform-Signature-256') ?? ''\n// if (!verifyWebhookSignature(req.body, sig, process.env.VOICE_AGENT_WEBHOOK_SECRET!)) {\n// return res.status(401).send('invalid signature')\n// }\n// const event = JSON.parse(req.body.toString('utf8'))\n// // ...handle event\n// })\n//\n// The signature is `sha256=<hex HMAC-SHA256 of rawBody with secret>`.\n// Timing-safe compare to avoid microtiming side channels.\n\nexport const verifyWebhookSignature = (\n rawBody: Buffer | string,\n signatureHeader: string,\n secret: string,\n): boolean => {\n if (!signatureHeader || !secret) return false\n const [algo, provided] = signatureHeader.split('=')\n if (algo !== 'sha256' || !provided) return false\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(typeof rawBody === 'string' ? rawBody : rawBody)\n .digest('hex')\n\n // Buffers must match in length for timingSafeEqual.\n const a = Buffer.from(expected, 'hex')\n const b = Buffer.from(provided, 'hex')\n if (a.length !== b.length) return false\n return crypto.timingSafeEqual(a, b)\n}\n"],"mappings":"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","names":["path","fs","path","crypto"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/resources/me.ts","../src/resources/agentWebhooks.ts","../src/resources/agents.ts","../src/resources/calls.ts","../src/resources/knowledgeBases.ts","../src/resources/credits.ts","../src/resources/callTokens.ts","../src/resources/webhooks.ts","../src/resources/orgs.ts","../src/PlatformClient.ts","../src/verify.ts"],"sourcesContent":["// Public API of @craftedxp/sdk-node.\n\nexport { PlatformClient } from './PlatformClient'\nexport type { PlatformClientOptions } from './PlatformClient'\n\n// Error class for `instanceof` checks in consumer code.\nexport { PlatformError } from './errors'\nexport type { ApiErrorCode } from './errors'\n\n// Webhook signature verification helper — standalone so frameworks\n// (Express, Koa, Next.js route handlers) can use it without instantiating\n// a PlatformClient.\nexport { verifyWebhookSignature } from './verify'\n\n// Re-export DTO types. Consumers often type their own storage models\n// against these — re-exporting avoids `import type` gymnastics.\nexport type * from './types'\n\n// Advanced: expose the resource types for consumers subclassing / wrapping\n// the client. 99% of users don't need these.\nexport type { MeResource } from './resources/me'\nexport type { AgentsResource } from './resources/agents'\nexport type { AgentWebhooksResource } from './resources/agentWebhooks'\nexport type { CallsResource } from './resources/calls'\nexport type { KnowledgeBasesResource } from './resources/knowledgeBases'\nexport type { CreditsResource } from './resources/credits'\nexport type { CallTokensResource } from './resources/callTokens'\nexport type { WebhooksResource } from './resources/webhooks'\nexport type { OrgsResource } from './resources/orgs'\n","// Typed error class mirroring the server's ErrorV1 shape:\n// { error: { code, message, field?, docs_url? } }\n//\n// Consumers do `err instanceof PlatformError` to branch on code without\n// parsing string messages. `status` carries the HTTP code for the 1% of\n// cases where the code field isn't enough (e.g. rate-limit → retry-after\n// header correlation).\n\nexport type ApiErrorCode =\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'bad_request'\n | 'conflict'\n | 'rate_limited'\n | 'payment_required'\n | 'internal_error'\n | 'unknown'\n\nexport class PlatformError extends Error {\n readonly code: ApiErrorCode\n readonly status: number\n readonly field?: string\n readonly docsUrl?: string\n // The raw response body for debugging. Intentionally optional — we clear\n // it on `error.toJSON()` so logging libraries don't dump the whole\n // server response into production logs.\n readonly body?: unknown\n\n constructor(params: {\n code: ApiErrorCode\n message: string\n status: number\n field?: string\n docsUrl?: string\n body?: unknown\n }) {\n super(params.message)\n this.name = 'PlatformError'\n this.code = params.code\n this.status = params.status\n this.field = params.field\n this.docsUrl = params.docsUrl\n this.body = params.body\n // Preserve the stack trace — Node's Error doesn't capture it\n // automatically when subclassing in some older runtimes.\n if (\n typeof (Error as typeof Error & { captureStackTrace?: unknown }).captureStackTrace ===\n 'function'\n ) {\n ;(\n Error as typeof Error & { captureStackTrace: (target: unknown, ctor: unknown) => void }\n ).captureStackTrace(this, PlatformError)\n }\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n status: this.status,\n field: this.field,\n docsUrl: this.docsUrl,\n }\n }\n}\n","import { PlatformError, type ApiErrorCode } from './errors'\n\n// Low-level HTTP wrapper around `fetch` (native in Node 18+). Every resource\n// method routes through here for consistent auth + error handling + retries.\n//\n// v1 scope: JSON-in / JSON-out + multipart for file uploads. No streaming —\n// call WebSockets go through @craftedxp/voice-rn (the React Native client)\n// or a web equivalent, not this server-side SDK.\n\nexport interface HttpClientOptions {\n apiKey: string\n baseUrl: string\n // Default 30s. File uploads can override per-request.\n timeoutMs?: number\n // 429 + 5xx retries. Defaults to 3 attempts (original + 2 retries) with\n // exponential backoff (250ms, 1s). Set to 0 to disable.\n maxRetries?: number\n // Optional — lets consumers swap in a test/mock fetch (or a custom one\n // with instrumentation). Defaults to the global.\n fetch?: typeof fetch\n // Optional — a callback that fires per-request with the final status and\n // duration. Handy for dropping traces into the consumer's observability\n // stack without wrapping each call.\n onRequest?: (info: {\n method: string\n url: string\n status: number\n durationMs: number\n attempt: number\n }) => void\n}\n\nexport interface HttpRequest {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n path: string // starts with `/`, e.g. `/v1/agents/123`\n query?: Record<string, string | number | boolean | undefined>\n body?: unknown // JSON-serialised — for multipart use `formData`\n formData?: FormData\n timeoutMs?: number\n // Exposed for edge cases where a resource wants to tack on extra\n // headers (we don't have any today but leave the hook).\n headers?: Record<string, string>\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000\nconst DEFAULT_MAX_RETRIES = 2 // total attempts = 3\n\nconst buildUrl = (baseUrl: string, path: string, query?: HttpRequest['query']): string => {\n const u = new URL(path, baseUrl)\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue\n u.searchParams.set(k, String(v))\n }\n }\n return u.toString()\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\nconst isRetryable = (status: number): boolean => status === 429 || (status >= 500 && status < 600)\n\n// Parse whatever the server returned into a PlatformError. Falls back to\n// synthesising a sensible error for non-JSON responses.\nconst errorFromResponse = async (res: Response): Promise<PlatformError> => {\n const bodyText = await res.text().catch(() => '')\n let parsed: unknown = undefined\n try {\n parsed = bodyText ? JSON.parse(bodyText) : undefined\n } catch {\n // non-JSON response (e.g. an HTML error page from a proxy). Fall\n // through with parsed = undefined.\n }\n const errObj = (\n parsed as { error?: { code?: string; message?: string; field?: string; docs_url?: string } }\n )?.error\n return new PlatformError({\n code: (errObj?.code as ApiErrorCode) ?? 'unknown',\n message: errObj?.message ?? res.statusText ?? `HTTP ${res.status}`,\n status: res.status,\n field: errObj?.field,\n docsUrl: errObj?.docs_url,\n body: parsed ?? bodyText,\n })\n}\n\nexport const createHttpClient = (opts: HttpClientOptions) => {\n const fetchImpl = opts.fetch ?? globalThis.fetch\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES\n\n if (!fetchImpl) {\n throw new Error('No global fetch available. @craftedxp/sdk-node requires Node >= 18.')\n }\n\n const request = async <T>(req: HttpRequest): Promise<T> => {\n const url = buildUrl(opts.baseUrl, req.path, req.query)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${opts.apiKey}`,\n Accept: 'application/json',\n ...(req.headers ?? {}),\n }\n\n // Body shaping: prefer formData when provided; otherwise JSON.\n // `FormData` sets its own Content-Type with boundary — don't preempt it.\n // Typed as `string | FormData | undefined` (a subset of the global\n // BodyInit) so we don't need DOM lib types in this server-side SDK.\n let body: string | FormData | undefined\n if (req.formData) {\n body = req.formData\n } else if (req.body !== undefined) {\n headers['Content-Type'] = 'application/json'\n body = JSON.stringify(req.body)\n }\n\n const reqTimeout = req.timeoutMs ?? timeoutMs\n let lastErr: unknown\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), reqTimeout)\n const started = Date.now()\n try {\n const res = await fetchImpl(url, {\n method: req.method,\n headers,\n body,\n signal: controller.signal,\n })\n const durationMs = Date.now() - started\n opts.onRequest?.({\n method: req.method,\n url,\n status: res.status,\n durationMs,\n attempt: attempt + 1,\n })\n\n if (res.ok) {\n // 204 No Content — some endpoints (DELETE webhooks) return nothing.\n if (res.status === 204) return undefined as T\n const ct = res.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n return (await res.json()) as T\n }\n // Non-JSON 2xx — rare. Return raw text cast as T.\n return (await res.text()) as unknown as T\n }\n\n // Retryable error: back off + retry up to maxRetries.\n if (isRetryable(res.status) && attempt < maxRetries) {\n const retryAfter = res.headers.get('Retry-After')\n const backoff = retryAfter ? Number(retryAfter) * 1000 : 250 * Math.pow(2, attempt)\n await sleep(backoff)\n continue\n }\n\n throw await errorFromResponse(res)\n } catch (err) {\n if (err instanceof PlatformError) throw err\n // AbortError / network / DNS failures — retry up to maxRetries.\n if (attempt < maxRetries) {\n lastErr = err\n await sleep(250 * Math.pow(2, attempt))\n continue\n }\n const msg = err instanceof Error ? err.message : 'network error'\n throw new PlatformError({\n code: 'unknown',\n message: `Network error: ${msg}`,\n status: 0,\n })\n } finally {\n clearTimeout(timer)\n }\n }\n\n // Should be unreachable — the loop either returns, throws, or continues.\n throw lastErr instanceof Error ? lastErr : new Error('request exhausted retries')\n }\n\n return { request }\n}\n\nexport type HttpClient = ReturnType<typeof createHttpClient>\n","import type { HttpClient } from '../http'\nimport type { MeResponse } from '../types'\n\nexport const createMeResource = (http: HttpClient) => ({\n // Smoke test — confirms the API key is valid and returns the org + balance.\n // Most consumers use this as a ping on startup.\n get: async (): Promise<MeResponse> => http.request<MeResponse>({ method: 'GET', path: '/v1/me' }),\n})\n\nexport type MeResource = ReturnType<typeof createMeResource>\n","import type { HttpClient } from '../http'\nimport type {\n WebhookConfig,\n WebhookCreateInput,\n WebhookDelivery,\n WebhookUpdateInput,\n} from '../types'\n\n// Per-agent webhook resource, scoped at factory time to a specific agentId.\n// All endpoints mirror /v1/agents/:agentId/webhooks[/:id].\n\nexport const createAgentWebhooksResource = (http: HttpClient, agentId: string) => ({\n // Returns the webhook + its signing secret. The secret is only present in\n // this response — persist immediately, subsequent GETs omit it. Use the\n // secret to verify `X-Platform-Signature-256` (sha256=hex HMAC over body).\n create: async (input: WebhookCreateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks`,\n body: input,\n }),\n\n list: async (): Promise<WebhookConfig[]> => {\n const res = await http.request<{ data: WebhookConfig[] }>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks`,\n })\n return res.data\n },\n\n get: async (webhookId: string): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n update: async (webhookId: string, patch: WebhookUpdateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'PATCH',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n body: patch,\n }),\n\n delete: async (webhookId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n // Fires a synthetic call.started against this webhook and returns the\n // full delivery record (including attempt status codes) once the retry\n // sequence has finished. Useful during setup to verify receiver wiring.\n test: async (webhookId: string): Promise<WebhookDelivery> =>\n http.request<WebhookDelivery>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}/test`,\n }),\n})\n\nexport type AgentWebhooksResource = ReturnType<typeof createAgentWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { Agent, AgentCreateInput, AgentUpdateInput } from '../types'\nimport { createAgentWebhooksResource, type AgentWebhooksResource } from './agentWebhooks'\n\n// The server returns Agent with `apiKey` stripped and `hasApiKey: boolean`\n// on the model. We type the happy path but keep our input type permissive\n// so consumers can POST a plaintext `apiKey` that the server encrypts +\n// swaps for `apiKeySecret` on persistence.\n\nexport const createAgentsResource = (http: HttpClient) => {\n const agents = {\n create: async (input: AgentCreateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'POST', path: '/v1/agents', body: input }),\n\n list: async (): Promise<Agent[]> => {\n const res = await http.request<{ data: Agent[] }>({ method: 'GET', path: '/v1/agents' })\n return res.data\n },\n\n // Async iterator for \"give me every agent\" — v1 server returns the full\n // list in one page, so this is just a thin convenience. When the server\n // picks up cursor pagination, this is the method that papers over that\n // migration without consumer changes.\n listAll: async function* (): AsyncIterable<Agent> {\n const page = await agents.list()\n for (const a of page) yield a\n },\n\n get: async (agentId: string): Promise<Agent> =>\n http.request<Agent>({ method: 'GET', path: `/v1/agents/${agentId}` }),\n\n update: async (agentId: string, patch: AgentUpdateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'PATCH', path: `/v1/agents/${agentId}`, body: patch }),\n\n delete: async (agentId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/agents/${agentId}` }),\n\n /**\n * Upload an avatar image for the agent. Server re-encodes to a 512×512\n * WebP and stores it in the public-read avatars bucket; the returned\n * `Agent` has `avatarUrl` set to the canonical public URL with a\n * `?v=` cache-buster.\n *\n * `file` accepts a Node Buffer / Uint8Array / Blob / typed-array.\n * `filename` and `contentType` are optional but help the server log\n * meaningful errors when something rejects.\n */\n uploadAvatar: async (\n agentId: string,\n file: Buffer | Uint8Array | Blob | ArrayBuffer,\n opts: { filename?: string; contentType?: string } = {},\n ): Promise<Agent> => {\n const fd = new FormData()\n const blob =\n file instanceof Blob\n ? file\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n new Blob([file as any], { type: opts.contentType ?? 'application/octet-stream' })\n fd.append('file', blob, opts.filename ?? 'avatar')\n return http.request<Agent>({\n method: 'POST',\n path: `/v1/agents/${agentId}/avatar`,\n formData: fd,\n })\n },\n\n /**\n * Remove the agent's avatar — both the GCS object and the `avatarUrl`\n * field. Idempotent: calling on an agent without an avatar still\n * returns the agent.\n */\n removeAvatar: async (agentId: string): Promise<Agent> =>\n http.request<Agent>({ method: 'DELETE', path: `/v1/agents/${agentId}/avatar` }),\n\n // Nested resource. Per-agent webhook CRUD lives at\n // /v1/agents/:agentId/webhooks/* — we expose it as a factory keyed on\n // agentId so consumers can bind once and reuse:\n //\n // const hooks = client.agents.webhooks(myAgentId)\n // await hooks.create({ url, events })\n // await hooks.list()\n webhooks: (agentId: string): AgentWebhooksResource =>\n createAgentWebhooksResource(http, agentId),\n }\n return agents\n}\n\nexport type AgentsResource = ReturnType<typeof createAgentsResource>\n","import type { HttpClient } from '../http'\nimport type {\n CallListFilters,\n CallRecord,\n CallRecordingUrlResponse,\n CallSummary,\n TranscriptTurn,\n} from '../types'\n\nexport const createCallsResource = (http: HttpClient) => {\n const calls = {\n list: async (filters: CallListFilters = {}): Promise<CallSummary[]> => {\n const res = await http.request<{ data: CallSummary[] }>({\n method: 'GET',\n path: '/v1/calls',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination helper. v1 server caps at limit (max 200) in a single\n // page — consumers who ask for \"every call since X\" get that page, the\n // iterator ends. This is the shape we'd extend with cursor support\n // without breaking callers.\n listAll: async function* (filters: CallListFilters = {}): AsyncIterable<CallSummary> {\n const page = await calls.list(filters)\n for (const c of page) yield c\n },\n\n get: async (callId: string): Promise<CallRecord> =>\n http.request<CallRecord>({ method: 'GET', path: `/v1/calls/${callId}` }),\n\n transcript: async (callId: string): Promise<{ callId: string; transcript: TranscriptTurn[] }> =>\n http.request<{ callId: string; transcript: TranscriptTurn[] }>({\n method: 'GET',\n path: `/v1/calls/${callId}/transcript`,\n }),\n\n // Returns a V4 signed URL (1-hour TTL). `ready: false` + `artifact:\n // 'caller-raw'` means the async mix job hasn't finished — the URL still\n // points at a playable file (raw caller PCM).\n recording: async (callId: string): Promise<CallRecordingUrlResponse> =>\n http.request<CallRecordingUrlResponse>({\n method: 'GET',\n path: `/v1/calls/${callId}/recording`,\n }),\n\n // Outbound dialling (POST /v1/calls) + in-call control are Phase 1.4.4 /\n // 1.4.5 respectively — blocked on telephony. Surface clear \"not built\n // yet\" errors here if consumers guess those method names, so they don't\n // silently hit a non-existent endpoint and wonder why it 404s.\n }\n return calls\n}\n\nexport type CallsResource = ReturnType<typeof createCallsResource>\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { HttpClient } from '../http'\nimport type { KnowledgeBase, KnowledgeBaseFile } from '../types'\n\nexport const createKnowledgeBasesResource = (http: HttpClient) => ({\n create: async (name: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({\n method: 'POST',\n path: '/v1/knowledge-bases',\n body: { name },\n }),\n\n list: async (): Promise<KnowledgeBase[]> => {\n const res = await http.request<{ data: KnowledgeBase[] }>({\n method: 'GET',\n path: '/v1/knowledge-bases',\n })\n return res.data\n },\n\n get: async (kbId: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({ method: 'GET', path: `/v1/knowledge-bases/${kbId}` }),\n\n delete: async (kbId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/knowledge-bases/${kbId}` }),\n\n // File upload — accepts either a local file path OR raw bytes + filename.\n // The server ingests synchronously today (Phase 3.1.4 Cloud Tasks is a\n // followup), so the returned file has status='ready' in most cases.\n uploadFile: async (\n kbId: string,\n source:\n | { path: string; filename?: string; mimeType?: string }\n | { data: Buffer | Uint8Array; filename: string; mimeType?: string },\n ): Promise<KnowledgeBaseFile> => {\n const form = new FormData()\n let blob: Blob\n let filename: string\n let mime: string\n\n if ('path' in source) {\n const buf = await fs.promises.readFile(source.path)\n filename = source.filename ?? path.basename(source.path)\n mime = source.mimeType ?? 'application/octet-stream'\n blob = new Blob([new Uint8Array(buf)], { type: mime })\n } else {\n filename = source.filename\n mime = source.mimeType ?? 'application/octet-stream'\n const bytes = source.data instanceof Buffer ? new Uint8Array(source.data) : source.data\n blob = new Blob([bytes], { type: mime })\n }\n form.append('file', blob, filename)\n\n // File uploads can take a while (OCR, chunking, embedding) — give them\n // room before timing out. 5 min cap matches the server's own\n // processing budget.\n return http.request<KnowledgeBaseFile>({\n method: 'POST',\n path: `/v1/knowledge-bases/${kbId}/files`,\n formData: form,\n timeoutMs: 5 * 60 * 1000,\n })\n },\n\n listFiles: async (kbId: string): Promise<KnowledgeBaseFile[]> => {\n const res = await http.request<{ data: KnowledgeBaseFile[] }>({\n method: 'GET',\n path: `/v1/knowledge-bases/${kbId}/files`,\n })\n return res.data\n },\n\n deleteFile: async (kbId: string, fileId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/knowledge-bases/${kbId}/files/${fileId}`,\n }),\n})\n\nexport type KnowledgeBasesResource = ReturnType<typeof createKnowledgeBasesResource>\n","import type { HttpClient } from '../http'\nimport type { LedgerEntry } from '../types'\n\nexport const createCreditsResource = (http: HttpClient) => {\n const credits = {\n getBalance: async (): Promise<{ orgId: string; balanceCents: number }> =>\n http.request<{ orgId: string; balanceCents: number }>({\n method: 'GET',\n path: '/v1/credits/balance',\n }),\n\n // Returns ledger entries newest-first. `limit` is capped at 500 server-side.\n getLedger: async (opts: { limit?: number } = {}): Promise<LedgerEntry[]> => {\n const res = await http.request<{ data: LedgerEntry[] }>({\n method: 'GET',\n path: '/v1/credits/ledger',\n query: opts as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination scaffold — v1 server returns a single page up to\n // limit=500. When cursor pagination lands this is where it gets wired.\n getLedgerAll: async function* (opts: { limit?: number } = {}): AsyncIterable<LedgerEntry> {\n const page = await credits.getLedger(opts)\n for (const e of page) yield e\n },\n }\n return credits\n}\n\nexport type CreditsResource = ReturnType<typeof createCreditsResource>\n","import type { HttpClient } from '../http'\nimport type { CallTokenMintInput, CallTokenMintResult, CallTokenSummary } from '../types'\n\n// Short-lived, agent-scoped `ct_` tokens. Mint one per user session on your\n// backend, hand the raw value to the browser; let this SDK handle lifecycle\n// (revoke on sign-out, list active tokens for an admin panel).\n\nexport const createCallTokensResource = (http: HttpClient) => ({\n // Returns the RAW token value once. Don't log it; pass it straight to the\n // browser + discard server-side. `tokenId` is the stable public handle\n // used for revocation later.\n mint: async (input: CallTokenMintInput): Promise<CallTokenMintResult> =>\n http.request<CallTokenMintResult>({\n method: 'POST',\n path: '/v1/call-tokens',\n body: input,\n }),\n\n // Live tokens only by default. Pass includeRevoked/includeExpired when\n // debugging \"why did my token stop working\".\n list: async (\n opts: { includeRevoked?: boolean; includeExpired?: boolean; limit?: number } = {},\n ): Promise<CallTokenSummary[]> => {\n const query: Record<string, string | number | undefined> = {}\n if (opts.includeRevoked) query.includeRevoked = '1'\n if (opts.includeExpired) query.includeExpired = '1'\n if (opts.limit !== undefined) query.limit = opts.limit\n const res = await http.request<{ data: CallTokenSummary[] }>({\n method: 'GET',\n path: '/v1/call-tokens',\n query,\n })\n return res.data\n },\n\n // Idempotent — re-revoking a revoked token returns `alreadyRevoked: true`.\n revoke: async (\n tokenId: string,\n ): Promise<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }> =>\n http.request<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }>({\n method: 'DELETE',\n path: `/v1/call-tokens/${tokenId}`,\n }),\n})\n\nexport type CallTokensResource = ReturnType<typeof createCallTokensResource>\n","import type { HttpClient } from '../http'\nimport type { WebhookDelivery } from '../types'\n\n// Org-wide webhook delivery log. Per-agent CRUD lives on\n// `client.agents.webhooks(agentId)` — this namespace is just the\n// read-only deliveries surface that spans all agents.\n\nexport const createWebhooksResource = (http: HttpClient) => ({\n deliveries: async (\n filters: {\n agentId?: string\n webhookId?: string\n callId?: string\n limit?: number\n } = {},\n ): Promise<WebhookDelivery[]> => {\n const res = await http.request<{ data: WebhookDelivery[] }>({\n method: 'GET',\n path: '/v1/webhooks/deliveries',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n})\n\nexport type WebhooksResource = ReturnType<typeof createWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { CatalogAgent, CatalogListInput } from '../types'\n\n// Org-scoped consumer endpoints. Today this is just the agent catalog —\n// the trimmed shape your mobile app picks an agent from. Lives at\n// `/v1/orgs/:orgId/agents` rather than reusing the admin `/v1/agents/*`\n// tree on purpose: the catalog response intentionally excludes the\n// operator-only fields (system prompt, tools, KB IDs) so a leaked\n// consumer-backend `sk_` can't lift the operator config out of it.\n//\n// Pattern from your backend:\n//\n// const client = new PlatformClient({ apiKey: process.env.SK })\n// const visible = await client.orgs.listAgents({\n// orgId: process.env.ORG_ID!,\n// userTags: ['tier1'], // omit to get the unfiltered admin view\n// })\n// res.json(visible)\n\nexport const createOrgsResource = (http: HttpClient) => ({\n /**\n * Fetch the agent catalog for an org. Returns the consumer-trimmed shape;\n * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`\n * with the same `sk_` if you need the full admin shape.\n *\n * `userTags`: end-user entitlement tags. When supplied, hides agents\n * whose `allowedUserTags` is non-empty and doesn't intersect with the\n * supplied list. Omit to get the unfiltered admin view.\n *\n * Persona / category filtering is intentionally not a server param —\n * filter the returned list client-side over `name`s if you need it.\n *\n * Throws `403 forbidden` if `orgId` doesn't match the key's org.\n */\n listAgents: async (input: CatalogListInput): Promise<CatalogAgent[]> => {\n const query: Record<string, string | undefined> = {}\n if (input.userTags && input.userTags.length > 0) query.userTags = input.userTags.join(',')\n const res = await http.request<{ data: CatalogAgent[] }>({\n method: 'GET',\n path: `/v1/orgs/${input.orgId}/agents`,\n query,\n })\n return res.data\n },\n})\n\nexport type OrgsResource = ReturnType<typeof createOrgsResource>\n","import { createHttpClient, type HttpClientOptions } from './http'\nimport { createMeResource, type MeResource } from './resources/me'\nimport { createAgentsResource, type AgentsResource } from './resources/agents'\nimport { createCallsResource, type CallsResource } from './resources/calls'\nimport {\n createKnowledgeBasesResource,\n type KnowledgeBasesResource,\n} from './resources/knowledgeBases'\nimport { createCreditsResource, type CreditsResource } from './resources/credits'\nimport { createCallTokensResource, type CallTokensResource } from './resources/callTokens'\nimport { createWebhooksResource, type WebhooksResource } from './resources/webhooks'\nimport { createOrgsResource, type OrgsResource } from './resources/orgs'\n\nexport interface PlatformClientOptions {\n // Full-org API key minted from the dashboard or bootstrap CLI. Start with\n // `sk_`. Never ship to a browser — use `client.callTokens.mint(...)` to\n // generate a narrow `ct_` token for client-side use instead.\n apiKey: string\n // Defaults to the hosted platform. Point at `http://localhost:8080` for\n // local dev or at a self-hosted deployment.\n baseUrl?: string\n // Passthrough tuning for the HTTP layer.\n timeoutMs?: number\n maxRetries?: number\n fetch?: HttpClientOptions['fetch']\n onRequest?: HttpClientOptions['onRequest']\n}\n\n// Single entry point. All resources are lazy-constructed in the constructor\n// so their refs don't incur any per-call allocation. Shape mirrors Stripe /\n// Twilio / Vapi's `resource.method()` convention for familiarity.\nexport class PlatformClient {\n readonly me: MeResource\n readonly agents: AgentsResource\n readonly calls: CallsResource\n readonly knowledgeBases: KnowledgeBasesResource\n readonly credits: CreditsResource\n readonly callTokens: CallTokensResource\n readonly webhooks: WebhooksResource\n readonly orgs: OrgsResource\n\n constructor(options: PlatformClientOptions) {\n if (!options.apiKey) {\n throw new Error('PlatformClient: `apiKey` is required')\n }\n const http = createHttpClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl ?? 'https://api.example.com',\n timeoutMs: options.timeoutMs,\n maxRetries: options.maxRetries,\n fetch: options.fetch,\n onRequest: options.onRequest,\n })\n\n this.me = createMeResource(http)\n this.agents = createAgentsResource(http)\n this.calls = createCallsResource(http)\n this.knowledgeBases = createKnowledgeBasesResource(http)\n this.credits = createCreditsResource(http)\n this.callTokens = createCallTokensResource(http)\n this.webhooks = createWebhooksResource(http)\n this.orgs = createOrgsResource(http)\n }\n}\n","import crypto from 'node:crypto'\n\n// Helper for consumers receiving webhooks: verify the `X-Platform-Signature-256`\n// header against the raw body + secret. Plain function (not tied to\n// PlatformClient) so Express/Koa/Next.js middleware can use it without\n// instantiating a client.\n//\n// import { verifyWebhookSignature } from '@craftedxp/sdk-node'\n// app.post('/webhooks/voice-agent', express.raw({ type: 'application/json' }), (req, res) => {\n// const sig = req.header('X-Platform-Signature-256') ?? ''\n// if (!verifyWebhookSignature(req.body, sig, process.env.VOICE_AGENT_WEBHOOK_SECRET!)) {\n// return res.status(401).send('invalid signature')\n// }\n// const event = JSON.parse(req.body.toString('utf8'))\n// // ...handle event\n// })\n//\n// The signature is `sha256=<hex HMAC-SHA256 of rawBody with secret>`.\n// Timing-safe compare to avoid microtiming side channels.\n\nexport const verifyWebhookSignature = (\n rawBody: Buffer | string,\n signatureHeader: string,\n secret: string,\n): boolean => {\n if (!signatureHeader || !secret) return false\n const [algo, provided] = signatureHeader.split('=')\n if (algo !== 'sha256' || !provided) return false\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(typeof rawBody === 'string' ? rawBody : rawBody)\n .digest('hex')\n\n // Buffers must match in length for timingSafeEqual.\n const a = Buffer.from(expected, 'hex')\n const b = Buffer.from(provided, 'hex')\n if (a.length !== b.length) return false\n return crypto.timingSafeEqual(a, b)\n}\n"],"mappings":"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","names":["path","fs","path","crypto"]}
package/dist/index.mjs CHANGED
@@ -209,6 +209,35 @@ var createAgentsResource = (http) => {
209
209
  get: async (agentId) => http.request({ method: "GET", path: `/v1/agents/${agentId}` }),
210
210
  update: async (agentId, patch) => http.request({ method: "PATCH", path: `/v1/agents/${agentId}`, body: patch }),
211
211
  delete: async (agentId) => http.request({ method: "DELETE", path: `/v1/agents/${agentId}` }),
212
+ /**
213
+ * Upload an avatar image for the agent. Server re-encodes to a 512×512
214
+ * WebP and stores it in the public-read avatars bucket; the returned
215
+ * `Agent` has `avatarUrl` set to the canonical public URL with a
216
+ * `?v=` cache-buster.
217
+ *
218
+ * `file` accepts a Node Buffer / Uint8Array / Blob / typed-array.
219
+ * `filename` and `contentType` are optional but help the server log
220
+ * meaningful errors when something rejects.
221
+ */
222
+ uploadAvatar: async (agentId, file, opts = {}) => {
223
+ const fd = new FormData();
224
+ const blob = file instanceof Blob ? file : (
225
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
+ new Blob([file], { type: opts.contentType ?? "application/octet-stream" })
227
+ );
228
+ fd.append("file", blob, opts.filename ?? "avatar");
229
+ return http.request({
230
+ method: "POST",
231
+ path: `/v1/agents/${agentId}/avatar`,
232
+ formData: fd
233
+ });
234
+ },
235
+ /**
236
+ * Remove the agent's avatar — both the GCS object and the `avatarUrl`
237
+ * field. Idempotent: calling on an agent without an avatar still
238
+ * returns the agent.
239
+ */
240
+ removeAvatar: async (agentId) => http.request({ method: "DELETE", path: `/v1/agents/${agentId}/avatar` }),
212
241
  // Nested resource. Per-agent webhook CRUD lives at
213
242
  // /v1/agents/:agentId/webhooks/* — we expose it as a factory keyed on
214
243
  // agentId so consumers can bind once and reuse:
@@ -394,17 +423,17 @@ var createOrgsResource = (http) => ({
394
423
  * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`
395
424
  * with the same `sk_` if you need the full admin shape.
396
425
  *
397
- * Filters:
398
- * - `tags`: OR-match on persona tags. Empty = all.
399
- * - `userTags`: tier gate. Hides agents whose `allowedUserTags` is
400
- * non-empty and doesn't intersect. Omit to skip the gate filter
401
- * (admin / dashboard usage).
426
+ * `userTags`: end-user entitlement tags. When supplied, hides agents
427
+ * whose `allowedUserTags` is non-empty and doesn't intersect with the
428
+ * supplied list. Omit to get the unfiltered admin view.
429
+ *
430
+ * Persona / category filtering is intentionally not a server param —
431
+ * filter the returned list client-side over `name`s if you need it.
402
432
  *
403
433
  * Throws `403 forbidden` if `orgId` doesn't match the key's org.
404
434
  */
405
435
  listAgents: async (input) => {
406
436
  const query = {};
407
- if (input.tags && input.tags.length > 0) query.tags = input.tags.join(",");
408
437
  if (input.userTags && input.userTags.length > 0) query.userTags = input.userTags.join(",");
409
438
  const res = await http.request({
410
439
  method: "GET",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/resources/me.ts","../src/resources/agentWebhooks.ts","../src/resources/agents.ts","../src/resources/calls.ts","../src/resources/knowledgeBases.ts","../src/resources/credits.ts","../src/resources/callTokens.ts","../src/resources/webhooks.ts","../src/resources/orgs.ts","../src/PlatformClient.ts","../src/verify.ts"],"sourcesContent":["// Typed error class mirroring the server's ErrorV1 shape:\n// { error: { code, message, field?, docs_url? } }\n//\n// Consumers do `err instanceof PlatformError` to branch on code without\n// parsing string messages. `status` carries the HTTP code for the 1% of\n// cases where the code field isn't enough (e.g. rate-limit → retry-after\n// header correlation).\n\nexport type ApiErrorCode =\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'bad_request'\n | 'conflict'\n | 'rate_limited'\n | 'payment_required'\n | 'internal_error'\n | 'unknown'\n\nexport class PlatformError extends Error {\n readonly code: ApiErrorCode\n readonly status: number\n readonly field?: string\n readonly docsUrl?: string\n // The raw response body for debugging. Intentionally optional — we clear\n // it on `error.toJSON()` so logging libraries don't dump the whole\n // server response into production logs.\n readonly body?: unknown\n\n constructor(params: {\n code: ApiErrorCode\n message: string\n status: number\n field?: string\n docsUrl?: string\n body?: unknown\n }) {\n super(params.message)\n this.name = 'PlatformError'\n this.code = params.code\n this.status = params.status\n this.field = params.field\n this.docsUrl = params.docsUrl\n this.body = params.body\n // Preserve the stack trace — Node's Error doesn't capture it\n // automatically when subclassing in some older runtimes.\n if (\n typeof (Error as typeof Error & { captureStackTrace?: unknown }).captureStackTrace ===\n 'function'\n ) {\n ;(\n Error as typeof Error & { captureStackTrace: (target: unknown, ctor: unknown) => void }\n ).captureStackTrace(this, PlatformError)\n }\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n status: this.status,\n field: this.field,\n docsUrl: this.docsUrl,\n }\n }\n}\n","import { PlatformError, type ApiErrorCode } from './errors'\n\n// Low-level HTTP wrapper around `fetch` (native in Node 18+). Every resource\n// method routes through here for consistent auth + error handling + retries.\n//\n// v1 scope: JSON-in / JSON-out + multipart for file uploads. No streaming —\n// call WebSockets go through @craftedxp/voice-rn (the React Native client)\n// or a web equivalent, not this server-side SDK.\n\nexport interface HttpClientOptions {\n apiKey: string\n baseUrl: string\n // Default 30s. File uploads can override per-request.\n timeoutMs?: number\n // 429 + 5xx retries. Defaults to 3 attempts (original + 2 retries) with\n // exponential backoff (250ms, 1s). Set to 0 to disable.\n maxRetries?: number\n // Optional — lets consumers swap in a test/mock fetch (or a custom one\n // with instrumentation). Defaults to the global.\n fetch?: typeof fetch\n // Optional — a callback that fires per-request with the final status and\n // duration. Handy for dropping traces into the consumer's observability\n // stack without wrapping each call.\n onRequest?: (info: {\n method: string\n url: string\n status: number\n durationMs: number\n attempt: number\n }) => void\n}\n\nexport interface HttpRequest {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n path: string // starts with `/`, e.g. `/v1/agents/123`\n query?: Record<string, string | number | boolean | undefined>\n body?: unknown // JSON-serialised — for multipart use `formData`\n formData?: FormData\n timeoutMs?: number\n // Exposed for edge cases where a resource wants to tack on extra\n // headers (we don't have any today but leave the hook).\n headers?: Record<string, string>\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000\nconst DEFAULT_MAX_RETRIES = 2 // total attempts = 3\n\nconst buildUrl = (baseUrl: string, path: string, query?: HttpRequest['query']): string => {\n const u = new URL(path, baseUrl)\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue\n u.searchParams.set(k, String(v))\n }\n }\n return u.toString()\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\nconst isRetryable = (status: number): boolean => status === 429 || (status >= 500 && status < 600)\n\n// Parse whatever the server returned into a PlatformError. Falls back to\n// synthesising a sensible error for non-JSON responses.\nconst errorFromResponse = async (res: Response): Promise<PlatformError> => {\n const bodyText = await res.text().catch(() => '')\n let parsed: unknown = undefined\n try {\n parsed = bodyText ? JSON.parse(bodyText) : undefined\n } catch {\n // non-JSON response (e.g. an HTML error page from a proxy). Fall\n // through with parsed = undefined.\n }\n const errObj = (\n parsed as { error?: { code?: string; message?: string; field?: string; docs_url?: string } }\n )?.error\n return new PlatformError({\n code: (errObj?.code as ApiErrorCode) ?? 'unknown',\n message: errObj?.message ?? res.statusText ?? `HTTP ${res.status}`,\n status: res.status,\n field: errObj?.field,\n docsUrl: errObj?.docs_url,\n body: parsed ?? bodyText,\n })\n}\n\nexport const createHttpClient = (opts: HttpClientOptions) => {\n const fetchImpl = opts.fetch ?? globalThis.fetch\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES\n\n if (!fetchImpl) {\n throw new Error('No global fetch available. @craftedxp/sdk-node requires Node >= 18.')\n }\n\n const request = async <T>(req: HttpRequest): Promise<T> => {\n const url = buildUrl(opts.baseUrl, req.path, req.query)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${opts.apiKey}`,\n Accept: 'application/json',\n ...(req.headers ?? {}),\n }\n\n // Body shaping: prefer formData when provided; otherwise JSON.\n // `FormData` sets its own Content-Type with boundary — don't preempt it.\n // Typed as `string | FormData | undefined` (a subset of the global\n // BodyInit) so we don't need DOM lib types in this server-side SDK.\n let body: string | FormData | undefined\n if (req.formData) {\n body = req.formData\n } else if (req.body !== undefined) {\n headers['Content-Type'] = 'application/json'\n body = JSON.stringify(req.body)\n }\n\n const reqTimeout = req.timeoutMs ?? timeoutMs\n let lastErr: unknown\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), reqTimeout)\n const started = Date.now()\n try {\n const res = await fetchImpl(url, {\n method: req.method,\n headers,\n body,\n signal: controller.signal,\n })\n const durationMs = Date.now() - started\n opts.onRequest?.({\n method: req.method,\n url,\n status: res.status,\n durationMs,\n attempt: attempt + 1,\n })\n\n if (res.ok) {\n // 204 No Content — some endpoints (DELETE webhooks) return nothing.\n if (res.status === 204) return undefined as T\n const ct = res.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n return (await res.json()) as T\n }\n // Non-JSON 2xx — rare. Return raw text cast as T.\n return (await res.text()) as unknown as T\n }\n\n // Retryable error: back off + retry up to maxRetries.\n if (isRetryable(res.status) && attempt < maxRetries) {\n const retryAfter = res.headers.get('Retry-After')\n const backoff = retryAfter ? Number(retryAfter) * 1000 : 250 * Math.pow(2, attempt)\n await sleep(backoff)\n continue\n }\n\n throw await errorFromResponse(res)\n } catch (err) {\n if (err instanceof PlatformError) throw err\n // AbortError / network / DNS failures — retry up to maxRetries.\n if (attempt < maxRetries) {\n lastErr = err\n await sleep(250 * Math.pow(2, attempt))\n continue\n }\n const msg = err instanceof Error ? err.message : 'network error'\n throw new PlatformError({\n code: 'unknown',\n message: `Network error: ${msg}`,\n status: 0,\n })\n } finally {\n clearTimeout(timer)\n }\n }\n\n // Should be unreachable — the loop either returns, throws, or continues.\n throw lastErr instanceof Error ? lastErr : new Error('request exhausted retries')\n }\n\n return { request }\n}\n\nexport type HttpClient = ReturnType<typeof createHttpClient>\n","import type { HttpClient } from '../http'\nimport type { MeResponse } from '../types'\n\nexport const createMeResource = (http: HttpClient) => ({\n // Smoke test — confirms the API key is valid and returns the org + balance.\n // Most consumers use this as a ping on startup.\n get: async (): Promise<MeResponse> => http.request<MeResponse>({ method: 'GET', path: '/v1/me' }),\n})\n\nexport type MeResource = ReturnType<typeof createMeResource>\n","import type { HttpClient } from '../http'\nimport type {\n WebhookConfig,\n WebhookCreateInput,\n WebhookDelivery,\n WebhookUpdateInput,\n} from '../types'\n\n// Per-agent webhook resource, scoped at factory time to a specific agentId.\n// All endpoints mirror /v1/agents/:agentId/webhooks[/:id].\n\nexport const createAgentWebhooksResource = (http: HttpClient, agentId: string) => ({\n // Returns the webhook + its signing secret. The secret is only present in\n // this response — persist immediately, subsequent GETs omit it. Use the\n // secret to verify `X-Platform-Signature-256` (sha256=hex HMAC over body).\n create: async (input: WebhookCreateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks`,\n body: input,\n }),\n\n list: async (): Promise<WebhookConfig[]> => {\n const res = await http.request<{ data: WebhookConfig[] }>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks`,\n })\n return res.data\n },\n\n get: async (webhookId: string): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n update: async (webhookId: string, patch: WebhookUpdateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'PATCH',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n body: patch,\n }),\n\n delete: async (webhookId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n // Fires a synthetic call.started against this webhook and returns the\n // full delivery record (including attempt status codes) once the retry\n // sequence has finished. Useful during setup to verify receiver wiring.\n test: async (webhookId: string): Promise<WebhookDelivery> =>\n http.request<WebhookDelivery>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}/test`,\n }),\n})\n\nexport type AgentWebhooksResource = ReturnType<typeof createAgentWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { Agent, AgentCreateInput, AgentUpdateInput } from '../types'\nimport { createAgentWebhooksResource, type AgentWebhooksResource } from './agentWebhooks'\n\n// The server returns Agent with `apiKey` stripped and `hasApiKey: boolean`\n// on the model. We type the happy path but keep our input type permissive\n// so consumers can POST a plaintext `apiKey` that the server encrypts +\n// swaps for `apiKeySecret` on persistence.\n\nexport const createAgentsResource = (http: HttpClient) => {\n const agents = {\n create: async (input: AgentCreateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'POST', path: '/v1/agents', body: input }),\n\n list: async (): Promise<Agent[]> => {\n const res = await http.request<{ data: Agent[] }>({ method: 'GET', path: '/v1/agents' })\n return res.data\n },\n\n // Async iterator for \"give me every agent\" — v1 server returns the full\n // list in one page, so this is just a thin convenience. When the server\n // picks up cursor pagination, this is the method that papers over that\n // migration without consumer changes.\n listAll: async function* (): AsyncIterable<Agent> {\n const page = await agents.list()\n for (const a of page) yield a\n },\n\n get: async (agentId: string): Promise<Agent> =>\n http.request<Agent>({ method: 'GET', path: `/v1/agents/${agentId}` }),\n\n update: async (agentId: string, patch: AgentUpdateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'PATCH', path: `/v1/agents/${agentId}`, body: patch }),\n\n delete: async (agentId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/agents/${agentId}` }),\n\n // Nested resource. Per-agent webhook CRUD lives at\n // /v1/agents/:agentId/webhooks/* — we expose it as a factory keyed on\n // agentId so consumers can bind once and reuse:\n //\n // const hooks = client.agents.webhooks(myAgentId)\n // await hooks.create({ url, events })\n // await hooks.list()\n webhooks: (agentId: string): AgentWebhooksResource =>\n createAgentWebhooksResource(http, agentId),\n }\n return agents\n}\n\nexport type AgentsResource = ReturnType<typeof createAgentsResource>\n","import type { HttpClient } from '../http'\nimport type {\n CallListFilters,\n CallRecord,\n CallRecordingUrlResponse,\n CallSummary,\n TranscriptTurn,\n} from '../types'\n\nexport const createCallsResource = (http: HttpClient) => {\n const calls = {\n list: async (filters: CallListFilters = {}): Promise<CallSummary[]> => {\n const res = await http.request<{ data: CallSummary[] }>({\n method: 'GET',\n path: '/v1/calls',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination helper. v1 server caps at limit (max 200) in a single\n // page — consumers who ask for \"every call since X\" get that page, the\n // iterator ends. This is the shape we'd extend with cursor support\n // without breaking callers.\n listAll: async function* (filters: CallListFilters = {}): AsyncIterable<CallSummary> {\n const page = await calls.list(filters)\n for (const c of page) yield c\n },\n\n get: async (callId: string): Promise<CallRecord> =>\n http.request<CallRecord>({ method: 'GET', path: `/v1/calls/${callId}` }),\n\n transcript: async (callId: string): Promise<{ callId: string; transcript: TranscriptTurn[] }> =>\n http.request<{ callId: string; transcript: TranscriptTurn[] }>({\n method: 'GET',\n path: `/v1/calls/${callId}/transcript`,\n }),\n\n // Returns a V4 signed URL (1-hour TTL). `ready: false` + `artifact:\n // 'caller-raw'` means the async mix job hasn't finished — the URL still\n // points at a playable file (raw caller PCM).\n recording: async (callId: string): Promise<CallRecordingUrlResponse> =>\n http.request<CallRecordingUrlResponse>({\n method: 'GET',\n path: `/v1/calls/${callId}/recording`,\n }),\n\n // Outbound dialling (POST /v1/calls) + in-call control are Phase 1.4.4 /\n // 1.4.5 respectively — blocked on telephony. Surface clear \"not built\n // yet\" errors here if consumers guess those method names, so they don't\n // silently hit a non-existent endpoint and wonder why it 404s.\n }\n return calls\n}\n\nexport type CallsResource = ReturnType<typeof createCallsResource>\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { HttpClient } from '../http'\nimport type { KnowledgeBase, KnowledgeBaseFile } from '../types'\n\nexport const createKnowledgeBasesResource = (http: HttpClient) => ({\n create: async (name: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({\n method: 'POST',\n path: '/v1/knowledge-bases',\n body: { name },\n }),\n\n list: async (): Promise<KnowledgeBase[]> => {\n const res = await http.request<{ data: KnowledgeBase[] }>({\n method: 'GET',\n path: '/v1/knowledge-bases',\n })\n return res.data\n },\n\n get: async (kbId: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({ method: 'GET', path: `/v1/knowledge-bases/${kbId}` }),\n\n delete: async (kbId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/knowledge-bases/${kbId}` }),\n\n // File upload — accepts either a local file path OR raw bytes + filename.\n // The server ingests synchronously today (Phase 3.1.4 Cloud Tasks is a\n // followup), so the returned file has status='ready' in most cases.\n uploadFile: async (\n kbId: string,\n source:\n | { path: string; filename?: string; mimeType?: string }\n | { data: Buffer | Uint8Array; filename: string; mimeType?: string },\n ): Promise<KnowledgeBaseFile> => {\n const form = new FormData()\n let blob: Blob\n let filename: string\n let mime: string\n\n if ('path' in source) {\n const buf = await fs.promises.readFile(source.path)\n filename = source.filename ?? path.basename(source.path)\n mime = source.mimeType ?? 'application/octet-stream'\n blob = new Blob([new Uint8Array(buf)], { type: mime })\n } else {\n filename = source.filename\n mime = source.mimeType ?? 'application/octet-stream'\n const bytes = source.data instanceof Buffer ? new Uint8Array(source.data) : source.data\n blob = new Blob([bytes], { type: mime })\n }\n form.append('file', blob, filename)\n\n // File uploads can take a while (OCR, chunking, embedding) — give them\n // room before timing out. 5 min cap matches the server's own\n // processing budget.\n return http.request<KnowledgeBaseFile>({\n method: 'POST',\n path: `/v1/knowledge-bases/${kbId}/files`,\n formData: form,\n timeoutMs: 5 * 60 * 1000,\n })\n },\n\n listFiles: async (kbId: string): Promise<KnowledgeBaseFile[]> => {\n const res = await http.request<{ data: KnowledgeBaseFile[] }>({\n method: 'GET',\n path: `/v1/knowledge-bases/${kbId}/files`,\n })\n return res.data\n },\n\n deleteFile: async (kbId: string, fileId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/knowledge-bases/${kbId}/files/${fileId}`,\n }),\n})\n\nexport type KnowledgeBasesResource = ReturnType<typeof createKnowledgeBasesResource>\n","import type { HttpClient } from '../http'\nimport type { LedgerEntry } from '../types'\n\nexport const createCreditsResource = (http: HttpClient) => {\n const credits = {\n getBalance: async (): Promise<{ orgId: string; balanceCents: number }> =>\n http.request<{ orgId: string; balanceCents: number }>({\n method: 'GET',\n path: '/v1/credits/balance',\n }),\n\n // Returns ledger entries newest-first. `limit` is capped at 500 server-side.\n getLedger: async (opts: { limit?: number } = {}): Promise<LedgerEntry[]> => {\n const res = await http.request<{ data: LedgerEntry[] }>({\n method: 'GET',\n path: '/v1/credits/ledger',\n query: opts as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination scaffold — v1 server returns a single page up to\n // limit=500. When cursor pagination lands this is where it gets wired.\n getLedgerAll: async function* (opts: { limit?: number } = {}): AsyncIterable<LedgerEntry> {\n const page = await credits.getLedger(opts)\n for (const e of page) yield e\n },\n }\n return credits\n}\n\nexport type CreditsResource = ReturnType<typeof createCreditsResource>\n","import type { HttpClient } from '../http'\nimport type { CallTokenMintInput, CallTokenMintResult, CallTokenSummary } from '../types'\n\n// Short-lived, agent-scoped `ct_` tokens. Mint one per user session on your\n// backend, hand the raw value to the browser; let this SDK handle lifecycle\n// (revoke on sign-out, list active tokens for an admin panel).\n\nexport const createCallTokensResource = (http: HttpClient) => ({\n // Returns the RAW token value once. Don't log it; pass it straight to the\n // browser + discard server-side. `tokenId` is the stable public handle\n // used for revocation later.\n mint: async (input: CallTokenMintInput): Promise<CallTokenMintResult> =>\n http.request<CallTokenMintResult>({\n method: 'POST',\n path: '/v1/call-tokens',\n body: input,\n }),\n\n // Live tokens only by default. Pass includeRevoked/includeExpired when\n // debugging \"why did my token stop working\".\n list: async (\n opts: { includeRevoked?: boolean; includeExpired?: boolean; limit?: number } = {},\n ): Promise<CallTokenSummary[]> => {\n const query: Record<string, string | number | undefined> = {}\n if (opts.includeRevoked) query.includeRevoked = '1'\n if (opts.includeExpired) query.includeExpired = '1'\n if (opts.limit !== undefined) query.limit = opts.limit\n const res = await http.request<{ data: CallTokenSummary[] }>({\n method: 'GET',\n path: '/v1/call-tokens',\n query,\n })\n return res.data\n },\n\n // Idempotent — re-revoking a revoked token returns `alreadyRevoked: true`.\n revoke: async (\n tokenId: string,\n ): Promise<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }> =>\n http.request<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }>({\n method: 'DELETE',\n path: `/v1/call-tokens/${tokenId}`,\n }),\n})\n\nexport type CallTokensResource = ReturnType<typeof createCallTokensResource>\n","import type { HttpClient } from '../http'\nimport type { WebhookDelivery } from '../types'\n\n// Org-wide webhook delivery log. Per-agent CRUD lives on\n// `client.agents.webhooks(agentId)` — this namespace is just the\n// read-only deliveries surface that spans all agents.\n\nexport const createWebhooksResource = (http: HttpClient) => ({\n deliveries: async (\n filters: {\n agentId?: string\n webhookId?: string\n callId?: string\n limit?: number\n } = {},\n ): Promise<WebhookDelivery[]> => {\n const res = await http.request<{ data: WebhookDelivery[] }>({\n method: 'GET',\n path: '/v1/webhooks/deliveries',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n})\n\nexport type WebhooksResource = ReturnType<typeof createWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { CatalogAgent, CatalogListInput } from '../types'\n\n// Org-scoped consumer endpoints. Today this is just the agent catalog —\n// the trimmed shape your mobile app picks an agent from. Lives at\n// `/v1/orgs/:orgId/agents` rather than reusing the admin `/v1/agents/*`\n// tree on purpose: the catalog response intentionally excludes the\n// operator-only fields (system prompt, tools, KB IDs) so a leaked\n// consumer-backend `sk_` can't lift the operator config out of it.\n//\n// Pattern from your backend:\n//\n// const client = new PlatformClient({ apiKey: process.env.SK })\n// const free = await client.orgs.listAgents({\n// orgId: process.env.ORG_ID!,\n// userTags: ['free'], // hide premium agents from free users\n// })\n// res.json(free)\n//\n// Or with persona filtering:\n//\n// const secretaries = await client.orgs.listAgents({\n// orgId: process.env.ORG_ID!,\n// tags: ['secretary'],\n// userTags: ['paying'],\n// })\n\nexport const createOrgsResource = (http: HttpClient) => ({\n /**\n * Fetch the agent catalog for an org. Returns the consumer-trimmed shape;\n * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`\n * with the same `sk_` if you need the full admin shape.\n *\n * Filters:\n * - `tags`: OR-match on persona tags. Empty = all.\n * - `userTags`: tier gate. Hides agents whose `allowedUserTags` is\n * non-empty and doesn't intersect. Omit to skip the gate filter\n * (admin / dashboard usage).\n *\n * Throws `403 forbidden` if `orgId` doesn't match the key's org.\n */\n listAgents: async (input: CatalogListInput): Promise<CatalogAgent[]> => {\n const query: Record<string, string | undefined> = {}\n if (input.tags && input.tags.length > 0) query.tags = input.tags.join(',')\n if (input.userTags && input.userTags.length > 0) query.userTags = input.userTags.join(',')\n const res = await http.request<{ data: CatalogAgent[] }>({\n method: 'GET',\n path: `/v1/orgs/${input.orgId}/agents`,\n query,\n })\n return res.data\n },\n})\n\nexport type OrgsResource = ReturnType<typeof createOrgsResource>\n","import { createHttpClient, type HttpClientOptions } from './http'\nimport { createMeResource, type MeResource } from './resources/me'\nimport { createAgentsResource, type AgentsResource } from './resources/agents'\nimport { createCallsResource, type CallsResource } from './resources/calls'\nimport {\n createKnowledgeBasesResource,\n type KnowledgeBasesResource,\n} from './resources/knowledgeBases'\nimport { createCreditsResource, type CreditsResource } from './resources/credits'\nimport { createCallTokensResource, type CallTokensResource } from './resources/callTokens'\nimport { createWebhooksResource, type WebhooksResource } from './resources/webhooks'\nimport { createOrgsResource, type OrgsResource } from './resources/orgs'\n\nexport interface PlatformClientOptions {\n // Full-org API key minted from the dashboard or bootstrap CLI. Start with\n // `sk_`. Never ship to a browser — use `client.callTokens.mint(...)` to\n // generate a narrow `ct_` token for client-side use instead.\n apiKey: string\n // Defaults to the hosted platform. Point at `http://localhost:8080` for\n // local dev or at a self-hosted deployment.\n baseUrl?: string\n // Passthrough tuning for the HTTP layer.\n timeoutMs?: number\n maxRetries?: number\n fetch?: HttpClientOptions['fetch']\n onRequest?: HttpClientOptions['onRequest']\n}\n\n// Single entry point. All resources are lazy-constructed in the constructor\n// so their refs don't incur any per-call allocation. Shape mirrors Stripe /\n// Twilio / Vapi's `resource.method()` convention for familiarity.\nexport class PlatformClient {\n readonly me: MeResource\n readonly agents: AgentsResource\n readonly calls: CallsResource\n readonly knowledgeBases: KnowledgeBasesResource\n readonly credits: CreditsResource\n readonly callTokens: CallTokensResource\n readonly webhooks: WebhooksResource\n readonly orgs: OrgsResource\n\n constructor(options: PlatformClientOptions) {\n if (!options.apiKey) {\n throw new Error('PlatformClient: `apiKey` is required')\n }\n const http = createHttpClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl ?? 'https://api.example.com',\n timeoutMs: options.timeoutMs,\n maxRetries: options.maxRetries,\n fetch: options.fetch,\n onRequest: options.onRequest,\n })\n\n this.me = createMeResource(http)\n this.agents = createAgentsResource(http)\n this.calls = createCallsResource(http)\n this.knowledgeBases = createKnowledgeBasesResource(http)\n this.credits = createCreditsResource(http)\n this.callTokens = createCallTokensResource(http)\n this.webhooks = createWebhooksResource(http)\n this.orgs = createOrgsResource(http)\n }\n}\n","import crypto from 'node:crypto'\n\n// Helper for consumers receiving webhooks: verify the `X-Platform-Signature-256`\n// header against the raw body + secret. Plain function (not tied to\n// PlatformClient) so Express/Koa/Next.js middleware can use it without\n// instantiating a client.\n//\n// import { verifyWebhookSignature } from '@craftedxp/sdk-node'\n// app.post('/webhooks/voice-agent', express.raw({ type: 'application/json' }), (req, res) => {\n// const sig = req.header('X-Platform-Signature-256') ?? ''\n// if (!verifyWebhookSignature(req.body, sig, process.env.VOICE_AGENT_WEBHOOK_SECRET!)) {\n// return res.status(401).send('invalid signature')\n// }\n// const event = JSON.parse(req.body.toString('utf8'))\n// // ...handle event\n// })\n//\n// The signature is `sha256=<hex HMAC-SHA256 of rawBody with secret>`.\n// Timing-safe compare to avoid microtiming side channels.\n\nexport const verifyWebhookSignature = (\n rawBody: Buffer | string,\n signatureHeader: string,\n secret: string,\n): boolean => {\n if (!signatureHeader || !secret) return false\n const [algo, provided] = signatureHeader.split('=')\n if (algo !== 'sha256' || !provided) return false\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(typeof rawBody === 'string' ? rawBody : rawBody)\n .digest('hex')\n\n // Buffers must match in length for timingSafeEqual.\n const a = Buffer.from(expected, 'hex')\n const b = Buffer.from(provided, 'hex')\n if (a.length !== b.length) return false\n return crypto.timingSafeEqual(a, b)\n}\n"],"mappings":"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","names":["path"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/resources/me.ts","../src/resources/agentWebhooks.ts","../src/resources/agents.ts","../src/resources/calls.ts","../src/resources/knowledgeBases.ts","../src/resources/credits.ts","../src/resources/callTokens.ts","../src/resources/webhooks.ts","../src/resources/orgs.ts","../src/PlatformClient.ts","../src/verify.ts"],"sourcesContent":["// Typed error class mirroring the server's ErrorV1 shape:\n// { error: { code, message, field?, docs_url? } }\n//\n// Consumers do `err instanceof PlatformError` to branch on code without\n// parsing string messages. `status` carries the HTTP code for the 1% of\n// cases where the code field isn't enough (e.g. rate-limit → retry-after\n// header correlation).\n\nexport type ApiErrorCode =\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'bad_request'\n | 'conflict'\n | 'rate_limited'\n | 'payment_required'\n | 'internal_error'\n | 'unknown'\n\nexport class PlatformError extends Error {\n readonly code: ApiErrorCode\n readonly status: number\n readonly field?: string\n readonly docsUrl?: string\n // The raw response body for debugging. Intentionally optional — we clear\n // it on `error.toJSON()` so logging libraries don't dump the whole\n // server response into production logs.\n readonly body?: unknown\n\n constructor(params: {\n code: ApiErrorCode\n message: string\n status: number\n field?: string\n docsUrl?: string\n body?: unknown\n }) {\n super(params.message)\n this.name = 'PlatformError'\n this.code = params.code\n this.status = params.status\n this.field = params.field\n this.docsUrl = params.docsUrl\n this.body = params.body\n // Preserve the stack trace — Node's Error doesn't capture it\n // automatically when subclassing in some older runtimes.\n if (\n typeof (Error as typeof Error & { captureStackTrace?: unknown }).captureStackTrace ===\n 'function'\n ) {\n ;(\n Error as typeof Error & { captureStackTrace: (target: unknown, ctor: unknown) => void }\n ).captureStackTrace(this, PlatformError)\n }\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n status: this.status,\n field: this.field,\n docsUrl: this.docsUrl,\n }\n }\n}\n","import { PlatformError, type ApiErrorCode } from './errors'\n\n// Low-level HTTP wrapper around `fetch` (native in Node 18+). Every resource\n// method routes through here for consistent auth + error handling + retries.\n//\n// v1 scope: JSON-in / JSON-out + multipart for file uploads. No streaming —\n// call WebSockets go through @craftedxp/voice-rn (the React Native client)\n// or a web equivalent, not this server-side SDK.\n\nexport interface HttpClientOptions {\n apiKey: string\n baseUrl: string\n // Default 30s. File uploads can override per-request.\n timeoutMs?: number\n // 429 + 5xx retries. Defaults to 3 attempts (original + 2 retries) with\n // exponential backoff (250ms, 1s). Set to 0 to disable.\n maxRetries?: number\n // Optional — lets consumers swap in a test/mock fetch (or a custom one\n // with instrumentation). Defaults to the global.\n fetch?: typeof fetch\n // Optional — a callback that fires per-request with the final status and\n // duration. Handy for dropping traces into the consumer's observability\n // stack without wrapping each call.\n onRequest?: (info: {\n method: string\n url: string\n status: number\n durationMs: number\n attempt: number\n }) => void\n}\n\nexport interface HttpRequest {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n path: string // starts with `/`, e.g. `/v1/agents/123`\n query?: Record<string, string | number | boolean | undefined>\n body?: unknown // JSON-serialised — for multipart use `formData`\n formData?: FormData\n timeoutMs?: number\n // Exposed for edge cases where a resource wants to tack on extra\n // headers (we don't have any today but leave the hook).\n headers?: Record<string, string>\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000\nconst DEFAULT_MAX_RETRIES = 2 // total attempts = 3\n\nconst buildUrl = (baseUrl: string, path: string, query?: HttpRequest['query']): string => {\n const u = new URL(path, baseUrl)\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue\n u.searchParams.set(k, String(v))\n }\n }\n return u.toString()\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\nconst isRetryable = (status: number): boolean => status === 429 || (status >= 500 && status < 600)\n\n// Parse whatever the server returned into a PlatformError. Falls back to\n// synthesising a sensible error for non-JSON responses.\nconst errorFromResponse = async (res: Response): Promise<PlatformError> => {\n const bodyText = await res.text().catch(() => '')\n let parsed: unknown = undefined\n try {\n parsed = bodyText ? JSON.parse(bodyText) : undefined\n } catch {\n // non-JSON response (e.g. an HTML error page from a proxy). Fall\n // through with parsed = undefined.\n }\n const errObj = (\n parsed as { error?: { code?: string; message?: string; field?: string; docs_url?: string } }\n )?.error\n return new PlatformError({\n code: (errObj?.code as ApiErrorCode) ?? 'unknown',\n message: errObj?.message ?? res.statusText ?? `HTTP ${res.status}`,\n status: res.status,\n field: errObj?.field,\n docsUrl: errObj?.docs_url,\n body: parsed ?? bodyText,\n })\n}\n\nexport const createHttpClient = (opts: HttpClientOptions) => {\n const fetchImpl = opts.fetch ?? globalThis.fetch\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES\n\n if (!fetchImpl) {\n throw new Error('No global fetch available. @craftedxp/sdk-node requires Node >= 18.')\n }\n\n const request = async <T>(req: HttpRequest): Promise<T> => {\n const url = buildUrl(opts.baseUrl, req.path, req.query)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${opts.apiKey}`,\n Accept: 'application/json',\n ...(req.headers ?? {}),\n }\n\n // Body shaping: prefer formData when provided; otherwise JSON.\n // `FormData` sets its own Content-Type with boundary — don't preempt it.\n // Typed as `string | FormData | undefined` (a subset of the global\n // BodyInit) so we don't need DOM lib types in this server-side SDK.\n let body: string | FormData | undefined\n if (req.formData) {\n body = req.formData\n } else if (req.body !== undefined) {\n headers['Content-Type'] = 'application/json'\n body = JSON.stringify(req.body)\n }\n\n const reqTimeout = req.timeoutMs ?? timeoutMs\n let lastErr: unknown\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), reqTimeout)\n const started = Date.now()\n try {\n const res = await fetchImpl(url, {\n method: req.method,\n headers,\n body,\n signal: controller.signal,\n })\n const durationMs = Date.now() - started\n opts.onRequest?.({\n method: req.method,\n url,\n status: res.status,\n durationMs,\n attempt: attempt + 1,\n })\n\n if (res.ok) {\n // 204 No Content — some endpoints (DELETE webhooks) return nothing.\n if (res.status === 204) return undefined as T\n const ct = res.headers.get('content-type') ?? ''\n if (ct.includes('application/json')) {\n return (await res.json()) as T\n }\n // Non-JSON 2xx — rare. Return raw text cast as T.\n return (await res.text()) as unknown as T\n }\n\n // Retryable error: back off + retry up to maxRetries.\n if (isRetryable(res.status) && attempt < maxRetries) {\n const retryAfter = res.headers.get('Retry-After')\n const backoff = retryAfter ? Number(retryAfter) * 1000 : 250 * Math.pow(2, attempt)\n await sleep(backoff)\n continue\n }\n\n throw await errorFromResponse(res)\n } catch (err) {\n if (err instanceof PlatformError) throw err\n // AbortError / network / DNS failures — retry up to maxRetries.\n if (attempt < maxRetries) {\n lastErr = err\n await sleep(250 * Math.pow(2, attempt))\n continue\n }\n const msg = err instanceof Error ? err.message : 'network error'\n throw new PlatformError({\n code: 'unknown',\n message: `Network error: ${msg}`,\n status: 0,\n })\n } finally {\n clearTimeout(timer)\n }\n }\n\n // Should be unreachable — the loop either returns, throws, or continues.\n throw lastErr instanceof Error ? lastErr : new Error('request exhausted retries')\n }\n\n return { request }\n}\n\nexport type HttpClient = ReturnType<typeof createHttpClient>\n","import type { HttpClient } from '../http'\nimport type { MeResponse } from '../types'\n\nexport const createMeResource = (http: HttpClient) => ({\n // Smoke test — confirms the API key is valid and returns the org + balance.\n // Most consumers use this as a ping on startup.\n get: async (): Promise<MeResponse> => http.request<MeResponse>({ method: 'GET', path: '/v1/me' }),\n})\n\nexport type MeResource = ReturnType<typeof createMeResource>\n","import type { HttpClient } from '../http'\nimport type {\n WebhookConfig,\n WebhookCreateInput,\n WebhookDelivery,\n WebhookUpdateInput,\n} from '../types'\n\n// Per-agent webhook resource, scoped at factory time to a specific agentId.\n// All endpoints mirror /v1/agents/:agentId/webhooks[/:id].\n\nexport const createAgentWebhooksResource = (http: HttpClient, agentId: string) => ({\n // Returns the webhook + its signing secret. The secret is only present in\n // this response — persist immediately, subsequent GETs omit it. Use the\n // secret to verify `X-Platform-Signature-256` (sha256=hex HMAC over body).\n create: async (input: WebhookCreateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks`,\n body: input,\n }),\n\n list: async (): Promise<WebhookConfig[]> => {\n const res = await http.request<{ data: WebhookConfig[] }>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks`,\n })\n return res.data\n },\n\n get: async (webhookId: string): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'GET',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n update: async (webhookId: string, patch: WebhookUpdateInput): Promise<WebhookConfig> =>\n http.request<WebhookConfig>({\n method: 'PATCH',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n body: patch,\n }),\n\n delete: async (webhookId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}`,\n }),\n\n // Fires a synthetic call.started against this webhook and returns the\n // full delivery record (including attempt status codes) once the retry\n // sequence has finished. Useful during setup to verify receiver wiring.\n test: async (webhookId: string): Promise<WebhookDelivery> =>\n http.request<WebhookDelivery>({\n method: 'POST',\n path: `/v1/agents/${agentId}/webhooks/${webhookId}/test`,\n }),\n})\n\nexport type AgentWebhooksResource = ReturnType<typeof createAgentWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { Agent, AgentCreateInput, AgentUpdateInput } from '../types'\nimport { createAgentWebhooksResource, type AgentWebhooksResource } from './agentWebhooks'\n\n// The server returns Agent with `apiKey` stripped and `hasApiKey: boolean`\n// on the model. We type the happy path but keep our input type permissive\n// so consumers can POST a plaintext `apiKey` that the server encrypts +\n// swaps for `apiKeySecret` on persistence.\n\nexport const createAgentsResource = (http: HttpClient) => {\n const agents = {\n create: async (input: AgentCreateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'POST', path: '/v1/agents', body: input }),\n\n list: async (): Promise<Agent[]> => {\n const res = await http.request<{ data: Agent[] }>({ method: 'GET', path: '/v1/agents' })\n return res.data\n },\n\n // Async iterator for \"give me every agent\" — v1 server returns the full\n // list in one page, so this is just a thin convenience. When the server\n // picks up cursor pagination, this is the method that papers over that\n // migration without consumer changes.\n listAll: async function* (): AsyncIterable<Agent> {\n const page = await agents.list()\n for (const a of page) yield a\n },\n\n get: async (agentId: string): Promise<Agent> =>\n http.request<Agent>({ method: 'GET', path: `/v1/agents/${agentId}` }),\n\n update: async (agentId: string, patch: AgentUpdateInput): Promise<Agent> =>\n http.request<Agent>({ method: 'PATCH', path: `/v1/agents/${agentId}`, body: patch }),\n\n delete: async (agentId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/agents/${agentId}` }),\n\n /**\n * Upload an avatar image for the agent. Server re-encodes to a 512×512\n * WebP and stores it in the public-read avatars bucket; the returned\n * `Agent` has `avatarUrl` set to the canonical public URL with a\n * `?v=` cache-buster.\n *\n * `file` accepts a Node Buffer / Uint8Array / Blob / typed-array.\n * `filename` and `contentType` are optional but help the server log\n * meaningful errors when something rejects.\n */\n uploadAvatar: async (\n agentId: string,\n file: Buffer | Uint8Array | Blob | ArrayBuffer,\n opts: { filename?: string; contentType?: string } = {},\n ): Promise<Agent> => {\n const fd = new FormData()\n const blob =\n file instanceof Blob\n ? file\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n new Blob([file as any], { type: opts.contentType ?? 'application/octet-stream' })\n fd.append('file', blob, opts.filename ?? 'avatar')\n return http.request<Agent>({\n method: 'POST',\n path: `/v1/agents/${agentId}/avatar`,\n formData: fd,\n })\n },\n\n /**\n * Remove the agent's avatar — both the GCS object and the `avatarUrl`\n * field. Idempotent: calling on an agent without an avatar still\n * returns the agent.\n */\n removeAvatar: async (agentId: string): Promise<Agent> =>\n http.request<Agent>({ method: 'DELETE', path: `/v1/agents/${agentId}/avatar` }),\n\n // Nested resource. Per-agent webhook CRUD lives at\n // /v1/agents/:agentId/webhooks/* — we expose it as a factory keyed on\n // agentId so consumers can bind once and reuse:\n //\n // const hooks = client.agents.webhooks(myAgentId)\n // await hooks.create({ url, events })\n // await hooks.list()\n webhooks: (agentId: string): AgentWebhooksResource =>\n createAgentWebhooksResource(http, agentId),\n }\n return agents\n}\n\nexport type AgentsResource = ReturnType<typeof createAgentsResource>\n","import type { HttpClient } from '../http'\nimport type {\n CallListFilters,\n CallRecord,\n CallRecordingUrlResponse,\n CallSummary,\n TranscriptTurn,\n} from '../types'\n\nexport const createCallsResource = (http: HttpClient) => {\n const calls = {\n list: async (filters: CallListFilters = {}): Promise<CallSummary[]> => {\n const res = await http.request<{ data: CallSummary[] }>({\n method: 'GET',\n path: '/v1/calls',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination helper. v1 server caps at limit (max 200) in a single\n // page — consumers who ask for \"every call since X\" get that page, the\n // iterator ends. This is the shape we'd extend with cursor support\n // without breaking callers.\n listAll: async function* (filters: CallListFilters = {}): AsyncIterable<CallSummary> {\n const page = await calls.list(filters)\n for (const c of page) yield c\n },\n\n get: async (callId: string): Promise<CallRecord> =>\n http.request<CallRecord>({ method: 'GET', path: `/v1/calls/${callId}` }),\n\n transcript: async (callId: string): Promise<{ callId: string; transcript: TranscriptTurn[] }> =>\n http.request<{ callId: string; transcript: TranscriptTurn[] }>({\n method: 'GET',\n path: `/v1/calls/${callId}/transcript`,\n }),\n\n // Returns a V4 signed URL (1-hour TTL). `ready: false` + `artifact:\n // 'caller-raw'` means the async mix job hasn't finished — the URL still\n // points at a playable file (raw caller PCM).\n recording: async (callId: string): Promise<CallRecordingUrlResponse> =>\n http.request<CallRecordingUrlResponse>({\n method: 'GET',\n path: `/v1/calls/${callId}/recording`,\n }),\n\n // Outbound dialling (POST /v1/calls) + in-call control are Phase 1.4.4 /\n // 1.4.5 respectively — blocked on telephony. Surface clear \"not built\n // yet\" errors here if consumers guess those method names, so they don't\n // silently hit a non-existent endpoint and wonder why it 404s.\n }\n return calls\n}\n\nexport type CallsResource = ReturnType<typeof createCallsResource>\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { HttpClient } from '../http'\nimport type { KnowledgeBase, KnowledgeBaseFile } from '../types'\n\nexport const createKnowledgeBasesResource = (http: HttpClient) => ({\n create: async (name: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({\n method: 'POST',\n path: '/v1/knowledge-bases',\n body: { name },\n }),\n\n list: async (): Promise<KnowledgeBase[]> => {\n const res = await http.request<{ data: KnowledgeBase[] }>({\n method: 'GET',\n path: '/v1/knowledge-bases',\n })\n return res.data\n },\n\n get: async (kbId: string): Promise<KnowledgeBase> =>\n http.request<KnowledgeBase>({ method: 'GET', path: `/v1/knowledge-bases/${kbId}` }),\n\n delete: async (kbId: string): Promise<void> =>\n http.request<void>({ method: 'DELETE', path: `/v1/knowledge-bases/${kbId}` }),\n\n // File upload — accepts either a local file path OR raw bytes + filename.\n // The server ingests synchronously today (Phase 3.1.4 Cloud Tasks is a\n // followup), so the returned file has status='ready' in most cases.\n uploadFile: async (\n kbId: string,\n source:\n | { path: string; filename?: string; mimeType?: string }\n | { data: Buffer | Uint8Array; filename: string; mimeType?: string },\n ): Promise<KnowledgeBaseFile> => {\n const form = new FormData()\n let blob: Blob\n let filename: string\n let mime: string\n\n if ('path' in source) {\n const buf = await fs.promises.readFile(source.path)\n filename = source.filename ?? path.basename(source.path)\n mime = source.mimeType ?? 'application/octet-stream'\n blob = new Blob([new Uint8Array(buf)], { type: mime })\n } else {\n filename = source.filename\n mime = source.mimeType ?? 'application/octet-stream'\n const bytes = source.data instanceof Buffer ? new Uint8Array(source.data) : source.data\n blob = new Blob([bytes], { type: mime })\n }\n form.append('file', blob, filename)\n\n // File uploads can take a while (OCR, chunking, embedding) — give them\n // room before timing out. 5 min cap matches the server's own\n // processing budget.\n return http.request<KnowledgeBaseFile>({\n method: 'POST',\n path: `/v1/knowledge-bases/${kbId}/files`,\n formData: form,\n timeoutMs: 5 * 60 * 1000,\n })\n },\n\n listFiles: async (kbId: string): Promise<KnowledgeBaseFile[]> => {\n const res = await http.request<{ data: KnowledgeBaseFile[] }>({\n method: 'GET',\n path: `/v1/knowledge-bases/${kbId}/files`,\n })\n return res.data\n },\n\n deleteFile: async (kbId: string, fileId: string): Promise<void> =>\n http.request<void>({\n method: 'DELETE',\n path: `/v1/knowledge-bases/${kbId}/files/${fileId}`,\n }),\n})\n\nexport type KnowledgeBasesResource = ReturnType<typeof createKnowledgeBasesResource>\n","import type { HttpClient } from '../http'\nimport type { LedgerEntry } from '../types'\n\nexport const createCreditsResource = (http: HttpClient) => {\n const credits = {\n getBalance: async (): Promise<{ orgId: string; balanceCents: number }> =>\n http.request<{ orgId: string; balanceCents: number }>({\n method: 'GET',\n path: '/v1/credits/balance',\n }),\n\n // Returns ledger entries newest-first. `limit` is capped at 500 server-side.\n getLedger: async (opts: { limit?: number } = {}): Promise<LedgerEntry[]> => {\n const res = await http.request<{ data: LedgerEntry[] }>({\n method: 'GET',\n path: '/v1/credits/ledger',\n query: opts as Record<string, string | number | undefined>,\n })\n return res.data\n },\n\n // Auto-pagination scaffold — v1 server returns a single page up to\n // limit=500. When cursor pagination lands this is where it gets wired.\n getLedgerAll: async function* (opts: { limit?: number } = {}): AsyncIterable<LedgerEntry> {\n const page = await credits.getLedger(opts)\n for (const e of page) yield e\n },\n }\n return credits\n}\n\nexport type CreditsResource = ReturnType<typeof createCreditsResource>\n","import type { HttpClient } from '../http'\nimport type { CallTokenMintInput, CallTokenMintResult, CallTokenSummary } from '../types'\n\n// Short-lived, agent-scoped `ct_` tokens. Mint one per user session on your\n// backend, hand the raw value to the browser; let this SDK handle lifecycle\n// (revoke on sign-out, list active tokens for an admin panel).\n\nexport const createCallTokensResource = (http: HttpClient) => ({\n // Returns the RAW token value once. Don't log it; pass it straight to the\n // browser + discard server-side. `tokenId` is the stable public handle\n // used for revocation later.\n mint: async (input: CallTokenMintInput): Promise<CallTokenMintResult> =>\n http.request<CallTokenMintResult>({\n method: 'POST',\n path: '/v1/call-tokens',\n body: input,\n }),\n\n // Live tokens only by default. Pass includeRevoked/includeExpired when\n // debugging \"why did my token stop working\".\n list: async (\n opts: { includeRevoked?: boolean; includeExpired?: boolean; limit?: number } = {},\n ): Promise<CallTokenSummary[]> => {\n const query: Record<string, string | number | undefined> = {}\n if (opts.includeRevoked) query.includeRevoked = '1'\n if (opts.includeExpired) query.includeExpired = '1'\n if (opts.limit !== undefined) query.limit = opts.limit\n const res = await http.request<{ data: CallTokenSummary[] }>({\n method: 'GET',\n path: '/v1/call-tokens',\n query,\n })\n return res.data\n },\n\n // Idempotent — re-revoking a revoked token returns `alreadyRevoked: true`.\n revoke: async (\n tokenId: string,\n ): Promise<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }> =>\n http.request<{ tokenId: string; revoked: boolean; alreadyRevoked?: boolean }>({\n method: 'DELETE',\n path: `/v1/call-tokens/${tokenId}`,\n }),\n})\n\nexport type CallTokensResource = ReturnType<typeof createCallTokensResource>\n","import type { HttpClient } from '../http'\nimport type { WebhookDelivery } from '../types'\n\n// Org-wide webhook delivery log. Per-agent CRUD lives on\n// `client.agents.webhooks(agentId)` — this namespace is just the\n// read-only deliveries surface that spans all agents.\n\nexport const createWebhooksResource = (http: HttpClient) => ({\n deliveries: async (\n filters: {\n agentId?: string\n webhookId?: string\n callId?: string\n limit?: number\n } = {},\n ): Promise<WebhookDelivery[]> => {\n const res = await http.request<{ data: WebhookDelivery[] }>({\n method: 'GET',\n path: '/v1/webhooks/deliveries',\n query: filters as Record<string, string | number | undefined>,\n })\n return res.data\n },\n})\n\nexport type WebhooksResource = ReturnType<typeof createWebhooksResource>\n","import type { HttpClient } from '../http'\nimport type { CatalogAgent, CatalogListInput } from '../types'\n\n// Org-scoped consumer endpoints. Today this is just the agent catalog —\n// the trimmed shape your mobile app picks an agent from. Lives at\n// `/v1/orgs/:orgId/agents` rather than reusing the admin `/v1/agents/*`\n// tree on purpose: the catalog response intentionally excludes the\n// operator-only fields (system prompt, tools, KB IDs) so a leaked\n// consumer-backend `sk_` can't lift the operator config out of it.\n//\n// Pattern from your backend:\n//\n// const client = new PlatformClient({ apiKey: process.env.SK })\n// const visible = await client.orgs.listAgents({\n// orgId: process.env.ORG_ID!,\n// userTags: ['tier1'], // omit to get the unfiltered admin view\n// })\n// res.json(visible)\n\nexport const createOrgsResource = (http: HttpClient) => ({\n /**\n * Fetch the agent catalog for an org. Returns the consumer-trimmed shape;\n * no system prompt, tools, or KB info. Use `client.agents.get(agentId)`\n * with the same `sk_` if you need the full admin shape.\n *\n * `userTags`: end-user entitlement tags. When supplied, hides agents\n * whose `allowedUserTags` is non-empty and doesn't intersect with the\n * supplied list. Omit to get the unfiltered admin view.\n *\n * Persona / category filtering is intentionally not a server param —\n * filter the returned list client-side over `name`s if you need it.\n *\n * Throws `403 forbidden` if `orgId` doesn't match the key's org.\n */\n listAgents: async (input: CatalogListInput): Promise<CatalogAgent[]> => {\n const query: Record<string, string | undefined> = {}\n if (input.userTags && input.userTags.length > 0) query.userTags = input.userTags.join(',')\n const res = await http.request<{ data: CatalogAgent[] }>({\n method: 'GET',\n path: `/v1/orgs/${input.orgId}/agents`,\n query,\n })\n return res.data\n },\n})\n\nexport type OrgsResource = ReturnType<typeof createOrgsResource>\n","import { createHttpClient, type HttpClientOptions } from './http'\nimport { createMeResource, type MeResource } from './resources/me'\nimport { createAgentsResource, type AgentsResource } from './resources/agents'\nimport { createCallsResource, type CallsResource } from './resources/calls'\nimport {\n createKnowledgeBasesResource,\n type KnowledgeBasesResource,\n} from './resources/knowledgeBases'\nimport { createCreditsResource, type CreditsResource } from './resources/credits'\nimport { createCallTokensResource, type CallTokensResource } from './resources/callTokens'\nimport { createWebhooksResource, type WebhooksResource } from './resources/webhooks'\nimport { createOrgsResource, type OrgsResource } from './resources/orgs'\n\nexport interface PlatformClientOptions {\n // Full-org API key minted from the dashboard or bootstrap CLI. Start with\n // `sk_`. Never ship to a browser — use `client.callTokens.mint(...)` to\n // generate a narrow `ct_` token for client-side use instead.\n apiKey: string\n // Defaults to the hosted platform. Point at `http://localhost:8080` for\n // local dev or at a self-hosted deployment.\n baseUrl?: string\n // Passthrough tuning for the HTTP layer.\n timeoutMs?: number\n maxRetries?: number\n fetch?: HttpClientOptions['fetch']\n onRequest?: HttpClientOptions['onRequest']\n}\n\n// Single entry point. All resources are lazy-constructed in the constructor\n// so their refs don't incur any per-call allocation. Shape mirrors Stripe /\n// Twilio / Vapi's `resource.method()` convention for familiarity.\nexport class PlatformClient {\n readonly me: MeResource\n readonly agents: AgentsResource\n readonly calls: CallsResource\n readonly knowledgeBases: KnowledgeBasesResource\n readonly credits: CreditsResource\n readonly callTokens: CallTokensResource\n readonly webhooks: WebhooksResource\n readonly orgs: OrgsResource\n\n constructor(options: PlatformClientOptions) {\n if (!options.apiKey) {\n throw new Error('PlatformClient: `apiKey` is required')\n }\n const http = createHttpClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl ?? 'https://api.example.com',\n timeoutMs: options.timeoutMs,\n maxRetries: options.maxRetries,\n fetch: options.fetch,\n onRequest: options.onRequest,\n })\n\n this.me = createMeResource(http)\n this.agents = createAgentsResource(http)\n this.calls = createCallsResource(http)\n this.knowledgeBases = createKnowledgeBasesResource(http)\n this.credits = createCreditsResource(http)\n this.callTokens = createCallTokensResource(http)\n this.webhooks = createWebhooksResource(http)\n this.orgs = createOrgsResource(http)\n }\n}\n","import crypto from 'node:crypto'\n\n// Helper for consumers receiving webhooks: verify the `X-Platform-Signature-256`\n// header against the raw body + secret. Plain function (not tied to\n// PlatformClient) so Express/Koa/Next.js middleware can use it without\n// instantiating a client.\n//\n// import { verifyWebhookSignature } from '@craftedxp/sdk-node'\n// app.post('/webhooks/voice-agent', express.raw({ type: 'application/json' }), (req, res) => {\n// const sig = req.header('X-Platform-Signature-256') ?? ''\n// if (!verifyWebhookSignature(req.body, sig, process.env.VOICE_AGENT_WEBHOOK_SECRET!)) {\n// return res.status(401).send('invalid signature')\n// }\n// const event = JSON.parse(req.body.toString('utf8'))\n// // ...handle event\n// })\n//\n// The signature is `sha256=<hex HMAC-SHA256 of rawBody with secret>`.\n// Timing-safe compare to avoid microtiming side channels.\n\nexport const verifyWebhookSignature = (\n rawBody: Buffer | string,\n signatureHeader: string,\n secret: string,\n): boolean => {\n if (!signatureHeader || !secret) return false\n const [algo, provided] = signatureHeader.split('=')\n if (algo !== 'sha256' || !provided) return false\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(typeof rawBody === 'string' ? rawBody : rawBody)\n .digest('hex')\n\n // Buffers must match in length for timingSafeEqual.\n const a = Buffer.from(expected, 'hex')\n const b = Buffer.from(provided, 'hex')\n if (a.length !== b.length) return false\n return crypto.timingSafeEqual(a, b)\n}\n"],"mappings":";AAmBO,IAAM,gBAAN,MAAM,uBAAsB,MAAM;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EAET,YAAY,QAOT;AACD,UAAM,OAAO,OAAO;AACpB,SAAK,OAAO;AACZ,SAAK,OAAO,OAAO;AACnB,SAAK,SAAS,OAAO;AACrB,SAAK,QAAQ,OAAO;AACpB,SAAK,UAAU,OAAO;AACtB,SAAK,OAAO,OAAO;AAGnB,QACE,OAAQ,MAAyD,sBACjE,YACA;AACA;AAAC,MACC,MACA,kBAAkB,MAAM,cAAa;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,SAAkC;AAChC,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAChB;AAAA,EACF;AACF;;;ACtBA,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAE5B,IAAM,WAAW,CAAC,SAAiBA,OAAc,UAAyC;AACxF,QAAM,IAAI,IAAI,IAAIA,OAAM,OAAO;AAC/B,MAAI,OAAO;AACT,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,UAAI,MAAM,OAAW;AACrB,QAAE,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,IACjC;AAAA,EACF;AACA,SAAO,EAAE,SAAS;AACpB;AAEA,IAAM,QAAQ,CAAC,OAAe,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAExE,IAAM,cAAc,CAAC,WAA4B,WAAW,OAAQ,UAAU,OAAO,SAAS;AAI9F,IAAM,oBAAoB,OAAO,QAA0C;AACzE,QAAM,WAAW,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAChD,MAAI,SAAkB;AACtB,MAAI;AACF,aAAS,WAAW,KAAK,MAAM,QAAQ,IAAI;AAAA,EAC7C,QAAQ;AAAA,EAGR;AACA,QAAM,SACJ,QACC;AACH,SAAO,IAAI,cAAc;AAAA,IACvB,MAAO,QAAQ,QAAyB;AAAA,IACxC,SAAS,QAAQ,WAAW,IAAI,cAAc,QAAQ,IAAI,MAAM;AAAA,IAChE,QAAQ,IAAI;AAAA,IACZ,OAAO,QAAQ;AAAA,IACf,SAAS,QAAQ;AAAA,IACjB,MAAM,UAAU;AAAA,EAClB,CAAC;AACH;AAEO,IAAM,mBAAmB,CAAC,SAA4B;AAC3D,QAAM,YAAY,KAAK,SAAS,WAAW;AAC3C,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,aAAa,KAAK,cAAc;AAEtC,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,qEAAqE;AAAA,EACvF;AAEA,QAAM,UAAU,OAAU,QAAiC;AACzD,UAAM,MAAM,SAAS,KAAK,SAAS,IAAI,MAAM,IAAI,KAAK;AACtD,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,QAAQ;AAAA,MACR,GAAI,IAAI,WAAW,CAAC;AAAA,IACtB;AAMA,QAAI;AACJ,QAAI,IAAI,UAAU;AAChB,aAAO,IAAI;AAAA,IACb,WAAW,IAAI,SAAS,QAAW;AACjC,cAAQ,cAAc,IAAI;AAC1B,aAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IAChC;AAEA,UAAM,aAAa,IAAI,aAAa;AACpC,QAAI;AAEJ,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,UAAU;AAC7D,YAAM,UAAU,KAAK,IAAI;AACzB,UAAI;AACF,cAAM,MAAM,MAAM,UAAU,KAAK;AAAA,UAC/B,QAAQ,IAAI;AAAA,UACZ;AAAA,UACA;AAAA,UACA,QAAQ,WAAW;AAAA,QACrB,CAAC;AACD,cAAM,aAAa,KAAK,IAAI,IAAI;AAChC,aAAK,YAAY;AAAA,UACf,QAAQ,IAAI;AAAA,UACZ;AAAA,UACA,QAAQ,IAAI;AAAA,UACZ;AAAA,UACA,SAAS,UAAU;AAAA,QACrB,CAAC;AAED,YAAI,IAAI,IAAI;AAEV,cAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,gBAAM,KAAK,IAAI,QAAQ,IAAI,cAAc,KAAK;AAC9C,cAAI,GAAG,SAAS,kBAAkB,GAAG;AACnC,mBAAQ,MAAM,IAAI,KAAK;AAAA,UACzB;AAEA,iBAAQ,MAAM,IAAI,KAAK;AAAA,QACzB;AAGA,YAAI,YAAY,IAAI,MAAM,KAAK,UAAU,YAAY;AACnD,gBAAM,aAAa,IAAI,QAAQ,IAAI,aAAa;AAChD,gBAAM,UAAU,aAAa,OAAO,UAAU,IAAI,MAAO,MAAM,KAAK,IAAI,GAAG,OAAO;AAClF,gBAAM,MAAM,OAAO;AACnB;AAAA,QACF;AAEA,cAAM,MAAM,kBAAkB,GAAG;AAAA,MACnC,SAAS,KAAK;AACZ,YAAI,eAAe,cAAe,OAAM;AAExC,YAAI,UAAU,YAAY;AACxB,oBAAU;AACV,gBAAM,MAAM,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;AACtC;AAAA,QACF;AACA,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,cAAM,IAAI,cAAc;AAAA,UACtB,MAAM;AAAA,UACN,SAAS,kBAAkB,GAAG;AAAA,UAC9B,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAGA,UAAM,mBAAmB,QAAQ,UAAU,IAAI,MAAM,2BAA2B;AAAA,EAClF;AAEA,SAAO,EAAE,QAAQ;AACnB;;;ACnLO,IAAM,mBAAmB,CAAC,UAAsB;AAAA;AAAA;AAAA,EAGrD,KAAK,YAAiC,KAAK,QAAoB,EAAE,QAAQ,OAAO,MAAM,SAAS,CAAC;AAClG;;;ACIO,IAAM,8BAA8B,CAAC,MAAkB,aAAqB;AAAA;AAAA;AAAA;AAAA,EAIjF,QAAQ,OAAO,UACb,KAAK,QAAuB;AAAA,IAC1B,QAAQ;AAAA,IACR,MAAM,cAAc,OAAO;AAAA,IAC3B,MAAM;AAAA,EACR,CAAC;AAAA,EAEH,MAAM,YAAsC;AAC1C,UAAM,MAAM,MAAM,KAAK,QAAmC;AAAA,MACxD,QAAQ;AAAA,MACR,MAAM,cAAc,OAAO;AAAA,IAC7B,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,KAAK,OAAO,cACV,KAAK,QAAuB;AAAA,IAC1B,QAAQ;AAAA,IACR,MAAM,cAAc,OAAO,aAAa,SAAS;AAAA,EACnD,CAAC;AAAA,EAEH,QAAQ,OAAO,WAAmB,UAChC,KAAK,QAAuB;AAAA,IAC1B,QAAQ;AAAA,IACR,MAAM,cAAc,OAAO,aAAa,SAAS;AAAA,IACjD,MAAM;AAAA,EACR,CAAC;AAAA,EAEH,QAAQ,OAAO,cACb,KAAK,QAAc;AAAA,IACjB,QAAQ;AAAA,IACR,MAAM,cAAc,OAAO,aAAa,SAAS;AAAA,EACnD,CAAC;AAAA;AAAA;AAAA;AAAA,EAKH,MAAM,OAAO,cACX,KAAK,QAAyB;AAAA,IAC5B,QAAQ;AAAA,IACR,MAAM,cAAc,OAAO,aAAa,SAAS;AAAA,EACnD,CAAC;AACL;;;AChDO,IAAM,uBAAuB,CAAC,SAAqB;AACxD,QAAM,SAAS;AAAA,IACb,QAAQ,OAAO,UACb,KAAK,QAAe,EAAE,QAAQ,QAAQ,MAAM,cAAc,MAAM,MAAM,CAAC;AAAA,IAEzE,MAAM,YAA8B;AAClC,YAAM,MAAM,MAAM,KAAK,QAA2B,EAAE,QAAQ,OAAO,MAAM,aAAa,CAAC;AACvF,aAAO,IAAI;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,SAAS,mBAAyC;AAChD,YAAM,OAAO,MAAM,OAAO,KAAK;AAC/B,iBAAW,KAAK,KAAM,OAAM;AAAA,IAC9B;AAAA,IAEA,KAAK,OAAO,YACV,KAAK,QAAe,EAAE,QAAQ,OAAO,MAAM,cAAc,OAAO,GAAG,CAAC;AAAA,IAEtE,QAAQ,OAAO,SAAiB,UAC9B,KAAK,QAAe,EAAE,QAAQ,SAAS,MAAM,cAAc,OAAO,IAAI,MAAM,MAAM,CAAC;AAAA,IAErF,QAAQ,OAAO,YACb,KAAK,QAAc,EAAE,QAAQ,UAAU,MAAM,cAAc,OAAO,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYxE,cAAc,OACZ,SACA,MACA,OAAoD,CAAC,MAClC;AACnB,YAAM,KAAK,IAAI,SAAS;AACxB,YAAM,OACJ,gBAAgB,OACZ;AAAA;AAAA,QAEA,IAAI,KAAK,CAAC,IAAW,GAAG,EAAE,MAAM,KAAK,eAAe,2BAA2B,CAAC;AAAA;AACtF,SAAG,OAAO,QAAQ,MAAM,KAAK,YAAY,QAAQ;AACjD,aAAO,KAAK,QAAe;AAAA,QACzB,QAAQ;AAAA,QACR,MAAM,cAAc,OAAO;AAAA,QAC3B,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,cAAc,OAAO,YACnB,KAAK,QAAe,EAAE,QAAQ,UAAU,MAAM,cAAc,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAShF,UAAU,CAAC,YACT,4BAA4B,MAAM,OAAO;AAAA,EAC7C;AACA,SAAO;AACT;;;AC5EO,IAAM,sBAAsB,CAAC,SAAqB;AACvD,QAAM,QAAQ;AAAA,IACZ,MAAM,OAAO,UAA2B,CAAC,MAA8B;AACrE,YAAM,MAAM,MAAM,KAAK,QAAiC;AAAA,QACtD,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,IAAI;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,SAAS,iBAAiB,UAA2B,CAAC,GAA+B;AACnF,YAAM,OAAO,MAAM,MAAM,KAAK,OAAO;AACrC,iBAAW,KAAK,KAAM,OAAM;AAAA,IAC9B;AAAA,IAEA,KAAK,OAAO,WACV,KAAK,QAAoB,EAAE,QAAQ,OAAO,MAAM,aAAa,MAAM,GAAG,CAAC;AAAA,IAEzE,YAAY,OAAO,WACjB,KAAK,QAA0D;AAAA,MAC7D,QAAQ;AAAA,MACR,MAAM,aAAa,MAAM;AAAA,IAC3B,CAAC;AAAA;AAAA;AAAA;AAAA,IAKH,WAAW,OAAO,WAChB,KAAK,QAAkC;AAAA,MACrC,QAAQ;AAAA,MACR,MAAM,aAAa,MAAM;AAAA,IAC3B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAML;AACA,SAAO;AACT;;;ACrDA,OAAO,QAAQ;AACf,OAAO,UAAU;AAIV,IAAM,+BAA+B,CAAC,UAAsB;AAAA,EACjE,QAAQ,OAAO,SACb,KAAK,QAAuB;AAAA,IAC1B,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,MAAM,EAAE,KAAK;AAAA,EACf,CAAC;AAAA,EAEH,MAAM,YAAsC;AAC1C,UAAM,MAAM,MAAM,KAAK,QAAmC;AAAA,MACxD,QAAQ;AAAA,MACR,MAAM;AAAA,IACR,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,KAAK,OAAO,SACV,KAAK,QAAuB,EAAE,QAAQ,OAAO,MAAM,uBAAuB,IAAI,GAAG,CAAC;AAAA,EAEpF,QAAQ,OAAO,SACb,KAAK,QAAc,EAAE,QAAQ,UAAU,MAAM,uBAAuB,IAAI,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA,EAK9E,YAAY,OACV,MACA,WAG+B;AAC/B,UAAM,OAAO,IAAI,SAAS;AAC1B,QAAI;AACJ,QAAI;AACJ,QAAI;AAEJ,QAAI,UAAU,QAAQ;AACpB,YAAM,MAAM,MAAM,GAAG,SAAS,SAAS,OAAO,IAAI;AAClD,iBAAW,OAAO,YAAY,KAAK,SAAS,OAAO,IAAI;AACvD,aAAO,OAAO,YAAY;AAC1B,aAAO,IAAI,KAAK,CAAC,IAAI,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,IACvD,OAAO;AACL,iBAAW,OAAO;AAClB,aAAO,OAAO,YAAY;AAC1B,YAAM,QAAQ,OAAO,gBAAgB,SAAS,IAAI,WAAW,OAAO,IAAI,IAAI,OAAO;AACnF,aAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,IACzC;AACA,SAAK,OAAO,QAAQ,MAAM,QAAQ;AAKlC,WAAO,KAAK,QAA2B;AAAA,MACrC,QAAQ;AAAA,MACR,MAAM,uBAAuB,IAAI;AAAA,MACjC,UAAU;AAAA,MACV,WAAW,IAAI,KAAK;AAAA,IACtB,CAAC;AAAA,EACH;AAAA,EAEA,WAAW,OAAO,SAA+C;AAC/D,UAAM,MAAM,MAAM,KAAK,QAAuC;AAAA,MAC5D,QAAQ;AAAA,MACR,MAAM,uBAAuB,IAAI;AAAA,IACnC,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,YAAY,OAAO,MAAc,WAC/B,KAAK,QAAc;AAAA,IACjB,QAAQ;AAAA,IACR,MAAM,uBAAuB,IAAI,UAAU,MAAM;AAAA,EACnD,CAAC;AACL;;;AC3EO,IAAM,wBAAwB,CAAC,SAAqB;AACzD,QAAM,UAAU;AAAA,IACd,YAAY,YACV,KAAK,QAAiD;AAAA,MACpD,QAAQ;AAAA,MACR,MAAM;AAAA,IACR,CAAC;AAAA;AAAA,IAGH,WAAW,OAAO,OAA2B,CAAC,MAA8B;AAC1E,YAAM,MAAM,MAAM,KAAK,QAAiC;AAAA,QACtD,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,IAAI;AAAA,IACb;AAAA;AAAA;AAAA,IAIA,cAAc,iBAAiB,OAA2B,CAAC,GAA+B;AACxF,YAAM,OAAO,MAAM,QAAQ,UAAU,IAAI;AACzC,iBAAW,KAAK,KAAM,OAAM;AAAA,IAC9B;AAAA,EACF;AACA,SAAO;AACT;;;ACtBO,IAAM,2BAA2B,CAAC,UAAsB;AAAA;AAAA;AAAA;AAAA,EAI7D,MAAM,OAAO,UACX,KAAK,QAA6B;AAAA,IAChC,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,MAAM;AAAA,EACR,CAAC;AAAA;AAAA;AAAA,EAIH,MAAM,OACJ,OAA+E,CAAC,MAChD;AAChC,UAAM,QAAqD,CAAC;AAC5D,QAAI,KAAK,eAAgB,OAAM,iBAAiB;AAChD,QAAI,KAAK,eAAgB,OAAM,iBAAiB;AAChD,QAAI,KAAK,UAAU,OAAW,OAAM,QAAQ,KAAK;AACjD,UAAM,MAAM,MAAM,KAAK,QAAsC;AAAA,MAC3D,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAGA,QAAQ,OACN,YAEA,KAAK,QAAyE;AAAA,IAC5E,QAAQ;AAAA,IACR,MAAM,mBAAmB,OAAO;AAAA,EAClC,CAAC;AACL;;;ACpCO,IAAM,yBAAyB,CAAC,UAAsB;AAAA,EAC3D,YAAY,OACV,UAKI,CAAC,MAC0B;AAC/B,UAAM,MAAM,MAAM,KAAK,QAAqC;AAAA,MAC1D,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IACT,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AACF;;;ACJO,IAAM,qBAAqB,CAAC,UAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAevD,YAAY,OAAO,UAAqD;AACtE,UAAM,QAA4C,CAAC;AACnD,QAAI,MAAM,YAAY,MAAM,SAAS,SAAS,EAAG,OAAM,WAAW,MAAM,SAAS,KAAK,GAAG;AACzF,UAAM,MAAM,MAAM,KAAK,QAAkC;AAAA,MACvD,QAAQ;AAAA,MACR,MAAM,YAAY,MAAM,KAAK;AAAA,MAC7B;AAAA,IACF,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AACF;;;ACbO,IAAM,iBAAN,MAAqB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,SAAgC;AAC1C,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,UAAM,OAAO,iBAAiB;AAAA,MAC5B,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ,WAAW;AAAA,MAC5B,WAAW,QAAQ;AAAA,MACnB,YAAY,QAAQ;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,IACrB,CAAC;AAED,SAAK,KAAK,iBAAiB,IAAI;AAC/B,SAAK,SAAS,qBAAqB,IAAI;AACvC,SAAK,QAAQ,oBAAoB,IAAI;AACrC,SAAK,iBAAiB,6BAA6B,IAAI;AACvD,SAAK,UAAU,sBAAsB,IAAI;AACzC,SAAK,aAAa,yBAAyB,IAAI;AAC/C,SAAK,WAAW,uBAAuB,IAAI;AAC3C,SAAK,OAAO,mBAAmB,IAAI;AAAA,EACrC;AACF;;;AC/DA,OAAO,YAAY;AAoBZ,IAAM,yBAAyB,CACpC,SACA,iBACA,WACY;AACZ,MAAI,CAAC,mBAAmB,CAAC,OAAQ,QAAO;AACxC,QAAM,CAAC,MAAM,QAAQ,IAAI,gBAAgB,MAAM,GAAG;AAClD,MAAI,SAAS,YAAY,CAAC,SAAU,QAAO;AAE3C,QAAM,WAAW,OACd,WAAW,UAAU,MAAM,EAC3B,OAAO,OAAO,YAAY,WAAW,UAAU,OAAO,EACtD,OAAO,KAAK;AAGf,QAAM,IAAI,OAAO,KAAK,UAAU,KAAK;AACrC,QAAM,IAAI,OAAO,KAAK,UAAU,KAAK;AACrC,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,SAAO,OAAO,gBAAgB,GAAG,CAAC;AACpC;","names":["path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craftedxp/sdk-node",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Node.js / TypeScript SDK for the voice agent platform. Server-side API client — mint call tokens, manage agents, query calls, upload knowledge-base docs.",
5
5
  "author": "Crafted XP",
6
6
  "license": "MIT",