@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
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A Claude agent that can read anything, but rings a phone before it runs a
3
+ * shell command or writes a file.
4
+ *
5
+ * M91_SEND_LINK=... ANTHROPIC_API_KEY=... npx tsx examples/claude-agent-sdk.ts
6
+ */
7
+
8
+ import { query } from '@anthropic-ai/claude-agent-sdk';
9
+
10
+ import { M91Client } from '@msg91comm/m91';
11
+ import { createM91CanUseTool, createM91McpServer } from '@msg91comm/m91/integrations/claude-agent-sdk';
12
+
13
+ const client = new M91Client(process.env.M91_SEND_LINK!);
14
+
15
+ for await (const message of query({
16
+ prompt: 'Find the feature branches merged more than 30 days ago and delete them.',
17
+ options: {
18
+ canUseTool: createM91CanUseTool(client, { tools: ['Bash', 'Write', 'Edit'] }),
19
+ mcpServers: { m91: await createM91McpServer(client) },
20
+ },
21
+ })) {
22
+ if (message.type === 'result') console.log(message);
23
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A Deep Agent that can plan and research on its own, but rings a phone before
3
+ * it writes files or runs shell commands.
4
+ *
5
+ * M91_SEND_LINK=... ANTHROPIC_API_KEY=... npx tsx examples/deep-agents.ts
6
+ */
7
+
8
+ import { MemorySaver } from '@langchain/langgraph';
9
+ import { createDeepAgent } from 'deepagents';
10
+
11
+ import { M91Client } from '@msg91comm/m91';
12
+ import { invokeWithM91Approvals } from '@msg91comm/m91/integrations/langchain';
13
+
14
+ const client = new M91Client(process.env.M91_SEND_LINK!);
15
+
16
+ const agent = createDeepAgent({
17
+ model: 'anthropic:claude-sonnet-4-5',
18
+ systemPrompt: 'You are a release engineer.',
19
+ // These tools pause the run; invokeWithM91Approvals rings a phone for each.
20
+ interruptOn: { write_file: true, edit_file: true, execute: true },
21
+ checkpointer: new MemorySaver(),
22
+ });
23
+
24
+ const result = await invokeWithM91Approvals(
25
+ agent,
26
+ { messages: [{ role: 'user', content: 'Write the release notes for v2.4 to RELEASE.md.' }] },
27
+ { configurable: { thread_id: 'release-v2.4' } },
28
+ client,
29
+ { timeoutSeconds: 600 },
30
+ );
31
+ console.log(result.messages.at(-1)?.content);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * A Google ADK agent whose refunds ring a phone before they run.
3
+ *
4
+ * M91_SEND_LINK=... GOOGLE_GENAI_API_KEY=... npx tsx examples/google-adk.ts
5
+ */
6
+
7
+ import { FunctionTool, InMemoryRunner, LlmAgent } from '@google/adk';
8
+ import { z } from 'zod';
9
+
10
+ import { M91Client } from '@msg91comm/m91';
11
+ import { createM91BeforeToolCallback } from '@msg91comm/m91/integrations/google-adk';
12
+
13
+ const client = new M91Client(process.env.M91_SEND_LINK!);
14
+
15
+ const issueRefund = new FunctionTool({
16
+ name: 'issue_refund',
17
+ description: 'Refund an order to the original payment method.',
18
+ parameters: z.object({ orderId: z.string(), amountInr: z.number() }),
19
+ execute: async ({ orderId, amountInr }) => ({ status: 'refunded', orderId, amountInr }),
20
+ });
21
+
22
+ const agent = new LlmAgent({
23
+ name: 'support_agent',
24
+ model: 'gemini-2.5-flash',
25
+ instruction: 'Resolve refund requests. If a refund is rejected, tell the customer why.',
26
+ tools: [issueRefund],
27
+ beforeToolCallback: createM91BeforeToolCallback(client, { tools: ['issue_refund'] }),
28
+ });
29
+
30
+ const runner = new InMemoryRunner({ agent, appName: 'm91-example' });
31
+ const session = await runner.sessionService.createSession({ appName: 'm91-example', userId: 'u1' });
32
+ for await (const event of runner.runAsync({
33
+ userId: 'u1',
34
+ sessionId: session.id,
35
+ newMessage: { role: 'user', parts: [{ text: 'Order ORD-40122 never arrived. Refund 4,800 INR.' }] },
36
+ })) {
37
+ const text = event.content?.parts?.map((p) => p.text ?? '').join('');
38
+ if (text) console.log(text);
39
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * A Mastra agent whose refunds need a person's approval, by phone.
3
+ *
4
+ * M91_SEND_LINK=... OPENAI_API_KEY=... npx tsx examples/mastra.ts
5
+ */
6
+
7
+ import { Mastra } from '@mastra/core';
8
+ import { Agent } from '@mastra/core/agent';
9
+ import { createTool } from '@mastra/core/tools';
10
+ import { z } from 'zod';
11
+
12
+ import { M91Client } from '@msg91comm/m91';
13
+ import { generateWithM91Approvals } from '@msg91comm/m91/integrations/mastra';
14
+
15
+ const client = new M91Client(process.env.M91_SEND_LINK!);
16
+
17
+ const issueRefund = createTool({
18
+ id: 'issue-refund',
19
+ description: 'Refund an order to the original payment method.',
20
+ inputSchema: z.object({ orderId: z.string(), amountInr: z.number() }),
21
+ requireApproval: true,
22
+ execute: async ({ orderId, amountInr }) => ({ status: 'refunded', orderId, amountInr }),
23
+ });
24
+
25
+ const support = new Agent({
26
+ id: 'support',
27
+ name: 'Support',
28
+ instructions: 'Resolve refund requests. If a refund is declined, tell the customer why.',
29
+ model: 'openai/gpt-5-mini',
30
+ tools: { issueRefund },
31
+ });
32
+
33
+ // Suspended runs live in Mastra's storage: add a storage provider here in production.
34
+ const mastra = new Mastra({ agents: { support } });
35
+
36
+ const output = await generateWithM91Approvals(
37
+ mastra.getAgent('support'),
38
+ 'Order ORD-40122 never arrived. Refund 4,800 INR.',
39
+ client,
40
+ );
41
+ console.log(output.text);
@@ -0,0 +1,33 @@
1
+ /**
2
+ * An OpenAI Agents SDK agent whose refunds need a person's approval, by phone.
3
+ *
4
+ * M91_SEND_LINK=... OPENAI_API_KEY=... npx tsx examples/openai-agents.ts
5
+ */
6
+
7
+ import { Agent, tool } from '@openai/agents';
8
+ import { z } from 'zod';
9
+
10
+ import { M91Client } from '@msg91comm/m91';
11
+ import { runWithM91Approvals } from '@msg91comm/m91/integrations/openai-agents';
12
+
13
+ const client = new M91Client(process.env.M91_SEND_LINK!);
14
+
15
+ const issueRefund = tool({
16
+ name: 'issue_refund',
17
+ description: 'Refund an order to the original payment method.',
18
+ parameters: z.object({ orderId: z.string(), amountInr: z.number() }),
19
+ // The SDK pauses here; runWithM91Approvals rings a phone and resumes.
20
+ needsApproval: true,
21
+ execute: async ({ orderId, amountInr }) => `Refunded ${amountInr} INR on ${orderId}.`,
22
+ });
23
+
24
+ const agent = new Agent({
25
+ name: 'Support',
26
+ instructions: 'Resolve refund requests. If a refund is not approved, tell the customer why.',
27
+ tools: [issueRefund],
28
+ });
29
+
30
+ const result = await runWithM91Approvals(agent, 'Order ORD-40122 never arrived. Refund 4,800 INR.', client, {
31
+ timeoutSeconds: 600,
32
+ });
33
+ console.log(result.finalOutput);
@@ -38,7 +38,7 @@ for (const url of builder.buildMany([
38
38
 
39
39
  // In a crontab, where there is no Node at all:
40
40
  //
41
- // 0 9 * * * curl -fsS "https://m91.msg91.com/s/TOKEN?title=Daily+backup+did+not+run&severity=HIGH"
41
+ // 0 9 * * * curl -fsS "https://m91-api.msg91.com/s/TOKEN?title=Daily+backup+did+not+run&severity=HIGH"
42
42
  //
43
43
  // The URL contains the token, so it is a credential. Keep it out of anything
44
44
  // public, and out of anything that ends up in a screenshot.
package/package.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "name": "@msg91comm/m91",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Critical alerting and human-in-the-loop approval for AI agents and on-call systems",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "MSG91",
8
- "publishConfig": {
9
- "access": "public"
10
- },
11
8
  "keywords": [
12
9
  "alerting",
13
10
  "on-call",
@@ -15,11 +12,26 @@
15
12
  "human-in-the-loop",
16
13
  "agent",
17
14
  "approval",
18
- "escalation"
15
+ "escalation",
16
+ "langchain",
17
+ "langgraph",
18
+ "langchainjs",
19
+ "vercel-ai",
20
+ "ai-sdk",
21
+ "openai-agents",
22
+ "claude-agent-sdk",
23
+ "google-adk",
24
+ "mastra",
25
+ "tool-approval"
19
26
  ],
20
- "homepage": "https://m91.msg91.com",
21
- "repository": { "type": "git", "url": "git+https://github.com/MSG91/m91-sdk.git" },
22
- "bugs": { "url": "https://github.com/MSG91/m91-sdk/issues" },
27
+ "homepage": "https://m91-api.msg91.com/docs",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/MSG91/m91-sdk.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/MSG91/m91-sdk/issues"
34
+ },
23
35
  "main": "./dist/src/index.js",
24
36
  "types": "./dist/src/index.d.ts",
25
37
  "exports": {
@@ -39,6 +51,22 @@
39
51
  "./integrations/vercel-ai": {
40
52
  "types": "./dist/src/integrations/vercel-ai.d.ts",
41
53
  "default": "./dist/src/integrations/vercel-ai.js"
54
+ },
55
+ "./integrations/openai-agents": {
56
+ "types": "./dist/src/integrations/openai-agents.d.ts",
57
+ "default": "./dist/src/integrations/openai-agents.js"
58
+ },
59
+ "./integrations/claude-agent-sdk": {
60
+ "types": "./dist/src/integrations/claude-agent-sdk.d.ts",
61
+ "default": "./dist/src/integrations/claude-agent-sdk.js"
62
+ },
63
+ "./integrations/google-adk": {
64
+ "types": "./dist/src/integrations/google-adk.d.ts",
65
+ "default": "./dist/src/integrations/google-adk.js"
66
+ },
67
+ "./integrations/mastra": {
68
+ "types": "./dist/src/integrations/mastra.d.ts",
69
+ "default": "./dist/src/integrations/mastra.js"
42
70
  }
43
71
  },
44
72
  "files": [
@@ -57,24 +85,48 @@
57
85
  "test": "tsc && node --test dist/tests/*.test.js",
58
86
  "prepublishOnly": "npm run build"
59
87
  },
60
- "dependencies": {},
61
88
  "devDependencies": {
89
+ "@anthropic-ai/claude-agent-sdk": "^0.3.308",
90
+ "@google/adk": "^2.3.0",
91
+ "@langchain/core": "^1.2.20",
92
+ "@mastra/core": "^1.79.0",
93
+ "@openai/agents": "^0.21.0",
62
94
  "@types/node": "^22.0.0",
63
- "typescript": "^5.4.0"
95
+ "ai": "^7.0.148",
96
+ "deepagents": "^1.15.1",
97
+ "langchain": "^1.5.18",
98
+ "typescript": "^5.4.0",
99
+ "zod": "^4.6.7"
64
100
  },
65
101
  "peerDependencies": {
102
+ "@anthropic-ai/claude-agent-sdk": ">=0.3.0",
103
+ "@google/adk": ">=2.3.0",
66
104
  "@langchain/core": ">=0.2.0",
67
105
  "@langchain/langgraph": ">=0.2.0",
68
- "ai": ">=3.0.0",
106
+ "@mastra/core": ">=1.79.0",
107
+ "@openai/agents": ">=0.21.0",
108
+ "ai": ">=5.0.0",
69
109
  "zod": ">=3.0.0"
70
110
  },
71
111
  "peerDependenciesMeta": {
112
+ "@anthropic-ai/claude-agent-sdk": {
113
+ "optional": true
114
+ },
115
+ "@google/adk": {
116
+ "optional": true
117
+ },
72
118
  "@langchain/core": {
73
119
  "optional": true
74
120
  },
75
121
  "@langchain/langgraph": {
76
122
  "optional": true
77
123
  },
124
+ "@mastra/core": {
125
+ "optional": true
126
+ },
127
+ "@openai/agents": {
128
+ "optional": true
129
+ },
78
130
  "ai": {
79
131
  "optional": true
80
132
  },
package/src/index.ts CHANGED
@@ -88,4 +88,4 @@ export type {
88
88
  * written twice — and it drifted, reporting 1.0.0 while the package shipped as
89
89
  * 1.1.0. A test is what makes two copies safe.
90
90
  */
91
- export const VERSION = '1.1.0';
91
+ export const VERSION = '1.2.0';
@@ -0,0 +1,158 @@
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
+
33
+ import type {
34
+ CanUseTool,
35
+ HookCallbackMatcher,
36
+ McpSdkServerConfigWithInstance,
37
+ PermissionResult,
38
+ } from '@anthropic-ai/claude-agent-sdk';
39
+
40
+ import type { M91Client } from '../client.js';
41
+ import {
42
+ ALERT_DESCRIPTION,
43
+ ALERT_TOOL_NAME,
44
+ APPROVAL_DESCRIPTION,
45
+ APPROVAL_TOOL_NAME,
46
+ type AlertArgs,
47
+ type AlertDefaults,
48
+ type ApprovalArgs,
49
+ type ApprovalDefaults,
50
+ type GateOptions,
51
+ decideToolCall,
52
+ needsHuman,
53
+ runAlert,
54
+ runApproval,
55
+ } from './specs.js';
56
+ import { alertShape, approvalShape, loadZod } from './zod-shapes.js';
57
+
58
+ /** Tool names, or a predicate. Required: an agent that rings for every `Read` gets muted. */
59
+ type ClaudeGateOptions = GateOptions & { tools: NonNullable<GateOptions['tools']> };
60
+
61
+ /**
62
+ * A `canUseTool` callback that asks a person on their phone before the tools
63
+ * in `options.tools` run.
64
+ */
65
+ export function createM91CanUseTool(client: M91Client, options: ClaudeGateOptions): CanUseTool {
66
+ return async (toolName, input, { toolUseID }): Promise<PermissionResult> => {
67
+ if (!(await needsHuman(options, toolName, input))) {
68
+ return { behavior: 'allow', updatedInput: input };
69
+ }
70
+ const decision = await decideToolCall(client, { toolName, input, callId: toolUseID }, options);
71
+ return decision.approved
72
+ ? { behavior: 'allow', updatedInput: input }
73
+ : { behavior: 'deny', message: decision.reason };
74
+ };
75
+ }
76
+
77
+ /**
78
+ * A `PreToolUse` hook matcher that asks a person before the tools in
79
+ * `options.tools` run, in every permission mode. Pass it as
80
+ * `options.hooks.PreToolUse`.
81
+ *
82
+ * ```ts
83
+ * query({ prompt, options: { hooks: { PreToolUse: createM91PreToolUseHook(client, { tools: ['Bash'] }) } } })
84
+ * ```
85
+ */
86
+ export function createM91PreToolUseHook(
87
+ client: M91Client,
88
+ options: ClaudeGateOptions & { timeoutSeconds?: number },
89
+ ): HookCallbackMatcher[] {
90
+ return [
91
+ {
92
+ // The hook must outlive the wait for a human, or the SDK gives up first.
93
+ timeout: (options.timeoutSeconds ?? 300) + 60,
94
+ hooks: [
95
+ async (input) => {
96
+ if (input.hook_event_name !== 'PreToolUse') return {};
97
+ if (!(await needsHuman(options, input.tool_name, input.tool_input))) return {};
98
+ const decision = await decideToolCall(
99
+ client,
100
+ { toolName: input.tool_name, input: input.tool_input, callId: input.tool_use_id },
101
+ options,
102
+ );
103
+ return {
104
+ hookSpecificOutput: {
105
+ hookEventName: 'PreToolUse',
106
+ permissionDecision: decision.approved ? 'allow' : 'deny',
107
+ permissionDecisionReason: decision.reason,
108
+ },
109
+ };
110
+ },
111
+ ],
112
+ },
113
+ ];
114
+ }
115
+
116
+ interface ClaudeSdkModule {
117
+ tool: (name: string, description: string, shape: unknown, handler: unknown) => unknown;
118
+ createSdkMcpServer: (options: { name: string; version?: string; tools: unknown[] }) => unknown;
119
+ }
120
+
121
+ async function claudeSdk(): Promise<ClaudeSdkModule> {
122
+ try {
123
+ const specifier = '@anthropic-ai/claude-agent-sdk';
124
+ return (await import(specifier)) as unknown as ClaudeSdkModule;
125
+ } catch (error) {
126
+ throw new Error(
127
+ 'The Claude Agent SDK integration needs @anthropic-ai/claude-agent-sdk. Install it with: ' +
128
+ 'npm install @anthropic-ai/claude-agent-sdk',
129
+ { cause: error },
130
+ );
131
+ }
132
+ }
133
+
134
+ const text = (value: string) => ({ content: [{ type: 'text' as const, text: value }] });
135
+
136
+ /**
137
+ * `m91_request_approval` and `m91_send_alert` as an in-process MCP server.
138
+ * Register it under `mcpServers.m91`; the tools appear to Claude as
139
+ * `mcp__m91__m91_request_approval` and `mcp__m91__m91_send_alert`.
140
+ */
141
+ export async function createM91McpServer(
142
+ client: M91Client,
143
+ defaults: ApprovalDefaults & AlertDefaults = {},
144
+ ): Promise<McpSdkServerConfigWithInstance> {
145
+ const { tool, createSdkMcpServer } = await claudeSdk();
146
+ const z = await loadZod('Claude Agent SDK');
147
+ return createSdkMcpServer({
148
+ name: 'm91',
149
+ tools: [
150
+ tool(APPROVAL_TOOL_NAME, APPROVAL_DESCRIPTION, approvalShape(z), async (args: ApprovalArgs) =>
151
+ text(await runApproval(client, args, defaults)),
152
+ ),
153
+ tool(ALERT_TOOL_NAME, ALERT_DESCRIPTION, alertShape(z), async (args: AlertArgs) =>
154
+ text(await runAlert(client, args, defaults)),
155
+ ),
156
+ ],
157
+ }) as McpSdkServerConfigWithInstance;
158
+ }
@@ -0,0 +1,122 @@
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
+
31
+ import type { BaseTool, FunctionTool, SingleBeforeToolCallback } from '@google/adk';
32
+
33
+ import type { M91Client } from '../client.js';
34
+ import {
35
+ ALERT_DESCRIPTION,
36
+ ALERT_TOOL_NAME,
37
+ APPROVAL_DESCRIPTION,
38
+ APPROVAL_TOOL_NAME,
39
+ type AlertArgs,
40
+ type AlertDefaults,
41
+ type ApprovalArgs,
42
+ type ApprovalDefaults,
43
+ type GateOptions,
44
+ decideToolCall,
45
+ needsHuman,
46
+ runAlert,
47
+ runApproval,
48
+ } from './specs.js';
49
+ import { alertShape, approvalShape, loadZod } from './zod-shapes.js';
50
+
51
+ /**
52
+ * A `beforeToolCallback` that asks a person on their phone before the tools
53
+ * in `options.tools` run. Returning a result object skips the tool, which is
54
+ * how a rejection reaches the model.
55
+ */
56
+ export function createM91BeforeToolCallback(
57
+ client: M91Client,
58
+ options: GateOptions & { tools: NonNullable<GateOptions['tools']> },
59
+ ): SingleBeforeToolCallback {
60
+ return async ({ tool, args, context }) => {
61
+ const toolName = tool.name;
62
+ if (!(await needsHuman(options, toolName, args))) return undefined;
63
+ const callId = context.functionCallId ?? `${context.invocationId}-${toolName}`;
64
+ const decision = await decideToolCall(client, { toolName, input: args, callId }, options);
65
+ if (decision.approved) return undefined;
66
+ return { status: 'rejected', error: decision.reason };
67
+ };
68
+ }
69
+
70
+ interface AdkModule {
71
+ FunctionTool: new (options: unknown) => FunctionTool<any>;
72
+ }
73
+
74
+ async function adkModule(): Promise<AdkModule> {
75
+ try {
76
+ const specifier = '@google/adk';
77
+ return (await import(specifier)) as unknown as AdkModule;
78
+ } catch (error) {
79
+ throw new Error(
80
+ 'The Google ADK integration needs @google/adk. Install it with: npm install @google/adk',
81
+ { cause: error },
82
+ );
83
+ }
84
+ }
85
+
86
+ /** The blocking approval tool, `m91_request_approval`, as an ADK `FunctionTool`. */
87
+ export async function createM91ApprovalTool(
88
+ client: M91Client,
89
+ defaults: ApprovalDefaults = {},
90
+ ): Promise<BaseTool> {
91
+ const { FunctionTool } = await adkModule();
92
+ const z = await loadZod('Google ADK');
93
+ return new FunctionTool({
94
+ name: APPROVAL_TOOL_NAME,
95
+ description: APPROVAL_DESCRIPTION,
96
+ parameters: z.object(approvalShape(z)),
97
+ execute: async (args: ApprovalArgs) => ({ result: await runApproval(client, args, defaults) }),
98
+ });
99
+ }
100
+
101
+ /** The fire-and-forget alert tool, `m91_send_alert`, as an ADK `FunctionTool`. */
102
+ export async function createM91AlertTool(
103
+ client: M91Client,
104
+ defaults: AlertDefaults = {},
105
+ ): Promise<BaseTool> {
106
+ const { FunctionTool } = await adkModule();
107
+ const z = await loadZod('Google ADK');
108
+ return new FunctionTool({
109
+ name: ALERT_TOOL_NAME,
110
+ description: ALERT_DESCRIPTION,
111
+ parameters: z.object(alertShape(z)),
112
+ execute: async (args: AlertArgs) => ({ result: await runAlert(client, args, defaults) }),
113
+ });
114
+ }
115
+
116
+ /** Both tools, for an agent's `tools` array. */
117
+ export async function createM91Tools(
118
+ client: M91Client,
119
+ defaults: ApprovalDefaults & AlertDefaults = {},
120
+ ): Promise<BaseTool[]> {
121
+ return [await createM91ApprovalTool(client, defaults), await createM91AlertTool(client, defaults)];
122
+ }
@@ -8,7 +8,7 @@
8
8
  * Importing a specific adapter directly is usually better —
9
9
  * `@msg91comm/m91/integrations/langchain` — because it pulls in only what you use.
10
10
  *
11
- * `langchain.ts` and `vercel-ai.ts` both export `createM91ApprovalTool`, so
11
+ * Several adapters export `createM91ApprovalTool`, so
12
12
  * they are re-exported under namespaces rather than flattened: two functions of
13
13
  * the same name doing the same job for different frameworks is exactly the pair
14
14
  * that would silently resolve to the wrong one.
@@ -17,7 +17,11 @@
17
17
  export * as langchain from './langchain.js';
18
18
  export * as langgraph from './langgraph.js';
19
19
  export * as vercelAi from './vercel-ai.js';
20
+ export * as openaiAgents from './openai-agents.js';
21
+ export * as claudeAgentSdk from './claude-agent-sdk.js';
22
+ export * as googleAdk from './google-adk.js';
23
+ export * as mastra from './mastra.js';
20
24
 
21
25
  export { M91InterruptNotifier, createM91ApprovalNode } from './langgraph.js';
22
- export { APPROVED, REJECTED, alertResult, errorResult } from './specs.js';
23
- export type { AlertArgs, ApprovalArgs } from './specs.js';
26
+ export { APPROVED, REJECTED, alertResult, decideToolCall, errorResult } from './specs.js';
27
+ export type { AlertArgs, ApprovalArgs, GateDecision, GateOptions, GatedCall } from './specs.js';