@msg91comm/m91 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +241 -19
  3. package/dist/src/index.d.ts +1 -1
  4. package/dist/src/index.js +1 -1
  5. package/dist/src/integrations/claude-agent-sdk.d.ts +63 -0
  6. package/dist/src/integrations/claude-agent-sdk.d.ts.map +1 -0
  7. package/dist/src/integrations/claude-agent-sdk.js +109 -0
  8. package/dist/src/integrations/claude-agent-sdk.js.map +1 -0
  9. package/dist/src/integrations/google-adk.d.ts +47 -0
  10. package/dist/src/integrations/google-adk.d.ts.map +1 -0
  11. package/dist/src/integrations/google-adk.js +84 -0
  12. package/dist/src/integrations/google-adk.js.map +1 -0
  13. package/dist/src/integrations/index.d.ts +7 -3
  14. package/dist/src/integrations/index.d.ts.map +1 -1
  15. package/dist/src/integrations/index.js +6 -2
  16. package/dist/src/integrations/index.js.map +1 -1
  17. package/dist/src/integrations/langchain.d.ts +56 -5
  18. package/dist/src/integrations/langchain.d.ts.map +1 -1
  19. package/dist/src/integrations/langchain.js +56 -2
  20. package/dist/src/integrations/langchain.js.map +1 -1
  21. package/dist/src/integrations/mastra.d.ts +75 -0
  22. package/dist/src/integrations/mastra.d.ts.map +1 -0
  23. package/dist/src/integrations/mastra.js +109 -0
  24. package/dist/src/integrations/mastra.js.map +1 -0
  25. package/dist/src/integrations/openai-agents.d.ts +61 -0
  26. package/dist/src/integrations/openai-agents.d.ts.map +1 -0
  27. package/dist/src/integrations/openai-agents.js +127 -0
  28. package/dist/src/integrations/openai-agents.js.map +1 -0
  29. package/dist/src/integrations/specs.d.ts +42 -0
  30. package/dist/src/integrations/specs.d.ts.map +1 -1
  31. package/dist/src/integrations/specs.js +61 -0
  32. package/dist/src/integrations/specs.js.map +1 -1
  33. package/dist/src/integrations/vercel-ai.d.ts +44 -6
  34. package/dist/src/integrations/vercel-ai.d.ts.map +1 -1
  35. package/dist/src/integrations/vercel-ai.js +38 -3
  36. package/dist/src/integrations/vercel-ai.js.map +1 -1
  37. package/dist/src/integrations/zod-shapes.d.ts +19 -0
  38. package/dist/src/integrations/zod-shapes.d.ts.map +1 -0
  39. package/dist/src/integrations/zod-shapes.js +44 -0
  40. package/dist/src/integrations/zod-shapes.js.map +1 -0
  41. package/dist/src/platform.d.ts +1 -1
  42. package/dist/src/platform.d.ts.map +1 -1
  43. package/dist/src/platform.js +1 -1
  44. package/dist/src/platform.js.map +1 -1
  45. package/dist/src/url-builder.js +1 -1
  46. package/dist/src/url-builder.js.map +1 -1
  47. package/examples/basic-alert.ts +1 -1
  48. package/examples/claude-agent-sdk.ts +23 -0
  49. package/examples/deep-agents.ts +31 -0
  50. package/examples/google-adk.ts +39 -0
  51. package/examples/mastra.ts +41 -0
  52. package/examples/openai-agents.ts +33 -0
  53. package/examples/webhook-urls.ts +1 -1
  54. package/package.json +63 -11
  55. package/src/index.ts +1 -1
  56. package/src/integrations/claude-agent-sdk.ts +158 -0
  57. package/src/integrations/google-adk.ts +122 -0
  58. package/src/integrations/index.ts +7 -3
  59. package/src/integrations/langchain.ts +109 -6
  60. package/src/integrations/mastra.ts +178 -0
  61. package/src/integrations/openai-agents.ts +173 -0
  62. package/src/integrations/specs.ts +104 -0
  63. package/src/integrations/vercel-ai.ts +59 -7
  64. package/src/integrations/zod-shapes.ts +55 -0
  65. package/src/platform.ts +1 -1
  66. package/src/url-builder.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Added
6
+ - **OpenAI Agents SDK** (`@msg91comm/m91/integrations/openai-agents`): `runWithM91Approvals` answers every `needsApproval` interruption on a phone and resumes the run; `approveInterruptionsWithM91` for your own loop; `createM91Tools`.
7
+ - **Claude Agent SDK** (`@msg91comm/m91/integrations/claude-agent-sdk`): `createM91CanUseTool` (a `canUseTool` callback), `createM91PreToolUseHook` (gates in every permission mode), and `createM91McpServer` (the two tools as an in-process MCP server).
8
+ - **Google ADK for TypeScript** (`@msg91comm/m91/integrations/google-adk`): `createM91BeforeToolCallback` gates named tools; `createM91Tools` as `FunctionTool`s.
9
+ - **Mastra** (`@msg91comm/m91/integrations/mastra`): `generateWithM91Approvals` answers `requireApproval` suspensions; `createM91Tools`.
10
+ - **Vercel AI SDK**: `createM91ToolApproval`, a `toolApproval` function that rings a phone before the tools you name run.
11
+ - **LangChain `humanInTheLoopMiddleware` and Deep Agents** (`@msg91comm/m91/integrations/langchain`): `invokeWithM91Approvals` answers each `interruptOn` pause on a phone and resumes with `Command({ resume: { decisions } })`; `resolveHITLWithM91` builds the decisions for your own loop.
12
+ - `decideToolCall`, `GateOptions`: the shared fail-closed gate every adapter above uses.
13
+
14
+ ### Fixed
15
+ - Vercel AI SDK tool factories return `Tool` / `ToolSet`, and LangChain's return `DynamicStructuredTool`, instead of `unknown`, so they type-check in `generateText({ tools })` and `createAgent({ tools })`. Runtime behaviour is unchanged.
16
+ - The `ai` peer range is `>=5.0.0`, matching the `inputSchema` tool format.
17
+ - Docs and docstrings use the published package name (`@msg91comm/m91`) and current APIs (`createAgent`, `isStepCount`).
18
+
19
+ ## Fixed
20
+ - `@msg91comm/m91/integrations/vercel-ai`: `createM91ApprovalTool`, `createM91AlertTool` and `createM91Tools` are now typed as the AI SDK's `Tool` / `ToolSet` instead of `unknown`, so they type-check inside `generateText({ tools })`. Runtime behaviour is unchanged.
21
+ - The `ai` peer dependency range is now `>=5.0.0`, matching the `inputSchema` tool format these factories use.
22
+
23
+ ## 1.1.1
24
+
25
+ Metadata only — no code change.
26
+
27
+ `homepage` pointed at `https://m91.msg91.com`, which does not resolve: that
28
+ host is the static site, and every backend route lives on `m91-api.msg91.com`.
29
+ npm renders `homepage` as a link on the package page and freezes it per
30
+ version, so 1.1.0's link is dead for the life of that version. It now points at
31
+ the documentation.
32
+
33
+ The package also went out as `@msg91comm/m91` while the repository still said
34
+ `"name": "m91"`, so the next publish would have gone to a different package.
35
+ The repository now matches what is published.
36
+
37
+
3
38
  ## 1.1.0
4
39
 
5
40
  Three gaps where the server had grown a field and the SDK had not, so the only
package/README.md CHANGED
@@ -28,13 +28,17 @@ dependencies, imported only when you call one:
28
28
  | `@msg91comm/m91` — client, platform, URL builder | nothing |
29
29
  | `@msg91comm/m91/integrations/langchain` | `@langchain/core` |
30
30
  | `@msg91comm/m91/integrations/langgraph` | nothing — it wraps the client, not the graph |
31
- | `@msg91comm/m91/integrations/vercel-ai` | `ai` |
31
+ | `@msg91comm/m91/integrations/vercel-ai` | `ai` (v5 or later) |
32
+ | `@msg91comm/m91/integrations/openai-agents` | `@openai/agents` |
33
+ | `@msg91comm/m91/integrations/claude-agent-sdk` | `@anthropic-ai/claude-agent-sdk`, `zod` |
34
+ | `@msg91comm/m91/integrations/google-adk` | `@google/adk`, `zod` |
35
+ | `@msg91comm/m91/integrations/mastra` | `@mastra/core`, `zod` |
32
36
 
33
37
  ---
34
38
 
35
39
  ## Quick start
36
40
 
37
- You need a **send link** — `https://m91.msg91.com/s/<token>` — from the channel
41
+ You need a **send link** — `https://m91-api.msg91.com/s/<token>` — from the channel
38
42
  in the M91 app. The token is in the URL path, so the link *is* the credential:
39
43
  keep it in the environment, never in the repository.
40
44
 
@@ -79,6 +83,58 @@ only when somebody tapped **Approve**. A rejection, a timeout and an unreachable
79
83
  channel all resolve `false` or throw — there is no configuration under which
80
84
  silence means yes.
81
85
 
86
+ ### Asking for a reason, not just a tap
87
+
88
+ An option can require something before it can be chosen. `required` blocks the
89
+ button until it is supplied; `optional` offers a skippable field.
90
+
91
+ ```ts
92
+ await client.send('Agent wants to refund ₹48,000 on order ORD-40122', {
93
+ customId: 'refund-ORD-40122',
94
+ responseOptions: [
95
+ { label: 'Approve', effect: 'SHARED' },
96
+ { label: 'Reject', effect: 'SHARED', required: ['note'] },
97
+ { label: 'Need the delivery photo', effect: 'SHARED', optional: ['attachment'] },
98
+ ],
99
+ });
100
+ ```
101
+
102
+ Only two things can be asked for: `'note'` and `'attachment'`. Requiring a note
103
+ on the rejection is the difference between your code learning *no* and learning
104
+ *why* — worth it on any branch that has to explain itself to a customer later.
105
+
106
+ Two more fields on `send`:
107
+
108
+ - **`attachments`** — https links to files the alert is about, shown on the
109
+ alert screen. URLs, never bytes.
110
+ - **`other`** — whether the free-text "Something else" reply is offered. Leave
111
+ it unset and the server's default (on) stands; set `false` when the answer
112
+ must be one of your options.
113
+
114
+ ### `callbackUrl` instead of polling
115
+
116
+ `waitForDecision` and `requestApproval` poll. For a service that is already
117
+ listening on a URL, hand M91 the URL instead and the outcome is POSTed to it
118
+ when somebody answers:
119
+
120
+ ```ts
121
+ await client.send('Agent wants to refund ₹48,000 on order ORD-40122', {
122
+ customId: 'refund-ORD-40122',
123
+ callbackUrl: 'https://your-service.example.com/hooks/m91',
124
+ responseOptions: [{ label: 'Approve', effect: 'SHARED' },
125
+ { label: 'Reject', effect: 'SHARED', required: ['note'] }],
126
+ });
127
+ ```
128
+
129
+ **It will not reach your laptop.** The callback is fetched by M91's servers and
130
+ private addresses are refused — `localhost`, `127.0.0.1`, `10.x`, `192.168.x`
131
+ and the rest — so local development and anything inside a VPC has to poll. Use
132
+ a tunnel, or keep `waitForDecision` for development and the callback in
133
+ production.
134
+
135
+ You still own the timeout either way. There is no server-side expiry: decide
136
+ what silence means before you send, and make it the safe answer.
137
+
82
138
  ---
83
139
 
84
140
  ## Severity
@@ -112,7 +168,7 @@ console.log(builder.build('CPU above 95% on prod-server-3', {
112
168
  customId: 'cpu-high-prod3',
113
169
  respond: ['On it', 'Escalate'],
114
170
  }));
115
- // https://m91.msg91.com/s/TOKEN?title=CPU+above+95%25+on+prod-server-3&severity=HIGH
171
+ // https://m91-api.msg91.com/s/TOKEN?title=CPU+above+95%25+on+prod-server-3&severity=HIGH
116
172
  // &customId=cpu-high-prod3&respond=On+it%2CEscalate
117
173
  ```
118
174
 
@@ -173,12 +229,20 @@ stale device token is the difference between waking somebody and waking nobody.
173
229
 
174
230
  The cases an API key exists for — per-user alerting, per-tenant channels, roster
175
231
  sync — and the much larger set where a send link is the right answer, are worked
176
- through at **<https://m91.msg91.com/docs>**.
232
+ through at **<https://m91-api.msg91.com/docs>**.
177
233
 
178
234
  ---
179
235
 
180
236
  ## LangChain
181
237
 
238
+ An agent that must not act alone needs a human, and the hard part is not pausing
239
+ — it is **reaching somebody**. `interrupt()` stops the graph and waits for a
240
+ resume that only arrives if a person happens to be watching a dashboard. M91
241
+ fills that half: the tool call rings every phone on a channel at full volume,
242
+ through silent mode and Do Not Disturb, escalating to a voice call if nobody
243
+ answers. The tool returns `"approved"` only when a human tapped **Approve**;
244
+ a rejection, a timeout and an unreachable channel are all a no.
245
+
182
246
  ```ts
183
247
  import { M91Client } from '@msg91comm/m91';
184
248
  import { createM91ApprovalTool, createM91AlertTool } from '@msg91comm/m91/integrations/langchain';
@@ -190,7 +254,7 @@ const tools = [
190
254
  await createM91AlertTool(client), // wakes somebody, carries on
191
255
  ...myTools,
192
256
  ];
193
- const agent = createReactAgent({ llm, tools, prompt: SYSTEM });
257
+ const agent = createAgent({ model: 'openai:gpt-5-mini', tools, systemPrompt: SYSTEM }); // from 'langchain'
194
258
  ```
195
259
 
196
260
  The factories are async because `@langchain/core` is imported only when you call
@@ -218,6 +282,37 @@ await createM91ApprovalTool(client, {
218
282
 
219
283
  ---
220
284
 
285
+ ### `humanInTheLoopMiddleware` and Deep Agents
286
+
287
+ LangChain's `humanInTheLoopMiddleware` (and Deep Agents' `interruptOn`, which is
288
+ built on it) pauses the run before the tools you name and waits for
289
+ `{ decisions: [...] }`. `invokeWithM91Approvals` answers each pause on a phone
290
+ and resumes the same thread:
291
+
292
+ ```ts
293
+ import { createDeepAgent } from 'deepagents';
294
+ import { MemorySaver } from '@langchain/langgraph';
295
+ import { invokeWithM91Approvals } from '@msg91comm/m91/integrations/langchain';
296
+
297
+ const agent = createDeepAgent({
298
+ interruptOn: { write_file: true, edit_file: true, execute: true },
299
+ checkpointer: new MemorySaver(),
300
+ });
301
+
302
+ const result = await invokeWithM91Approvals(
303
+ agent,
304
+ { messages: [{ role: 'user', content: 'Write the release notes to RELEASE.md.' }] },
305
+ { configurable: { thread_id: 'release-v2.4' } },
306
+ client,
307
+ );
308
+ ```
309
+
310
+ The same call works for `createAgent({ middleware: [humanInTheLoopMiddleware({ interruptOn })] })`.
311
+ Running your own loop? `resolveHITLWithM91(result.__interrupt__[0].value, client)`
312
+ returns the `{ decisions }` to pass to `new Command({ resume })`.
313
+
314
+ ---
315
+
221
316
  ## LangGraph
222
317
 
223
318
  LangGraph knows how to stop a graph and resume it. What it has no opinion about
@@ -255,22 +350,146 @@ The node reads `title` (or `question`) and `customId` from state, and writes
255
350
 
256
351
  ## Vercel AI SDK
257
352
 
353
+ Two ways in. **Tools** the model calls when it decides to ask:
354
+
258
355
  ```ts
259
- import { createM91ApprovalTool, createM91AlertTool } from '@msg91comm/m91/integrations/vercel-ai';
356
+ import { generateText, isStepCount } from 'ai';
357
+ import { createM91Tools } from '@msg91comm/m91/integrations/vercel-ai';
260
358
 
261
359
  const { text } = await generateText({
262
- model: openai('gpt-4o'),
263
- tools: {
264
- m91_request_approval: await createM91ApprovalTool(client),
265
- m91_send_alert: await createM91AlertTool(client),
266
- },
267
- stopWhen: stepCountIs(10),
360
+ model: 'openai/gpt-5-mini',
361
+ tools: { ...(await createM91Tools(client)) },
362
+ stopWhen: isStepCount(10),
268
363
  prompt: 'Verify the GDPR deletion request for acct_8821 and action it.',
269
364
  });
270
365
  ```
271
366
 
367
+ **A gate** on your own tools, through the AI SDK's `toolApproval` option. The
368
+ named tools ring a phone before they run; the model cannot skip it.
369
+
370
+ ```ts
371
+ import { createM91ToolApproval } from '@msg91comm/m91/integrations/vercel-ai';
372
+
373
+ await generateText({
374
+ model: 'openai/gpt-5-mini',
375
+ tools: { issueRefund, deleteUser },
376
+ toolApproval: createM91ToolApproval(client, { tools: ['issueRefund', 'deleteUser'] }),
377
+ stopWhen: isStepCount(10),
378
+ prompt: 'Refund ORD-40122.',
379
+ });
380
+ ```
381
+
272
382
  ---
273
383
 
384
+ ## OpenAI Agents SDK
385
+
386
+ Mark risky tools `needsApproval: true`. `runWithM91Approvals` answers every
387
+ pause on a phone and resumes the same run.
388
+
389
+ ```ts
390
+ import { Agent, tool } from '@openai/agents';
391
+ import { runWithM91Approvals } from '@msg91comm/m91/integrations/openai-agents';
392
+
393
+ const issueRefund = tool({
394
+ name: 'issue_refund',
395
+ description: 'Refund an order.',
396
+ parameters: z.object({ orderId: z.string(), amount: z.number() }),
397
+ needsApproval: true,
398
+ execute: async ({ orderId, amount }) => refunds.create(orderId, amount),
399
+ });
400
+
401
+ const agent = new Agent({ name: 'Support', instructions: 'Resolve refund requests.', tools: [issueRefund] });
402
+ const result = await runWithM91Approvals(agent, 'Refund ORD-40122, it never arrived.', client);
403
+ console.log(result.finalOutput);
404
+ ```
405
+
406
+ Running your own loop? `approveInterruptionsWithM91(result, client)` decides the
407
+ pending approvals on `result.state`; then call `run(agent, result.state)`.
408
+ `createM91Tools(client)` gives the agent the two tools as well.
409
+
410
+ ---
411
+
412
+ ## Claude Agent SDK
413
+
414
+ ```ts
415
+ import { query } from '@anthropic-ai/claude-agent-sdk';
416
+ import { createM91CanUseTool, createM91McpServer } from '@msg91comm/m91/integrations/claude-agent-sdk';
417
+
418
+ for await (const message of query({
419
+ prompt: 'Delete the stale feature branches and deploy main.',
420
+ options: {
421
+ // Bash and Write ring a phone first; every other tool runs as normal.
422
+ canUseTool: createM91CanUseTool(client, { tools: ['Bash', 'Write'] }),
423
+ // Optional: let Claude decide to ask, or to wake somebody.
424
+ mcpServers: { m91: await createM91McpServer(client) },
425
+ },
426
+ })) {
427
+ if (message.type === 'result') console.log(message);
428
+ }
429
+ ```
430
+
431
+ `canUseTool` only runs when the SDK would otherwise ask. To gate a tool in every
432
+ permission mode, including `bypassPermissions`, pass
433
+ `hooks: { PreToolUse: createM91PreToolUseHook(client, { tools: ['Bash'] }) }`.
434
+
435
+ ---
436
+
437
+ ## Google ADK
438
+
439
+ ```ts
440
+ import { LlmAgent } from '@google/adk';
441
+ import { createM91BeforeToolCallback, createM91Tools } from '@msg91comm/m91/integrations/google-adk';
442
+
443
+ const agent = new LlmAgent({
444
+ name: 'support_agent',
445
+ model: 'gemini-2.5-flash',
446
+ instruction: 'Resolve refund requests.',
447
+ tools: [issueRefund, ...(await createM91Tools(client))],
448
+ // issue_refund rings a phone first. A rejection skips the tool and tells the agent why.
449
+ beforeToolCallback: createM91BeforeToolCallback(client, { tools: ['issue_refund'] }),
450
+ });
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Mastra
456
+
457
+ ```ts
458
+ import { Agent } from '@mastra/core/agent';
459
+ import { createTool } from '@mastra/core/tools';
460
+ import { generateWithM91Approvals } from '@msg91comm/m91/integrations/mastra';
461
+
462
+ const issueRefund = createTool({
463
+ id: 'issue-refund',
464
+ description: 'Refund an order.',
465
+ inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
466
+ requireApproval: true,
467
+ execute: async ({ orderId, amount }) => refunds.create(orderId, amount),
468
+ });
469
+
470
+ const agent = new Agent({
471
+ id: 'support', name: 'Support', instructions: 'Resolve refund requests.',
472
+ model: 'openai/gpt-5-mini', tools: { issueRefund },
473
+ });
474
+
475
+ const output = await generateWithM91Approvals(agent, 'Refund ORD-40122.', client);
476
+ console.log(output.text);
477
+ ```
478
+
479
+ Mastra keeps suspended runs in storage, so configure a storage provider on your
480
+ `Mastra` instance.
481
+
482
+ ---
483
+
484
+ ### Which tools ring a phone
485
+
486
+ Every gate takes `tools`: a list of tool names, or a predicate
487
+ (`(name, input) => boolean`). Gates fail closed: a rejection, no answer within
488
+ `timeoutSeconds` (default 300) or an M91 error means the tool does not run, and
489
+ the agent is told why. Write the lock-screen text yourself with
490
+ `describe: (call) => ({ title, description })`.
491
+
492
+ ---
274
493
 
275
494
  ## Error handling
276
495
 
@@ -361,7 +580,7 @@ alert, and repeating it is how one condition wakes a team four times.
361
580
  | `close({ customId? , alertId? }): Promise<boolean>` | Close it. Exactly one id. |
362
581
  | `requestApproval(title, options): Promise<boolean>` | Send, wait, close. |
363
582
 
364
- `SendOptions`: `{ description?, severity?, customId?, isAutoClose?, responseOptions? }`
583
+ `SendOptions`: `{ description?, severity?, customId?, isAutoClose?, responseOptions?, other?, attachments?, callbackUrl? }`
365
584
  `WaitOptions`: `{ timeoutMs?, pollIntervalMs?, throwOnTimeout? }`
366
585
  `ApprovalOptions`: `{ customId, description?, severity?, approveLabel?, rejectLabel?, timeoutMs?, pollIntervalMs?, timeoutMeansReject? }`
367
586
 
@@ -375,6 +594,7 @@ alert, and repeating it is how one condition wakes a team four times.
375
594
  | `alertPerson(phone, title, options?): Promise<AlertResponse>` | Wake one person. |
376
595
  | `getAlert(alertId, phone): Promise<AlertDetail \| null>` | Read what they decided. |
377
596
  | `closeAlert(phone, { customId? , alertId? }): Promise<boolean>` | Close their alert. |
597
+ | `listChannels(): Promise<ProvisionedChannel[]>` | Every channel this key owns. |
378
598
  | `createChannel(name, alertOwner?): Promise<ProvisionedChannel>` | **Not idempotent.** Store the result. |
379
599
  | `addMembers(channelId, phones): Promise<InviteResult>` | Up to 500 per call. |
380
600
  | `removeMember(channelId, phone): Promise<boolean>` | Take them off the roster. |
@@ -390,7 +610,7 @@ alert, and repeating it is how one condition wakes a team four times.
390
610
  ### Types
391
611
 
392
612
  `Severity` · `Effect` · `AlertStatus` · `RecipientState` · `ResponseOption` ·
393
- `Decision` · `AlertResponse` · `AlertDetail` · `ChannelInfo` · `Recipient` ·
613
+ `Collectable` · `Attachment` · `Decision` · `AlertResponse` · `AlertDetail` · `ChannelInfo` · `Recipient` ·
394
614
  `ProvisionedChannel` · `InviteResult` · `ApiKeyInfo`
395
615
 
396
616
  Helpers: `isApproved(decision, approveLabel?)` · `validatePhone(phone)` ·
@@ -408,11 +628,12 @@ Compiles with `tsc` and runs the suite on Node's built-in test runner — no tes
408
628
  framework to install. Every test swaps `fetch`; nothing here makes a network
409
629
  call, and nothing wakes anybody.
410
630
 
411
- For the SDK against a real socket — a live server, a real poll loop, a real
412
- timeout — run the demo:
631
+ For the SDK against a real socket — a real send, a real poll loop, a real
632
+ timeout — point an example at your own channel. This wakes a phone:
413
633
 
414
634
  ```bash
415
- cd ../demo && python3 run_node_demo.py
635
+ export M91_SEND_LINK='https://m91-api.msg91.com/s/<token>'
636
+ npx tsx examples/basic-alert.ts
416
637
  ```
417
638
 
418
639
  ---
@@ -421,6 +642,7 @@ cd ../demo && python3 run_node_demo.py
421
642
 
422
643
  The `examples/` directory included with the SDK covers a basic alert, the approval
423
644
  gate, a LangChain agent, the LangGraph `interrupt()` pattern, a Vercel AI SDK
424
- agent, and webhook URLs for Grafana and friends.
645
+ agent, OpenAI Agents, Claude Agent SDK, Google ADK and Mastra agents gated by
646
+ M91, and webhook URLs for Grafana and friends.
425
647
 
426
- Full API documentation: **<https://m91.msg91.com/docs>**
648
+ Full API documentation: **<https://m91-api.msg91.com/docs>**
@@ -45,5 +45,5 @@ export type { AlertDetail, AlertResponse, AlertStatus, ApiKeyInfo, ChannelInfo,
45
45
  * written twice — and it drifted, reporting 1.0.0 while the package shipped as
46
46
  * 1.1.0. A test is what makes two copies safe.
47
47
  */
48
- export declare const VERSION = "1.1.0";
48
+ export declare const VERSION = "1.2.0";
49
49
  //# sourceMappingURL=index.d.ts.map
package/dist/src/index.js CHANGED
@@ -41,5 +41,5 @@ export { SEVERITIES, EFFECTS, ESCALATING_SEVERITIES, isApproved, validatePhone,
41
41
  * written twice — and it drifted, reporting 1.0.0 while the package shipped as
42
42
  * 1.1.0. A test is what makes two copies safe.
43
43
  */
44
- export const VERSION = '1.1.0';
44
+ export const VERSION = '1.2.0';
45
45
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`): M91 as the permission
3
+ * prompt, and M91 as tools.
4
+ *
5
+ * **Permission prompt.** `createM91CanUseTool` is a `canUseTool` callback: the
6
+ * tools you name ring a phone and run only if a person approves. Everything
7
+ * else is allowed through untouched.
8
+ *
9
+ * ```ts
10
+ * import { query } from '@anthropic-ai/claude-agent-sdk';
11
+ * import { M91Client } from '@msg91comm/m91';
12
+ * import { createM91CanUseTool } from '@msg91comm/m91/integrations/claude-agent-sdk';
13
+ *
14
+ * const client = new M91Client(process.env.M91_SEND_LINK!);
15
+ *
16
+ * for await (const message of query({
17
+ * prompt: 'Clean up the stale feature branches and deploy main.',
18
+ * options: { canUseTool: createM91CanUseTool(client, { tools: ['Bash', 'Write'] }) },
19
+ * })) {
20
+ * if (message.type === 'result') console.log(message);
21
+ * }
22
+ * ```
23
+ *
24
+ * `canUseTool` only runs when the SDK would otherwise ask. To gate a tool
25
+ * whatever the permission mode (including `bypassPermissions`), use the
26
+ * `PreToolUse` hook from `createM91PreToolUseHook` instead.
27
+ *
28
+ * **Tools.** `createM91McpServer` exposes `m91_request_approval` and
29
+ * `m91_send_alert` as an in-process MCP server, for when the agent itself
30
+ * should decide to ask or to wake somebody.
31
+ */
32
+ import type { CanUseTool, HookCallbackMatcher, McpSdkServerConfigWithInstance } from '@anthropic-ai/claude-agent-sdk';
33
+ import type { M91Client } from '../client.js';
34
+ import { type AlertDefaults, type ApprovalDefaults, type GateOptions } from './specs.js';
35
+ /** Tool names, or a predicate. Required: an agent that rings for every `Read` gets muted. */
36
+ type ClaudeGateOptions = GateOptions & {
37
+ tools: NonNullable<GateOptions['tools']>;
38
+ };
39
+ /**
40
+ * A `canUseTool` callback that asks a person on their phone before the tools
41
+ * in `options.tools` run.
42
+ */
43
+ export declare function createM91CanUseTool(client: M91Client, options: ClaudeGateOptions): CanUseTool;
44
+ /**
45
+ * A `PreToolUse` hook matcher that asks a person before the tools in
46
+ * `options.tools` run, in every permission mode. Pass it as
47
+ * `options.hooks.PreToolUse`.
48
+ *
49
+ * ```ts
50
+ * query({ prompt, options: { hooks: { PreToolUse: createM91PreToolUseHook(client, { tools: ['Bash'] }) } } })
51
+ * ```
52
+ */
53
+ export declare function createM91PreToolUseHook(client: M91Client, options: ClaudeGateOptions & {
54
+ timeoutSeconds?: number;
55
+ }): HookCallbackMatcher[];
56
+ /**
57
+ * `m91_request_approval` and `m91_send_alert` as an in-process MCP server.
58
+ * Register it under `mcpServers.m91`; the tools appear to Claude as
59
+ * `mcp__m91__m91_request_approval` and `mcp__m91__m91_send_alert`.
60
+ */
61
+ export declare function createM91McpServer(client: M91Client, defaults?: ApprovalDefaults & AlertDefaults): Promise<McpSdkServerConfigWithInstance>;
62
+ export {};
63
+ //# sourceMappingURL=claude-agent-sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-agent-sdk.d.ts","sourceRoot":"","sources":["../../../src/integrations/claude-agent-sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,mBAAmB,EACnB,8BAA8B,EAE/B,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAML,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAKjB,MAAM,YAAY,CAAC;AAGpB,6FAA6F;AAC7F,KAAK,iBAAiB,GAAG,WAAW,GAAG;IAAE,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;CAAE,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAU7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,iBAAiB,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD,mBAAmB,EAAE,CAyBvB;AAsBD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAgB,GAAG,aAAkB,GAC9C,OAAO,CAAC,8BAA8B,CAAC,CAczC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`): M91 as the permission
3
+ * prompt, and M91 as tools.
4
+ *
5
+ * **Permission prompt.** `createM91CanUseTool` is a `canUseTool` callback: the
6
+ * tools you name ring a phone and run only if a person approves. Everything
7
+ * else is allowed through untouched.
8
+ *
9
+ * ```ts
10
+ * import { query } from '@anthropic-ai/claude-agent-sdk';
11
+ * import { M91Client } from '@msg91comm/m91';
12
+ * import { createM91CanUseTool } from '@msg91comm/m91/integrations/claude-agent-sdk';
13
+ *
14
+ * const client = new M91Client(process.env.M91_SEND_LINK!);
15
+ *
16
+ * for await (const message of query({
17
+ * prompt: 'Clean up the stale feature branches and deploy main.',
18
+ * options: { canUseTool: createM91CanUseTool(client, { tools: ['Bash', 'Write'] }) },
19
+ * })) {
20
+ * if (message.type === 'result') console.log(message);
21
+ * }
22
+ * ```
23
+ *
24
+ * `canUseTool` only runs when the SDK would otherwise ask. To gate a tool
25
+ * whatever the permission mode (including `bypassPermissions`), use the
26
+ * `PreToolUse` hook from `createM91PreToolUseHook` instead.
27
+ *
28
+ * **Tools.** `createM91McpServer` exposes `m91_request_approval` and
29
+ * `m91_send_alert` as an in-process MCP server, for when the agent itself
30
+ * should decide to ask or to wake somebody.
31
+ */
32
+ import { ALERT_DESCRIPTION, ALERT_TOOL_NAME, APPROVAL_DESCRIPTION, APPROVAL_TOOL_NAME, decideToolCall, needsHuman, runAlert, runApproval, } from './specs.js';
33
+ import { alertShape, approvalShape, loadZod } from './zod-shapes.js';
34
+ /**
35
+ * A `canUseTool` callback that asks a person on their phone before the tools
36
+ * in `options.tools` run.
37
+ */
38
+ export function createM91CanUseTool(client, options) {
39
+ return async (toolName, input, { toolUseID }) => {
40
+ if (!(await needsHuman(options, toolName, input))) {
41
+ return { behavior: 'allow', updatedInput: input };
42
+ }
43
+ const decision = await decideToolCall(client, { toolName, input, callId: toolUseID }, options);
44
+ return decision.approved
45
+ ? { behavior: 'allow', updatedInput: input }
46
+ : { behavior: 'deny', message: decision.reason };
47
+ };
48
+ }
49
+ /**
50
+ * A `PreToolUse` hook matcher that asks a person before the tools in
51
+ * `options.tools` run, in every permission mode. Pass it as
52
+ * `options.hooks.PreToolUse`.
53
+ *
54
+ * ```ts
55
+ * query({ prompt, options: { hooks: { PreToolUse: createM91PreToolUseHook(client, { tools: ['Bash'] }) } } })
56
+ * ```
57
+ */
58
+ export function createM91PreToolUseHook(client, options) {
59
+ return [
60
+ {
61
+ // The hook must outlive the wait for a human, or the SDK gives up first.
62
+ timeout: (options.timeoutSeconds ?? 300) + 60,
63
+ hooks: [
64
+ async (input) => {
65
+ if (input.hook_event_name !== 'PreToolUse')
66
+ return {};
67
+ if (!(await needsHuman(options, input.tool_name, input.tool_input)))
68
+ return {};
69
+ const decision = await decideToolCall(client, { toolName: input.tool_name, input: input.tool_input, callId: input.tool_use_id }, options);
70
+ return {
71
+ hookSpecificOutput: {
72
+ hookEventName: 'PreToolUse',
73
+ permissionDecision: decision.approved ? 'allow' : 'deny',
74
+ permissionDecisionReason: decision.reason,
75
+ },
76
+ };
77
+ },
78
+ ],
79
+ },
80
+ ];
81
+ }
82
+ async function claudeSdk() {
83
+ try {
84
+ const specifier = '@anthropic-ai/claude-agent-sdk';
85
+ return (await import(specifier));
86
+ }
87
+ catch (error) {
88
+ throw new Error('The Claude Agent SDK integration needs @anthropic-ai/claude-agent-sdk. Install it with: ' +
89
+ 'npm install @anthropic-ai/claude-agent-sdk', { cause: error });
90
+ }
91
+ }
92
+ const text = (value) => ({ content: [{ type: 'text', text: value }] });
93
+ /**
94
+ * `m91_request_approval` and `m91_send_alert` as an in-process MCP server.
95
+ * Register it under `mcpServers.m91`; the tools appear to Claude as
96
+ * `mcp__m91__m91_request_approval` and `mcp__m91__m91_send_alert`.
97
+ */
98
+ export async function createM91McpServer(client, defaults = {}) {
99
+ const { tool, createSdkMcpServer } = await claudeSdk();
100
+ const z = await loadZod('Claude Agent SDK');
101
+ return createSdkMcpServer({
102
+ name: 'm91',
103
+ tools: [
104
+ tool(APPROVAL_TOOL_NAME, APPROVAL_DESCRIPTION, approvalShape(z), async (args) => text(await runApproval(client, args, defaults))),
105
+ tool(ALERT_TOOL_NAME, ALERT_DESCRIPTION, alertShape(z), async (args) => text(await runAlert(client, args, defaults))),
106
+ ],
107
+ });
108
+ }
109
+ //# sourceMappingURL=claude-agent-sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-agent-sdk.js","sourceRoot":"","sources":["../../../src/integrations/claude-agent-sdk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Google Agent Development Kit for TypeScript (`@google/adk`): M91 as a
3
+ * before-tool gate, and M91 as tools.
4
+ *
5
+ * **Gate.** `createM91BeforeToolCallback` goes in an `LlmAgent`'s
6
+ * `beforeToolCallback`. Calls to the tools you name ring a phone first; if the
7
+ * person rejects (or does not answer), the tool does not run and the agent
8
+ * gets the reason back as the tool's result.
9
+ *
10
+ * ```ts
11
+ * import { LlmAgent } from '@google/adk';
12
+ * import { M91Client } from '@msg91comm/m91';
13
+ * import { createM91BeforeToolCallback, createM91Tools } from '@msg91comm/m91/integrations/google-adk';
14
+ *
15
+ * const client = new M91Client(process.env.M91_SEND_LINK!);
16
+ *
17
+ * const agent = new LlmAgent({
18
+ * name: 'support_agent',
19
+ * model: 'gemini-2.5-flash',
20
+ * instruction: 'Resolve refund requests.',
21
+ * tools: [issueRefund, ...(await createM91Tools(client))],
22
+ * beforeToolCallback: createM91BeforeToolCallback(client, { tools: ['issue_refund'] }),
23
+ * });
24
+ * ```
25
+ *
26
+ * The gate waits for the answer in-process. For approvals that take hours,
27
+ * use ADK's own Tool Confirmation and resume the session when M91's answer
28
+ * arrives.
29
+ */
30
+ import type { BaseTool, SingleBeforeToolCallback } from '@google/adk';
31
+ import type { M91Client } from '../client.js';
32
+ import { type AlertDefaults, type ApprovalDefaults, type GateOptions } from './specs.js';
33
+ /**
34
+ * A `beforeToolCallback` that asks a person on their phone before the tools
35
+ * in `options.tools` run. Returning a result object skips the tool, which is
36
+ * how a rejection reaches the model.
37
+ */
38
+ export declare function createM91BeforeToolCallback(client: M91Client, options: GateOptions & {
39
+ tools: NonNullable<GateOptions['tools']>;
40
+ }): SingleBeforeToolCallback;
41
+ /** The blocking approval tool, `m91_request_approval`, as an ADK `FunctionTool`. */
42
+ export declare function createM91ApprovalTool(client: M91Client, defaults?: ApprovalDefaults): Promise<BaseTool>;
43
+ /** The fire-and-forget alert tool, `m91_send_alert`, as an ADK `FunctionTool`. */
44
+ export declare function createM91AlertTool(client: M91Client, defaults?: AlertDefaults): Promise<BaseTool>;
45
+ /** Both tools, for an agent's `tools` array. */
46
+ export declare function createM91Tools(client: M91Client, defaults?: ApprovalDefaults & AlertDefaults): Promise<BaseTool[]>;
47
+ //# sourceMappingURL=google-adk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-adk.d.ts","sourceRoot":"","sources":["../../../src/integrations/google-adk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAgB,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAML,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAKjB,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GAAG;IAAE,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;CAAE,GAClE,wBAAwB,CAS1B;AAkBD,oFAAoF;AACpF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAqB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CASnB;AAED,kFAAkF;AAClF,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,aAAkB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CASnB;AAED,gDAAgD;AAChD,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAgB,GAAG,aAAkB,GAC9C,OAAO,CAAC,QAAQ,EAAE,CAAC,CAErB"}