@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.
- package/CHANGELOG.md +35 -0
- package/README.md +241 -19
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/integrations/claude-agent-sdk.d.ts +63 -0
- package/dist/src/integrations/claude-agent-sdk.d.ts.map +1 -0
- package/dist/src/integrations/claude-agent-sdk.js +109 -0
- package/dist/src/integrations/claude-agent-sdk.js.map +1 -0
- package/dist/src/integrations/google-adk.d.ts +47 -0
- package/dist/src/integrations/google-adk.d.ts.map +1 -0
- package/dist/src/integrations/google-adk.js +84 -0
- package/dist/src/integrations/google-adk.js.map +1 -0
- package/dist/src/integrations/index.d.ts +7 -3
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +6 -2
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/langchain.d.ts +56 -5
- package/dist/src/integrations/langchain.d.ts.map +1 -1
- package/dist/src/integrations/langchain.js +56 -2
- package/dist/src/integrations/langchain.js.map +1 -1
- package/dist/src/integrations/mastra.d.ts +75 -0
- package/dist/src/integrations/mastra.d.ts.map +1 -0
- package/dist/src/integrations/mastra.js +109 -0
- package/dist/src/integrations/mastra.js.map +1 -0
- package/dist/src/integrations/openai-agents.d.ts +61 -0
- package/dist/src/integrations/openai-agents.d.ts.map +1 -0
- package/dist/src/integrations/openai-agents.js +127 -0
- package/dist/src/integrations/openai-agents.js.map +1 -0
- package/dist/src/integrations/specs.d.ts +42 -0
- package/dist/src/integrations/specs.d.ts.map +1 -1
- package/dist/src/integrations/specs.js +61 -0
- package/dist/src/integrations/specs.js.map +1 -1
- package/dist/src/integrations/vercel-ai.d.ts +44 -6
- package/dist/src/integrations/vercel-ai.d.ts.map +1 -1
- package/dist/src/integrations/vercel-ai.js +38 -3
- package/dist/src/integrations/vercel-ai.js.map +1 -1
- package/dist/src/integrations/zod-shapes.d.ts +19 -0
- package/dist/src/integrations/zod-shapes.d.ts.map +1 -0
- package/dist/src/integrations/zod-shapes.js +44 -0
- package/dist/src/integrations/zod-shapes.js.map +1 -0
- package/dist/src/platform.d.ts +1 -1
- package/dist/src/platform.d.ts.map +1 -1
- package/dist/src/platform.js +1 -1
- package/dist/src/platform.js.map +1 -1
- package/dist/src/url-builder.js +1 -1
- package/dist/src/url-builder.js.map +1 -1
- package/examples/basic-alert.ts +1 -1
- package/examples/claude-agent-sdk.ts +23 -0
- package/examples/deep-agents.ts +31 -0
- package/examples/google-adk.ts +39 -0
- package/examples/mastra.ts +41 -0
- package/examples/openai-agents.ts +33 -0
- package/examples/webhook-urls.ts +1 -1
- package/package.json +63 -11
- package/src/index.ts +1 -1
- package/src/integrations/claude-agent-sdk.ts +158 -0
- package/src/integrations/google-adk.ts +122 -0
- package/src/integrations/index.ts +7 -3
- package/src/integrations/langchain.ts +109 -6
- package/src/integrations/mastra.ts +178 -0
- package/src/integrations/openai-agents.ts +173 -0
- package/src/integrations/specs.ts +104 -0
- package/src/integrations/vercel-ai.ts +59 -7
- package/src/integrations/zod-shapes.ts +55 -0
- package/src/platform.ts +1 -1
- 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);
|
package/examples/webhook-urls.ts
CHANGED
|
@@ -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.
|
|
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": {
|
|
22
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
@@ -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
|
-
*
|
|
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';
|