@pushary/server 1.3.3 → 1.4.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/README.md CHANGED
@@ -224,6 +224,60 @@ app.post('/webhooks/pushary', async (req, res) => {
224
224
  })
225
225
  ```
226
226
 
227
+ ## Writing a framework adapter: `@pushary/server/adapters`
228
+
229
+ Every official Pushary adapter (`@pushary/eve`, `@pushary/ai-sdk`,
230
+ `@pushary/langgraph`, `@pushary/mastra`, `@pushary/openai-agents`) is a thin binding
231
+ over one shared kernel, and that kernel is public. If you run an in-house harness, or
232
+ a framework we have not shipped for, this is the same surface they are built on.
233
+
234
+ ```typescript
235
+ import { createAdapterKernel, renderApprovalQuestion } from '@pushary/server/adapters'
236
+
237
+ const kernel = createAdapterKernel('the Acme helpers')
238
+
239
+ // blocking ask; idempotency and the fail-closed result are already handled
240
+ export const askHuman = kernel.askExternalUser
241
+
242
+ // durable create, for a framework that parks its own run and resumes on a webhook
243
+ export const openDecision = kernel.createDurableDecision
244
+
245
+ // an enforced gate: build it once, call it per tool call
246
+ const gate = kernel.createGate({ apiKey: process.env.PUSHARY_API_KEY! })
247
+
248
+ export const approve = async (toolName: string, callId: string, input: unknown) => {
249
+ const decision = await gate({
250
+ toolName,
251
+ callId,
252
+ sessionId: currentRunId,
253
+ question: renderApprovalQuestion(toolName, input),
254
+ externalId: kernel.requireExternalId(currentUserId),
255
+ })
256
+ return decision // { approved: true } | { approved: false, reason }
257
+ }
258
+ ```
259
+
260
+ The label passed to `createAdapterKernel` is what appears in the error when a key or
261
+ an end-user is missing, so it names your helpers rather than ours.
262
+
263
+ Also exported: `describeAnswer` (turn an outcome into an instruction the model cannot
264
+ misread), `resolvePusharyCallback` (verify and parse a webhook in one call),
265
+ `idempotencyKeyFor`, and `isAffirmative`.
266
+
267
+ Python has the same surface as `pushary.adapters`:
268
+
269
+ ```python
270
+ from pushary.adapters import AdapterKernel, render_approval_question
271
+
272
+ kernel = AdapterKernel("the Acme helpers")
273
+ ask_human = kernel.ask_human
274
+ gate = kernel.create_gate()
275
+ ```
276
+
277
+ Both give you what the shipped adapters have: idempotency keyed on the call so a
278
+ replay never asks twice, a denial reason the model can read, and silence treated as a
279
+ no.
280
+
227
281
  ## Handling errors
228
282
 
229
283
  Every non-2xx response throws a `PusharyApiError`. It extends `Error`, so existing
@@ -0,0 +1,452 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/adapters.ts
21
+ var adapters_exports = {};
22
+ __export(adapters_exports, {
23
+ DENIED_REFUSED: () => DENIED_REFUSED,
24
+ DENIED_UNANSWERED: () => DENIED_UNANSWERED,
25
+ SIGNATURE_HEADER: () => SIGNATURE_HEADER,
26
+ createAdapterKernel: () => createAdapterKernel,
27
+ describeAnswer: () => describeAnswer,
28
+ deterministicKey: () => deterministicKey,
29
+ idempotencyKeyFor: () => idempotencyKeyFor,
30
+ isAffirmative: () => isAffirmative,
31
+ isApproved: () => isApproved,
32
+ parseDecisionCallback: () => parseDecisionCallback,
33
+ renderApprovalQuestion: () => renderApprovalQuestion,
34
+ resolvePusharyCallback: () => resolvePusharyCallback,
35
+ verifyWebhookSignature: () => verifyWebhookSignature
36
+ });
37
+ module.exports = __toCommonJS(adapters_exports);
38
+
39
+ // src/error.ts
40
+ var PusharyApiError = class _PusharyApiError extends Error {
41
+ constructor(message, init) {
42
+ super(message);
43
+ this.name = "PusharyApiError";
44
+ this.status = init.status;
45
+ this.statusText = init.statusText ?? "";
46
+ this.code = init.code;
47
+ this.body = init.body;
48
+ Object.setPrototypeOf(this, _PusharyApiError.prototype);
49
+ }
50
+ /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */
51
+ get isAuthError() {
52
+ return this.status === 401 || this.status === 403;
53
+ }
54
+ /** Rate limited. Back off and retry. */
55
+ get isRateLimited() {
56
+ return this.status === 429;
57
+ }
58
+ /** Server-side fault. Safe to retry an idempotent call. */
59
+ get isServerError() {
60
+ return this.status >= 500;
61
+ }
62
+ };
63
+
64
+ // src/request.ts
65
+ var buildUrl = (baseUrl, path, params) => {
66
+ const url = new URL(`${baseUrl}${path}`);
67
+ if (params) {
68
+ Object.entries(params).forEach(([key, value]) => {
69
+ if (value !== void 0 && value !== null) {
70
+ url.searchParams.set(key, String(value));
71
+ }
72
+ });
73
+ }
74
+ return url.toString();
75
+ };
76
+ var handleResponse = async (response) => {
77
+ if (!response.ok) {
78
+ const body = await response.json().catch(() => ({}));
79
+ throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {
80
+ status: response.status,
81
+ statusText: response.statusText,
82
+ code: body.code,
83
+ body
84
+ });
85
+ }
86
+ return response.json();
87
+ };
88
+ var createRequest = (ctx) => async (method, path, body) => {
89
+ const isGet = method === "GET";
90
+ const url = isGet && body ? buildUrl(ctx.baseUrl, path, body) : `${ctx.baseUrl}${path}`;
91
+ const response = await fetch(url, {
92
+ method,
93
+ headers: ctx.headers,
94
+ body: !isGet && body ? JSON.stringify(body) : void 0
95
+ });
96
+ return handleResponse(response);
97
+ };
98
+
99
+ // src/resources/subscribers.ts
100
+ var createSubscribersResource = (request) => Object.freeze({
101
+ list: (params) => request("GET", "/subscribers", params),
102
+ get: (id) => request("GET", `/subscribers/${id}`),
103
+ update: (id, data) => request("PATCH", `/subscribers/${id}`, data),
104
+ delete: (id) => request("DELETE", `/subscribers/${id}`),
105
+ count: () => request("GET", "/subscribers/count")
106
+ });
107
+
108
+ // src/resources/campaigns.ts
109
+ var createCampaignsResource = (request) => Object.freeze({
110
+ list: (params) => request("GET", "/campaigns", params),
111
+ create: (data) => request("POST", "/campaigns", data),
112
+ get: (id) => request("GET", `/campaigns/${id}`),
113
+ update: (id, data) => request("PATCH", `/campaigns/${id}`, data),
114
+ delete: (id) => request("DELETE", `/campaigns/${id}`),
115
+ send: (id) => request("POST", `/campaigns/${id}/send`),
116
+ pause: (id) => request("POST", `/campaigns/${id}/pause`),
117
+ resume: (id) => request("POST", `/campaigns/${id}/resume`),
118
+ stats: (id) => request("GET", `/campaigns/${id}/stats`)
119
+ });
120
+
121
+ // src/resources/templates.ts
122
+ var createTemplatesResource = (request) => Object.freeze({
123
+ list: (params) => request("GET", "/templates", params),
124
+ create: (data) => request("POST", "/templates", data),
125
+ get: (id) => request("GET", `/templates/${id}`),
126
+ update: (id, data) => request("PATCH", `/templates/${id}`, data),
127
+ delete: (id) => request("DELETE", `/templates/${id}`)
128
+ });
129
+
130
+ // src/resources/notifications.ts
131
+ var createNotificationsResource = (request) => Object.freeze({
132
+ send: (data) => request("POST", "/send", data)
133
+ });
134
+
135
+ // src/resources/flows.ts
136
+ var createFlowsResource = (request) => Object.freeze({
137
+ list: (params) => request("GET", "/flows", params),
138
+ create: (data) => request("POST", "/flows", data),
139
+ get: (id) => request("GET", `/flows/${id}`),
140
+ update: (id, data) => request("PATCH", `/flows/${id}`, data),
141
+ delete: (id) => request("DELETE", `/flows/${id}`),
142
+ activate: (id) => request("POST", `/flows/${id}/activate`),
143
+ pause: (id) => request("POST", `/flows/${id}/pause`)
144
+ });
145
+
146
+ // src/resources/decisions.ts
147
+ var import_crypto2 = require("crypto");
148
+
149
+ // src/util.ts
150
+ var import_crypto = require("crypto");
151
+ var deterministicKey = (parts) => (0, import_crypto.createHash)("sha256").update(parts.join("\0")).digest("hex").slice(0, 40);
152
+ var AFFIRMATIVE = /* @__PURE__ */ new Set([
153
+ "yes",
154
+ "y",
155
+ "approve",
156
+ "approved",
157
+ "ok",
158
+ "okay",
159
+ "confirm",
160
+ "accept",
161
+ "true"
162
+ ]);
163
+ var isApproved = (decision) => {
164
+ if (decision.status !== "answered") return false;
165
+ if (decision.type && decision.type !== "confirm") return true;
166
+ return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase());
167
+ };
168
+
169
+ // src/resources/decisions.ts
170
+ var POLL_WINDOW_SECONDS = 50;
171
+ var DEFAULT_ASK_TIMEOUT_MS = 55e3;
172
+ var createDecisionsResource = (request) => {
173
+ const ask = async (data) => {
174
+ const idempotencyKey = data.idempotencyKey ?? (0, import_crypto2.randomUUID)();
175
+ const created = await request("POST", "/decisions", {
176
+ question: data.question,
177
+ type: data.type,
178
+ options: data.options,
179
+ externalId: data.externalId,
180
+ email: data.email,
181
+ agentName: data.agentName,
182
+ context: data.context,
183
+ placeholder: data.placeholder,
184
+ expiresInSeconds: data.expiresInSeconds,
185
+ poweredBy: data.poweredBy,
186
+ callbackUrl: data.callbackUrl,
187
+ requireReachable: data.requireReachable,
188
+ idempotencyKey,
189
+ wait: false
190
+ });
191
+ const type = created.type ?? data.type ?? "confirm";
192
+ let status = created.status;
193
+ let answered = created.answered;
194
+ let value = created.value ?? null;
195
+ const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS);
196
+ while (status === "pending" && Date.now() < deadline) {
197
+ const waitSeconds = Math.max(
198
+ 1,
199
+ Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1e3))
200
+ );
201
+ const polled = await request("GET", `/decisions/${created.decisionId}`, {
202
+ wait: waitSeconds
203
+ });
204
+ status = polled.status;
205
+ answered = polled.answered;
206
+ value = polled.value;
207
+ }
208
+ return {
209
+ decisionId: created.decisionId,
210
+ status,
211
+ answered,
212
+ value,
213
+ type,
214
+ approved: isApproved({ status, type, value }),
215
+ // Surfaced from the create response so a caller can tell an unanswered
216
+ // decision that reached nobody (reachable:false) apart from a real decline.
217
+ reachable: created.reachable,
218
+ reachableChannels: created.reachableChannels,
219
+ deviceCount: created.deviceCount
220
+ };
221
+ };
222
+ return Object.freeze({
223
+ create: (data) => request("POST", "/decisions", data),
224
+ get: (id, opts) => request("GET", `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : void 0),
225
+ ask,
226
+ answer: (id, answer) => request("POST", `/decisions/${id}`, { answer }),
227
+ cancel: (id) => request("DELETE", `/decisions/${id}`),
228
+ getWebhookSecret: () => request("GET", "/webhook-secret"),
229
+ rotateWebhookSecret: () => request("POST", "/webhook-secret")
230
+ });
231
+ };
232
+
233
+ // src/resources/keys.ts
234
+ var createKeysResource = (request) => Object.freeze({
235
+ issue: (data) => request("POST", "/keys", data),
236
+ list: () => request("GET", "/keys").then((r) => r.keys),
237
+ revoke: (keyPrefix) => request("DELETE", `/keys/${encodeURIComponent(keyPrefix)}`)
238
+ });
239
+
240
+ // src/client.ts
241
+ var DEFAULT_BASE_URL = "https://pushary.com/api/v1/server";
242
+ var validateApiKey = (apiKey) => {
243
+ if (!apiKey) {
244
+ throw new Error("API key is required. Get your API key from https://pushary.com/dashboard/settings");
245
+ }
246
+ if (!apiKey.includes(".")) {
247
+ throw new Error("Invalid API key format. Use the full API key (pk_xxx.sk_xxx)");
248
+ }
249
+ };
250
+ var createContext = (config) => {
251
+ validateApiKey(config.apiKey);
252
+ return Object.freeze({
253
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
254
+ headers: Object.freeze({
255
+ "Content-Type": "application/json",
256
+ "Authorization": `Bearer ${config.apiKey}`
257
+ })
258
+ });
259
+ };
260
+ var createPusharyServer = (config) => {
261
+ const ctx = createContext(config);
262
+ const request = createRequest(ctx);
263
+ return Object.freeze({
264
+ subscribers: createSubscribersResource(request),
265
+ campaigns: createCampaignsResource(request),
266
+ templates: createTemplatesResource(request),
267
+ notifications: createNotificationsResource(request),
268
+ flows: createFlowsResource(request),
269
+ decisions: createDecisionsResource(request),
270
+ keys: createKeysResource(request),
271
+ enroll: (externalId) => request("POST", "/enroll", { externalId })
272
+ });
273
+ };
274
+
275
+ // src/webhook.ts
276
+ var import_crypto3 = require("crypto");
277
+ var SIGNATURE_HEADER = "x-pushary-signature";
278
+ var verifyWebhookSignature = (rawBody, signature, secret) => {
279
+ if (!signature || !secret) return false;
280
+ const expected = (0, import_crypto3.createHmac)("sha256", secret).update(rawBody).digest("hex");
281
+ const a = Buffer.from(expected);
282
+ const b = Buffer.from(signature);
283
+ if (a.length !== b.length) return false;
284
+ return (0, import_crypto3.timingSafeEqual)(a, b);
285
+ };
286
+ var parseDecisionCallback = (rawBody) => {
287
+ let parsed;
288
+ try {
289
+ parsed = JSON.parse(rawBody);
290
+ } catch {
291
+ return null;
292
+ }
293
+ if (!parsed || typeof parsed !== "object") return null;
294
+ const p = parsed;
295
+ if (typeof p.correlationId !== "string" || typeof p.answer !== "string") return null;
296
+ return {
297
+ correlationId: p.correlationId,
298
+ answer: p.answer,
299
+ value: typeof p.value === "string" ? p.value : p.answer,
300
+ answeredAt: typeof p.answeredAt === "string" ? p.answeredAt : "",
301
+ ...typeof p.context === "string" ? { context: p.context } : {}
302
+ };
303
+ };
304
+
305
+ // src/adapters.ts
306
+ var DEFAULT_NODE = "ask-human";
307
+ var idempotencyKeyFor = (input) => deterministicKey([input.externalId, input.node ?? DEFAULT_NODE, input.question]);
308
+ var isAffirmative = (answer) => isApproved({ status: "answered", type: "confirm", value: answer ?? null });
309
+ var describeAnswer = (type, result) => {
310
+ if (!result.answered) {
311
+ return `No answer (status: ${result.status}). Treat this as NOT approved and do not proceed.`;
312
+ }
313
+ if (type === "confirm") {
314
+ return result.approved ? "The human approved. You may proceed." : "The human declined. Do not proceed.";
315
+ }
316
+ return `The human answered: ${result.value ?? ""}`;
317
+ };
318
+ var resolvePusharyCallback = (rawBody, signature, secret) => {
319
+ if (!verifyWebhookSignature(rawBody, signature, secret)) return null;
320
+ const callback = parseDecisionCallback(rawBody);
321
+ if (!callback) return null;
322
+ return {
323
+ correlationId: callback.correlationId,
324
+ answer: callback.answer,
325
+ value: callback.value,
326
+ approved: isAffirmative(callback.answer),
327
+ context: callback.context,
328
+ answeredAt: callback.answeredAt
329
+ };
330
+ };
331
+ var MAX_INPUT_CHARS = 300;
332
+ var DENIED_UNANSWERED = "No answer from the approver, so this was denied. Do not retry the same action.";
333
+ var DENIED_REFUSED = "The approver denied this action.";
334
+ var renderApprovalQuestion = (toolName, toolInput) => {
335
+ if (toolInput === void 0 || toolInput === null) return `Approve ${toolName}?`;
336
+ const summary = summarise(toolInput);
337
+ return summary === void 0 ? `Approve ${toolName}?` : `Approve ${toolName}? ${summary}`;
338
+ };
339
+ var summarise = (toolInput) => {
340
+ let rendered;
341
+ if (typeof toolInput === "string") {
342
+ rendered = toolInput;
343
+ } else {
344
+ try {
345
+ rendered = JSON.stringify(toolInput);
346
+ } catch {
347
+ return void 0;
348
+ }
349
+ }
350
+ if (rendered === void 0) return void 0;
351
+ return rendered.length > MAX_INPUT_CHARS ? `${rendered.slice(0, MAX_INPUT_CHARS)}...` : rendered;
352
+ };
353
+ var createAdapterKernel = (helpersLabel) => {
354
+ const requireApiKey = (apiKey) => {
355
+ const resolved = apiKey ?? process.env.PUSHARY_API_KEY;
356
+ if (!resolved) {
357
+ throw new Error(`Pushary: set PUSHARY_API_KEY or pass { apiKey } to ${helpersLabel}.`);
358
+ }
359
+ return resolved;
360
+ };
361
+ const client = (config) => createPusharyServer({
362
+ apiKey: requireApiKey(config.apiKey),
363
+ ...config.baseUrl ? { baseUrl: config.baseUrl } : {}
364
+ });
365
+ const requireExternalId = (externalId) => {
366
+ if (!externalId) {
367
+ throw new Error(
368
+ `Pushary: no end-user to ask. Pass { externalId } to ${helpersLabel}, or run user-scoped auth so the framework supplies one.`
369
+ );
370
+ }
371
+ return externalId;
372
+ };
373
+ const createGate = (config) => {
374
+ const gateClient = createPusharyServer({
375
+ apiKey: requireApiKey(config.apiKey),
376
+ ...config.baseUrl ? { baseUrl: config.baseUrl } : {}
377
+ });
378
+ return async (ask) => {
379
+ const result = await gateClient.decisions.ask({
380
+ question: ask.question,
381
+ type: "confirm",
382
+ externalId: ask.externalId,
383
+ ...config.agentName ? { agentName: config.agentName } : {},
384
+ ...config.expiresInSeconds ? { expiresInSeconds: config.expiresInSeconds } : {},
385
+ ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
386
+ ...config.requireReachable ? { requireReachable: config.requireReachable } : {},
387
+ // Stable across replays, so a re-run of this call resolves to the same
388
+ // decision instead of asking twice.
389
+ idempotencyKey: deterministicKey([ask.sessionId, ask.callId, ask.toolName])
390
+ });
391
+ if (result.approved) return { approved: true };
392
+ return { approved: false, reason: result.answered ? DENIED_REFUSED : DENIED_UNANSWERED };
393
+ };
394
+ };
395
+ const askExternalUser = (config, input) => client(config).decisions.ask({
396
+ question: input.question,
397
+ type: input.type,
398
+ options: input.options,
399
+ externalId: input.externalId,
400
+ context: input.context,
401
+ agentName: config.agentName,
402
+ timeoutMs: config.timeoutMs,
403
+ idempotencyKey: idempotencyKeyFor(input)
404
+ });
405
+ const createDurableDecision = async (config, input) => {
406
+ const created = await client(config).decisions.create({
407
+ question: input.question,
408
+ type: input.type,
409
+ options: input.options,
410
+ externalId: input.externalId,
411
+ context: input.context,
412
+ callbackUrl: input.callbackUrl,
413
+ agentName: config.agentName,
414
+ idempotencyKey: idempotencyKeyFor(input),
415
+ wait: false
416
+ });
417
+ return {
418
+ decisionId: created.decisionId,
419
+ correlationId: created.decisionId,
420
+ status: created.status,
421
+ reachable: created.reachable,
422
+ reachableChannels: created.reachableChannels,
423
+ deviceCount: created.deviceCount
424
+ };
425
+ };
426
+ const connect = (config, externalId) => client(config).enroll(externalId);
427
+ return Object.freeze({
428
+ client,
429
+ askExternalUser,
430
+ createDurableDecision,
431
+ connect,
432
+ createGate,
433
+ requireExternalId
434
+ });
435
+ };
436
+ // Annotate the CommonJS export names for ESM import in node:
437
+ 0 && (module.exports = {
438
+ DENIED_REFUSED,
439
+ DENIED_UNANSWERED,
440
+ SIGNATURE_HEADER,
441
+ createAdapterKernel,
442
+ describeAnswer,
443
+ deterministicKey,
444
+ idempotencyKeyFor,
445
+ isAffirmative,
446
+ isApproved,
447
+ parseDecisionCallback,
448
+ renderApprovalQuestion,
449
+ resolvePusharyCallback,
450
+ verifyWebhookSignature
451
+ });
452
+ //# sourceMappingURL=adapters.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters.ts","../src/error.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/util.ts","../src/resources/keys.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["// The shared kernel behind every Pushary framework adapter. Published as\n// `@pushary/server/adapters` so a new adapter is a thin binding — map the\n// framework's context onto these shapes, map the result back — instead of another\n// copy of the same ~150 lines. Nothing here imports a framework.\n\nimport { createPusharyServer, type PusharyServer } from './client'\nimport { deterministicKey, isApproved } from './util'\nimport { parseDecisionCallback, verifyWebhookSignature } from './webhook'\nimport type { AskResult, DecisionType, EnrollResult } from './types'\n\n/** Config every adapter accepts, whatever framework it binds. */\nexport interface PusharyAdapterConfig {\n /** Pushary API key (pk_xxx.sk_xxx). Defaults to `process.env.PUSHARY_API_KEY`. */\n readonly apiKey?: string\n /** Shown on the approval so the human knows which agent is asking. */\n readonly agentName?: string\n /** How long a blocking ask waits before returning (default 55s, serverless-safe). */\n readonly timeoutMs?: number\n /** Override the API base URL (tests / self-host). */\n readonly baseUrl?: string\n}\n\n/** One ask, as an adapter hands it to the kernel. */\nexport interface AskHumanInput {\n readonly question: string\n /** Your own stable id for the end-user who decides. */\n readonly externalId: string\n /** Stable node/step/tool name, used to build a re-run-safe idempotency key. */\n readonly node?: string\n readonly type?: DecisionType\n readonly options?: readonly string[]\n readonly context?: string\n /** Present on the durable path; Pushary POSTs the answer here. */\n readonly callbackUrl?: string\n}\n\n/** A decision opened durably, before the framework parks its run. */\nexport interface CreatedDecision {\n readonly decisionId: string\n readonly correlationId: string\n readonly status: string\n readonly reachable?: boolean\n readonly reachableChannels?: number\n readonly deviceCount?: number\n}\n\n/** A verified, parsed answer callback, keyed by `correlationId`. */\nexport interface PusharyCallback {\n readonly correlationId: string\n readonly answer: string\n readonly value: string\n readonly approved: boolean\n readonly context?: string\n readonly answeredAt: string\n}\n\nconst DEFAULT_NODE = 'ask-human'\n\n/**\n * Re-run-safe idempotency key. A node, step, or tool call that replays on resume\n * lands on the same decision instead of paging the human twice.\n */\nexport const idempotencyKeyFor = (input: AskHumanInput): string =>\n deterministicKey([input.externalId, input.node ?? DEFAULT_NODE, input.question])\n\n/** Fail-closed yes/no check for a confirm answer. */\nexport const isAffirmative = (answer: string | null | undefined): boolean =>\n isApproved({ status: 'answered', type: 'confirm', value: answer ?? null })\n\n/** Turn a decision outcome into an unambiguous instruction for the model. */\nexport const describeAnswer = (type: DecisionType, result: AskResult): string => {\n if (!result.answered) {\n return `No answer (status: ${result.status}). Treat this as NOT approved and do not proceed.`\n }\n if (type === 'confirm') {\n return result.approved\n ? 'The human approved. You may proceed.'\n : 'The human declined. Do not proceed.'\n }\n return `The human answered: ${result.value ?? ''}`\n}\n\n/**\n * Verify a callback signature and parse it, or return null. The `answer` is what\n * you feed back into the framework's resume seam.\n */\nexport const resolvePusharyCallback = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): PusharyCallback | null => {\n if (!verifyWebhookSignature(rawBody, signature, secret)) return null\n const callback = parseDecisionCallback(rawBody)\n if (!callback) return null\n return {\n correlationId: callback.correlationId,\n answer: callback.answer,\n value: callback.value,\n approved: isAffirmative(callback.answer),\n context: callback.context,\n answeredAt: callback.answeredAt,\n }\n}\n\n/**\n * Connection-level options for a request-time approval gate. Everything that\n * varies per tool call lives on {@link ApprovalAsk} instead, so a gate holds one\n * client and no per-call state.\n */\nexport interface PusharyGateConfig {\n /** Pushary API key. Defaults to `process.env.PUSHARY_API_KEY`. */\n readonly apiKey?: string\n /** Shown on the approval so the human knows which agent is asking. */\n readonly agentName?: string\n /** How long the decision stays answerable. */\n readonly expiresInSeconds?: number\n /** How long to block waiting for an answer before failing closed. */\n readonly timeoutMs?: number\n /**\n * Refuse to open a decision nobody can receive, so an end-user with no connected\n * device is denied at request time instead of silently expiring.\n */\n readonly requireReachable?: boolean\n /** Override the API base URL (tests / self-host). */\n readonly baseUrl?: string\n}\n\n/**\n * One gated tool call, resolved down to what Pushary needs. The adapter owns the\n * mapping from its framework's context, so per-call resolvers stay typed against\n * the framework's own shapes rather than a lowest common denominator.\n */\nexport interface ApprovalAsk {\n /** The tool the model wants to run. */\n readonly toolName: string\n /** Unique per tool call. Keys the decision together with `sessionId`. */\n readonly callId: string\n /** Stable per run, session, or thread. */\n readonly sessionId: string\n /** What the human is shown. */\n readonly question: string\n /** The enrolled end-user who decides. */\n readonly externalId: string\n}\n\n/** What a gate answers. Denials always carry a reason the model can read. */\nexport type ApprovalDecision =\n | { readonly approved: true }\n | { readonly approved: false; readonly reason: string }\n\n/** A request-time gate: ask a person, fail closed. */\nexport type ApprovalGate = (ask: ApprovalAsk) => Promise<ApprovalDecision>\n\nconst MAX_INPUT_CHARS = 300\n\nexport const DENIED_UNANSWERED =\n 'No answer from the approver, so this was denied. Do not retry the same action.'\nexport const DENIED_REFUSED = 'The approver denied this action.'\n\n/**\n * The default question: the tool name plus a truncated view of its input, so the\n * human sees what they are approving without a wall of JSON on a lock screen.\n */\nexport const renderApprovalQuestion = (toolName: string, toolInput: unknown): string => {\n if (toolInput === undefined || toolInput === null) return `Approve ${toolName}?`\n const summary = summarise(toolInput)\n return summary === undefined ? `Approve ${toolName}?` : `Approve ${toolName}? ${summary}`\n}\n\nconst summarise = (toolInput: unknown): string | undefined => {\n let rendered: string | undefined\n if (typeof toolInput === 'string') {\n rendered = toolInput\n } else {\n try {\n // Returns undefined for a function or a symbol, and throws on a cycle. Neither\n // is a reason to fail the tool call: a gate that cannot render its input still\n // has to ask, so fall back to the bare tool name.\n rendered = JSON.stringify(toolInput)\n } catch {\n return undefined\n }\n }\n if (rendered === undefined) return undefined\n return rendered.length > MAX_INPUT_CHARS ? `${rendered.slice(0, MAX_INPUT_CHARS)}...` : rendered\n}\n\n/**\n * The calls an adapter makes, bound to one framework's name so a missing key or a\n * missing end-user says which helpers to configure. Build one per adapter module.\n */\nexport interface AdapterKernel {\n /** The underlying client, for anything the kernel does not cover. */\n readonly client: (config: PusharyAdapterConfig) => PusharyServer\n /**\n * Blocking ask: create a decision and poll durably until the human answers or\n * the deadline passes. Idempotency keyed by externalId + node + question.\n */\n readonly askExternalUser: (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ) => Promise<AskResult>\n /**\n * Durable create: open a decision with a `callbackUrl` and return at once, for\n * a framework that parks its run and resumes on the webhook.\n */\n readonly createDurableDecision: (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ) => Promise<CreatedDecision>\n /** Connect one end-user's phone (keyless). Show them the returned link. */\n readonly connect: (config: PusharyAdapterConfig, externalId: string) => Promise<EnrollResult>\n /**\n * Build a request-time approval gate. The client is created here, so a missing\n * key throws where the gate is defined rather than on the first tool call.\n */\n readonly createGate: (config: PusharyGateConfig) => ApprovalGate\n /**\n * The end-user to ask, or a clear error naming these helpers. Adapters call this\n * after applying their own resolver and the framework's own default.\n */\n readonly requireExternalId: (externalId: string | undefined) => string\n}\n\n/**\n * Build the kernel for one adapter.\n *\n * `helpersLabel` names the adapter in the missing-key error, so the message points\n * at the helpers the caller is actually using (\"the LangGraph helpers\").\n *\n * ```ts\n * const kernel = createAdapterKernel('the Acme helpers')\n * export const askHuman = kernel.askExternalUser\n * ```\n */\nexport const createAdapterKernel = (helpersLabel: string): AdapterKernel => {\n const requireApiKey = (apiKey: string | undefined): string => {\n const resolved = apiKey ?? process.env.PUSHARY_API_KEY\n if (!resolved) {\n throw new Error(`Pushary: set PUSHARY_API_KEY or pass { apiKey } to ${helpersLabel}.`)\n }\n return resolved\n }\n\n const client = (config: PusharyAdapterConfig): PusharyServer =>\n createPusharyServer({\n apiKey: requireApiKey(config.apiKey),\n ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),\n })\n\n const requireExternalId = (externalId: string | undefined): string => {\n if (!externalId) {\n throw new Error(\n `Pushary: no end-user to ask. Pass { externalId } to ${helpersLabel}, or run ` +\n 'user-scoped auth so the framework supplies one.',\n )\n }\n return externalId\n }\n\n const createGate = (config: PusharyGateConfig): ApprovalGate => {\n const gateClient = createPusharyServer({\n apiKey: requireApiKey(config.apiKey),\n ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),\n })\n\n return async (ask: ApprovalAsk): Promise<ApprovalDecision> => {\n const result = await gateClient.decisions.ask({\n question: ask.question,\n type: 'confirm',\n externalId: ask.externalId,\n ...(config.agentName ? { agentName: config.agentName } : {}),\n ...(config.expiresInSeconds ? { expiresInSeconds: config.expiresInSeconds } : {}),\n ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),\n ...(config.requireReachable ? { requireReachable: config.requireReachable } : {}),\n // Stable across replays, so a re-run of this call resolves to the same\n // decision instead of asking twice.\n idempotencyKey: deterministicKey([ask.sessionId, ask.callId, ask.toolName]),\n })\n\n if (result.approved) return { approved: true }\n return { approved: false, reason: result.answered ? DENIED_REFUSED : DENIED_UNANSWERED }\n }\n }\n\n const askExternalUser = (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ): Promise<AskResult> =>\n client(config).decisions.ask({\n question: input.question,\n type: input.type,\n options: input.options,\n externalId: input.externalId,\n context: input.context,\n agentName: config.agentName,\n timeoutMs: config.timeoutMs,\n idempotencyKey: idempotencyKeyFor(input),\n })\n\n const createDurableDecision = async (\n config: PusharyAdapterConfig,\n input: AskHumanInput,\n ): Promise<CreatedDecision> => {\n const created = await client(config).decisions.create({\n question: input.question,\n type: input.type,\n options: input.options,\n externalId: input.externalId,\n context: input.context,\n callbackUrl: input.callbackUrl,\n agentName: config.agentName,\n idempotencyKey: idempotencyKeyFor(input),\n wait: false,\n })\n return {\n decisionId: created.decisionId,\n correlationId: created.decisionId,\n status: created.status,\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n }\n\n const connect = (config: PusharyAdapterConfig, externalId: string): Promise<EnrollResult> =>\n client(config).enroll(externalId)\n\n return Object.freeze({\n client,\n askExternalUser,\n createDurableDecision,\n connect,\n createGate,\n requireExternalId,\n })\n}\n\nexport { SIGNATURE_HEADER, verifyWebhookSignature, parseDecisionCallback } from './webhook'\nexport { deterministicKey, isApproved } from './util'\nexport type { AskResult, DecisionType, EnrollResult, PusharyServer }\n","/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n requireReachable: data.requireReachable,\n idempotencyKey,\n wait: false,\n })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n })\n status = polled.status\n answered = polled.answered\n value = polled.value\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\n })\n","import type { PusharyConfig, RequestContext, EnrollResult } from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\nimport { createKeysResource, type KeysResource } from './resources/keys'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions: createDecisionsResource(request),\n keys: createKeysResource(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto"]}