@asyncify-hq/node 0.2.1 → 0.3.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
@@ -50,6 +50,29 @@ your frontend — API keys never reach the browser:
50
50
  const { token } = await asyncify.subscriberToken('user-42');
51
51
  ```
52
52
 
53
+ ## Agent tools & approvals
54
+
55
+ Give a managed agent a custom tool, then review the calls it wants to make:
56
+
57
+ ```ts
58
+ // Register a tool — the secret is returned ONCE; store it to verify our
59
+ // signed calls to your endpoint.
60
+ const { secret } = await asyncify.agents.tools.create('acme-support', {
61
+ name: 'lookup_order',
62
+ description: 'Fetch an order by id',
63
+ parameters: { type: 'object', properties: { orderId: { type: 'string' } } },
64
+ endpointUrl: 'https://api.acme.com/tools/lookup-order',
65
+ approval: 'required',
66
+ });
67
+
68
+ // Work the human-in-the-loop queue
69
+ const { approvals } = await asyncify.approvals.list({ status: 'pending' });
70
+ await asyncify.approvals.decide(approvals[0].id, 'approve');
71
+
72
+ // Route approval cards to a Slack channel
73
+ await asyncify.settings.putApprovals({ slackConnectionId, slackChannelId: 'C0123' });
74
+ ```
75
+
53
76
  ## API surface
54
77
 
55
78
  | Method | Purpose |
@@ -60,6 +83,10 @@ const { token } = await asyncify.subscriberToken('user-42');
60
83
  | `subscribers.upsert({ subscriberId, email?, phone?, pushToken? })` | Create/update a subscriber |
61
84
  | `topics.upsert / addSubscribers / removeSubscribers / list / delete` | Manage segments |
62
85
  | `workflows.upsert / list` · `templates.upsert / get / list / delete` | Manage workflows & MJML templates |
86
+ | `agents.create / list / get / update / rotateSecret / delete / linkToken` | Manage AI agents |
87
+ | `agents.tools.create / list / update / delete / rotateSecret` | Per-agent custom tool registry |
88
+ | `approvals.list / decide` | Human-in-the-loop tool-call queue |
89
+ | `settings.getApprovals / putApprovals` | Which channels carry approval cards |
63
90
  | `subscriberToken(subscriberId, ttlSeconds?)` | Browser-safe inbox token |
64
91
 
65
92
  Errors throw `AsyncifyError` with `status` and the API's message.
package/dist/index.cjs CHANGED
@@ -105,8 +105,69 @@ var AsyncifyClient = class {
105
105
  linkToken: (agentIdentifier, subscriberId) => this.request(
106
106
  "POST",
107
107
  `/v1/agents/${encodeURIComponent(agentIdentifier)}/subscribers/${encodeURIComponent(subscriberId)}/link-token`
108
+ ),
109
+ /**
110
+ * The per-agent custom tool registry (managed runtime dispatches these).
111
+ * Reads as `client.agents.tools.create('acme-support', {...})`.
112
+ */
113
+ tools: {
114
+ /**
115
+ * Register a tool. The secret is returned EXACTLY ONCE — store it; it is
116
+ * used to verify our signed calls to your endpoint.
117
+ */
118
+ create: (identifier, options) => this.request(
119
+ "POST",
120
+ `/v1/agents/${encodeURIComponent(identifier)}/tools`,
121
+ options
122
+ ),
123
+ /** Every tool registered on this agent. */
124
+ list: (identifier) => this.request(
125
+ "GET",
126
+ `/v1/agents/${encodeURIComponent(identifier)}/tools`
127
+ ),
128
+ /** Patch any subset; `status: 'disabled'` hides the tool from the model. */
129
+ update: (identifier, toolId, patch) => this.request(
130
+ "PATCH",
131
+ `/v1/agents/${encodeURIComponent(identifier)}/tools/${encodeURIComponent(toolId)}`,
132
+ patch
133
+ ),
134
+ delete: (identifier, toolId) => this.request(
135
+ "DELETE",
136
+ `/v1/agents/${encodeURIComponent(identifier)}/tools/${encodeURIComponent(toolId)}`
137
+ ),
138
+ /** New call secret, shown once; the old one stops working immediately. */
139
+ rotateSecret: (identifier, toolId) => this.request(
140
+ "POST",
141
+ `/v1/agents/${encodeURIComponent(identifier)}/tools/${encodeURIComponent(toolId)}/rotate-secret`
142
+ )
143
+ }
144
+ };
145
+ approvals = {
146
+ /** Gated tool calls: `pending` (default) awaiting review, or `decided`. */
147
+ list: (filters = {}) => {
148
+ const qs = filters.status ? `?status=${filters.status}` : "";
149
+ return this.request("GET", `/v1/approvals${qs}`);
150
+ },
151
+ /** Approve or deny a pending call — atomic: 409 once already decided. */
152
+ decide: (id, decision, note) => this.request(
153
+ "POST",
154
+ `/v1/approvals/${encodeURIComponent(id)}/decision`,
155
+ { decision, note }
108
156
  )
109
157
  };
158
+ settings = {
159
+ /** Current approval-channel config + count of linked telegram approvers. */
160
+ getApprovals: () => this.request(
161
+ "GET",
162
+ "/v1/settings/approvals"
163
+ ),
164
+ /**
165
+ * Merge-patch approval channels (an absent field is kept). An explicit
166
+ * `null` CLEARS a field; `slackChannelId` requires an active
167
+ * `slackConnectionId` (and is cleared when that connection is nulled).
168
+ */
169
+ putApprovals: (patch) => this.request("PUT", "/v1/settings/approvals", patch)
170
+ };
110
171
  conversations = {
111
172
  /** Conversations across your agents, newest first. */
112
173
  list: (filters = {}) => {
package/dist/index.d.cts CHANGED
@@ -105,6 +105,55 @@ interface ConversationSummary {
105
105
  lastMessagePreview: string | null;
106
106
  lastMessageAt: string;
107
107
  }
108
+ /** A custom tool in an agent's registry — the managed brain dispatches these. */
109
+ interface AgentTool {
110
+ id: string;
111
+ name: string;
112
+ description: string;
113
+ /** JSON Schema object ({ type: 'object', ... }) describing the tool's args. */
114
+ parameters: Record<string, unknown>;
115
+ endpointUrl: string;
116
+ /** 'required' routes every call through the human approval queue first. */
117
+ approval: 'auto' | 'required';
118
+ timeoutMs: number;
119
+ status: 'active' | 'disabled';
120
+ createdAt: string;
121
+ }
122
+ interface CreateAgentToolOptions {
123
+ /** Lowercase `^[a-z][a-z0-9_]{0,63}$`; reserved built-in names are rejected. */
124
+ name: string;
125
+ description: string;
126
+ /** JSON Schema; must be an object with `type: 'object'`. */
127
+ parameters: Record<string, unknown>;
128
+ /** We POST tool calls here — must be a public URL (SSRF-checked write-time). */
129
+ endpointUrl: string;
130
+ /** 'auto' (default) runs immediately; 'required' gates on human approval. */
131
+ approval?: 'auto' | 'required';
132
+ /** Per-call timeout in ms, 1000–30000 (default 10000). */
133
+ timeoutMs?: number;
134
+ }
135
+ /** A gated tool call in the approvals queue — pending, or already decided. */
136
+ interface ToolApproval {
137
+ id: string;
138
+ agentIdentifier: string | null;
139
+ toolName: string;
140
+ args: Record<string, unknown>;
141
+ conversationId: string;
142
+ status: 'pending' | 'approved' | 'denied' | 'expired' | 'executed' | 'failed';
143
+ /** Tool result, truncated to 500 chars; null until the call has executed. */
144
+ result: string | null;
145
+ note: string | null;
146
+ requestedAt: string;
147
+ decidedAt: string | null;
148
+ decidedBy: string | null;
149
+ expiresAt: string | null;
150
+ }
151
+ /** Which connections carry approval cards; each field null when unset. */
152
+ interface ApprovalSettings {
153
+ slackConnectionId: string | null;
154
+ slackChannelId: string | null;
155
+ telegramConnectionId: string | null;
156
+ }
108
157
  declare class AsyncifyError extends Error {
109
158
  readonly status: number;
110
159
  constructor(status: number, message: string);
@@ -201,6 +250,65 @@ declare class AsyncifyClient {
201
250
  deepLink: string;
202
251
  expiresAt: string;
203
252
  }>;
253
+ /**
254
+ * The per-agent custom tool registry (managed runtime dispatches these).
255
+ * Reads as `client.agents.tools.create('acme-support', {...})`.
256
+ */
257
+ tools: {
258
+ /**
259
+ * Register a tool. The secret is returned EXACTLY ONCE — store it; it is
260
+ * used to verify our signed calls to your endpoint.
261
+ */
262
+ create: (identifier: string, options: CreateAgentToolOptions) => Promise<{
263
+ tool: AgentTool;
264
+ secret: string;
265
+ }>;
266
+ /** Every tool registered on this agent. */
267
+ list: (identifier: string) => Promise<{
268
+ tools: AgentTool[];
269
+ }>;
270
+ /** Patch any subset; `status: 'disabled'` hides the tool from the model. */
271
+ update: (identifier: string, toolId: string, patch: Partial<Omit<CreateAgentToolOptions, "name">> & {
272
+ status?: "active" | "disabled";
273
+ }) => Promise<{
274
+ tool: AgentTool;
275
+ }>;
276
+ delete: (identifier: string, toolId: string) => Promise<{
277
+ deleted: boolean;
278
+ }>;
279
+ /** New call secret, shown once; the old one stops working immediately. */
280
+ rotateSecret: (identifier: string, toolId: string) => Promise<{
281
+ secret: string;
282
+ }>;
283
+ };
284
+ };
285
+ readonly approvals: {
286
+ /** Gated tool calls: `pending` (default) awaiting review, or `decided`. */
287
+ list: (filters?: {
288
+ status?: "pending" | "decided";
289
+ }) => Promise<{
290
+ approvals: ToolApproval[];
291
+ }>;
292
+ /** Approve or deny a pending call — atomic: 409 once already decided. */
293
+ decide: (id: string, decision: "approve" | "deny", note?: string) => Promise<{
294
+ id: string;
295
+ status: string;
296
+ }>;
297
+ };
298
+ readonly settings: {
299
+ /** Current approval-channel config + count of linked telegram approvers. */
300
+ getApprovals: () => Promise<{
301
+ settings: ApprovalSettings;
302
+ telegramApproverCount: number;
303
+ }>;
304
+ /**
305
+ * Merge-patch approval channels (an absent field is kept). An explicit
306
+ * `null` CLEARS a field; `slackChannelId` requires an active
307
+ * `slackConnectionId` (and is cleared when that connection is nulled).
308
+ */
309
+ putApprovals: (patch: Partial<ApprovalSettings>) => Promise<{
310
+ settings: ApprovalSettings;
311
+ }>;
204
312
  };
205
313
  readonly conversations: {
206
314
  /** Conversations across your agents, newest first. */
@@ -298,4 +406,4 @@ declare class AsyncifyClient {
298
406
  }>;
299
407
  }
300
408
 
301
- export { type Agent, AsyncifyClient, type AsyncifyClientOptions, AsyncifyError, type ConversationSummary, type CreateAgentOptions, type Priority, type Recipient, type TriggerOptions, type TriggerRecipient, type TriggerResult, type WorkflowStep };
409
+ export { type Agent, type AgentTool, type ApprovalSettings, AsyncifyClient, type AsyncifyClientOptions, AsyncifyError, type ConversationSummary, type CreateAgentOptions, type CreateAgentToolOptions, type Priority, type Recipient, type ToolApproval, type TriggerOptions, type TriggerRecipient, type TriggerResult, type WorkflowStep };
package/dist/index.d.ts CHANGED
@@ -105,6 +105,55 @@ interface ConversationSummary {
105
105
  lastMessagePreview: string | null;
106
106
  lastMessageAt: string;
107
107
  }
108
+ /** A custom tool in an agent's registry — the managed brain dispatches these. */
109
+ interface AgentTool {
110
+ id: string;
111
+ name: string;
112
+ description: string;
113
+ /** JSON Schema object ({ type: 'object', ... }) describing the tool's args. */
114
+ parameters: Record<string, unknown>;
115
+ endpointUrl: string;
116
+ /** 'required' routes every call through the human approval queue first. */
117
+ approval: 'auto' | 'required';
118
+ timeoutMs: number;
119
+ status: 'active' | 'disabled';
120
+ createdAt: string;
121
+ }
122
+ interface CreateAgentToolOptions {
123
+ /** Lowercase `^[a-z][a-z0-9_]{0,63}$`; reserved built-in names are rejected. */
124
+ name: string;
125
+ description: string;
126
+ /** JSON Schema; must be an object with `type: 'object'`. */
127
+ parameters: Record<string, unknown>;
128
+ /** We POST tool calls here — must be a public URL (SSRF-checked write-time). */
129
+ endpointUrl: string;
130
+ /** 'auto' (default) runs immediately; 'required' gates on human approval. */
131
+ approval?: 'auto' | 'required';
132
+ /** Per-call timeout in ms, 1000–30000 (default 10000). */
133
+ timeoutMs?: number;
134
+ }
135
+ /** A gated tool call in the approvals queue — pending, or already decided. */
136
+ interface ToolApproval {
137
+ id: string;
138
+ agentIdentifier: string | null;
139
+ toolName: string;
140
+ args: Record<string, unknown>;
141
+ conversationId: string;
142
+ status: 'pending' | 'approved' | 'denied' | 'expired' | 'executed' | 'failed';
143
+ /** Tool result, truncated to 500 chars; null until the call has executed. */
144
+ result: string | null;
145
+ note: string | null;
146
+ requestedAt: string;
147
+ decidedAt: string | null;
148
+ decidedBy: string | null;
149
+ expiresAt: string | null;
150
+ }
151
+ /** Which connections carry approval cards; each field null when unset. */
152
+ interface ApprovalSettings {
153
+ slackConnectionId: string | null;
154
+ slackChannelId: string | null;
155
+ telegramConnectionId: string | null;
156
+ }
108
157
  declare class AsyncifyError extends Error {
109
158
  readonly status: number;
110
159
  constructor(status: number, message: string);
@@ -201,6 +250,65 @@ declare class AsyncifyClient {
201
250
  deepLink: string;
202
251
  expiresAt: string;
203
252
  }>;
253
+ /**
254
+ * The per-agent custom tool registry (managed runtime dispatches these).
255
+ * Reads as `client.agents.tools.create('acme-support', {...})`.
256
+ */
257
+ tools: {
258
+ /**
259
+ * Register a tool. The secret is returned EXACTLY ONCE — store it; it is
260
+ * used to verify our signed calls to your endpoint.
261
+ */
262
+ create: (identifier: string, options: CreateAgentToolOptions) => Promise<{
263
+ tool: AgentTool;
264
+ secret: string;
265
+ }>;
266
+ /** Every tool registered on this agent. */
267
+ list: (identifier: string) => Promise<{
268
+ tools: AgentTool[];
269
+ }>;
270
+ /** Patch any subset; `status: 'disabled'` hides the tool from the model. */
271
+ update: (identifier: string, toolId: string, patch: Partial<Omit<CreateAgentToolOptions, "name">> & {
272
+ status?: "active" | "disabled";
273
+ }) => Promise<{
274
+ tool: AgentTool;
275
+ }>;
276
+ delete: (identifier: string, toolId: string) => Promise<{
277
+ deleted: boolean;
278
+ }>;
279
+ /** New call secret, shown once; the old one stops working immediately. */
280
+ rotateSecret: (identifier: string, toolId: string) => Promise<{
281
+ secret: string;
282
+ }>;
283
+ };
284
+ };
285
+ readonly approvals: {
286
+ /** Gated tool calls: `pending` (default) awaiting review, or `decided`. */
287
+ list: (filters?: {
288
+ status?: "pending" | "decided";
289
+ }) => Promise<{
290
+ approvals: ToolApproval[];
291
+ }>;
292
+ /** Approve or deny a pending call — atomic: 409 once already decided. */
293
+ decide: (id: string, decision: "approve" | "deny", note?: string) => Promise<{
294
+ id: string;
295
+ status: string;
296
+ }>;
297
+ };
298
+ readonly settings: {
299
+ /** Current approval-channel config + count of linked telegram approvers. */
300
+ getApprovals: () => Promise<{
301
+ settings: ApprovalSettings;
302
+ telegramApproverCount: number;
303
+ }>;
304
+ /**
305
+ * Merge-patch approval channels (an absent field is kept). An explicit
306
+ * `null` CLEARS a field; `slackChannelId` requires an active
307
+ * `slackConnectionId` (and is cleared when that connection is nulled).
308
+ */
309
+ putApprovals: (patch: Partial<ApprovalSettings>) => Promise<{
310
+ settings: ApprovalSettings;
311
+ }>;
204
312
  };
205
313
  readonly conversations: {
206
314
  /** Conversations across your agents, newest first. */
@@ -298,4 +406,4 @@ declare class AsyncifyClient {
298
406
  }>;
299
407
  }
300
408
 
301
- export { type Agent, AsyncifyClient, type AsyncifyClientOptions, AsyncifyError, type ConversationSummary, type CreateAgentOptions, type Priority, type Recipient, type TriggerOptions, type TriggerRecipient, type TriggerResult, type WorkflowStep };
409
+ export { type Agent, type AgentTool, type ApprovalSettings, AsyncifyClient, type AsyncifyClientOptions, AsyncifyError, type ConversationSummary, type CreateAgentOptions, type CreateAgentToolOptions, type Priority, type Recipient, type ToolApproval, type TriggerOptions, type TriggerRecipient, type TriggerResult, type WorkflowStep };
package/dist/index.js CHANGED
@@ -80,8 +80,69 @@ var AsyncifyClient = class {
80
80
  linkToken: (agentIdentifier, subscriberId) => this.request(
81
81
  "POST",
82
82
  `/v1/agents/${encodeURIComponent(agentIdentifier)}/subscribers/${encodeURIComponent(subscriberId)}/link-token`
83
+ ),
84
+ /**
85
+ * The per-agent custom tool registry (managed runtime dispatches these).
86
+ * Reads as `client.agents.tools.create('acme-support', {...})`.
87
+ */
88
+ tools: {
89
+ /**
90
+ * Register a tool. The secret is returned EXACTLY ONCE — store it; it is
91
+ * used to verify our signed calls to your endpoint.
92
+ */
93
+ create: (identifier, options) => this.request(
94
+ "POST",
95
+ `/v1/agents/${encodeURIComponent(identifier)}/tools`,
96
+ options
97
+ ),
98
+ /** Every tool registered on this agent. */
99
+ list: (identifier) => this.request(
100
+ "GET",
101
+ `/v1/agents/${encodeURIComponent(identifier)}/tools`
102
+ ),
103
+ /** Patch any subset; `status: 'disabled'` hides the tool from the model. */
104
+ update: (identifier, toolId, patch) => this.request(
105
+ "PATCH",
106
+ `/v1/agents/${encodeURIComponent(identifier)}/tools/${encodeURIComponent(toolId)}`,
107
+ patch
108
+ ),
109
+ delete: (identifier, toolId) => this.request(
110
+ "DELETE",
111
+ `/v1/agents/${encodeURIComponent(identifier)}/tools/${encodeURIComponent(toolId)}`
112
+ ),
113
+ /** New call secret, shown once; the old one stops working immediately. */
114
+ rotateSecret: (identifier, toolId) => this.request(
115
+ "POST",
116
+ `/v1/agents/${encodeURIComponent(identifier)}/tools/${encodeURIComponent(toolId)}/rotate-secret`
117
+ )
118
+ }
119
+ };
120
+ approvals = {
121
+ /** Gated tool calls: `pending` (default) awaiting review, or `decided`. */
122
+ list: (filters = {}) => {
123
+ const qs = filters.status ? `?status=${filters.status}` : "";
124
+ return this.request("GET", `/v1/approvals${qs}`);
125
+ },
126
+ /** Approve or deny a pending call — atomic: 409 once already decided. */
127
+ decide: (id, decision, note) => this.request(
128
+ "POST",
129
+ `/v1/approvals/${encodeURIComponent(id)}/decision`,
130
+ { decision, note }
83
131
  )
84
132
  };
133
+ settings = {
134
+ /** Current approval-channel config + count of linked telegram approvers. */
135
+ getApprovals: () => this.request(
136
+ "GET",
137
+ "/v1/settings/approvals"
138
+ ),
139
+ /**
140
+ * Merge-patch approval channels (an absent field is kept). An explicit
141
+ * `null` CLEARS a field; `slackChannelId` requires an active
142
+ * `slackConnectionId` (and is cleared when that connection is nulled).
143
+ */
144
+ putApprovals: (patch) => this.request("PUT", "/v1/settings/approvals", patch)
145
+ };
85
146
  conversations = {
86
147
  /** Conversations across your agents, newest first. */
87
148
  list: (filters = {}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asyncify-hq/node",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Server-side SDK for Asyncify — multi-channel notification infrastructure (email, SMS, push, in-app)",
5
5
  "keywords": [
6
6
  "notifications",