@msg91comm/m91 0.0.0-stage → 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 +94 -0
- package/LICENSE +21 -0
- package/README.md +647 -2
- package/dist/src/client.d.ts +165 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +221 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/exceptions.d.ts +114 -0
- package/dist/src/exceptions.d.ts.map +1 -0
- package/dist/src/exceptions.js +199 -0
- package/dist/src/exceptions.js.map +1 -0
- package/dist/src/http.d.ts +71 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +150 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +49 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +45 -0
- package/dist/src/index.js.map +1 -0
- 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 +26 -0
- package/dist/src/integrations/index.d.ts.map +1 -0
- package/dist/src/integrations/index.js +25 -0
- package/dist/src/integrations/index.js.map +1 -0
- package/dist/src/integrations/langchain.d.ts +94 -0
- package/dist/src/integrations/langchain.d.ts.map +1 -0
- package/dist/src/integrations/langchain.js +129 -0
- package/dist/src/integrations/langchain.js.map +1 -0
- package/dist/src/integrations/langgraph.d.ts +79 -0
- package/dist/src/integrations/langgraph.d.ts.map +1 -0
- package/dist/src/integrations/langgraph.js +114 -0
- package/dist/src/integrations/langgraph.js.map +1 -0
- 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 +173 -0
- package/dist/src/integrations/specs.d.ts.map +1 -0
- package/dist/src/integrations/specs.js +217 -0
- package/dist/src/integrations/specs.js.map +1 -0
- package/dist/src/integrations/vercel-ai.d.ts +74 -0
- package/dist/src/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/src/integrations/vercel-ai.js +102 -0
- package/dist/src/integrations/vercel-ai.js.map +1 -0
- 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/models.d.ts +274 -0
- package/dist/src/models.d.ts.map +1 -0
- package/dist/src/models.js +383 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/platform.d.ts +176 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +286 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/url-builder.d.ts +65 -0
- package/dist/src/url-builder.d.ts.map +1 -0
- package/dist/src/url-builder.js +167 -0
- package/dist/src/url-builder.js.map +1 -0
- package/examples/approval-gate.ts +35 -0
- package/examples/basic-alert.ts +53 -0
- package/examples/claude-agent-sdk.ts +23 -0
- package/examples/deep-agents.ts +31 -0
- package/examples/google-adk.ts +39 -0
- package/examples/langchain-agent.ts +65 -0
- package/examples/langgraph-hitl.ts +70 -0
- package/examples/mastra.ts +41 -0
- package/examples/openai-agents.ts +33 -0
- package/examples/vercel-ai-agent.ts +34 -0
- package/examples/webhook-urls.ts +44 -0
- package/package.json +135 -4
- package/src/client.ts +324 -0
- package/src/exceptions.ts +229 -0
- package/src/http.ts +174 -0
- package/src/index.ts +91 -0
- package/src/integrations/claude-agent-sdk.ts +158 -0
- package/src/integrations/google-adk.ts +122 -0
- package/src/integrations/index.ts +27 -0
- package/src/integrations/langchain.ts +207 -0
- package/src/integrations/langgraph.ts +142 -0
- package/src/integrations/mastra.ts +178 -0
- package/src/integrations/openai-agents.ts +173 -0
- package/src/integrations/specs.ts +328 -0
- package/src/integrations/vercel-ai.ts +151 -0
- package/src/integrations/zod-shapes.ts +55 -0
- package/src/models.ts +660 -0
- package/src/platform.ts +343 -0
- package/src/url-builder.ts +225 -0
|
@@ -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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { ALERT_DESCRIPTION, ALERT_TOOL_NAME, APPROVAL_DESCRIPTION, APPROVAL_TOOL_NAME, decideToolCall, needsHuman, runAlert, runApproval, } from './specs.js';
|
|
31
|
+
import { alertShape, approvalShape, loadZod } from './zod-shapes.js';
|
|
32
|
+
/**
|
|
33
|
+
* A `beforeToolCallback` that asks a person on their phone before the tools
|
|
34
|
+
* in `options.tools` run. Returning a result object skips the tool, which is
|
|
35
|
+
* how a rejection reaches the model.
|
|
36
|
+
*/
|
|
37
|
+
export function createM91BeforeToolCallback(client, options) {
|
|
38
|
+
return async ({ tool, args, context }) => {
|
|
39
|
+
const toolName = tool.name;
|
|
40
|
+
if (!(await needsHuman(options, toolName, args)))
|
|
41
|
+
return undefined;
|
|
42
|
+
const callId = context.functionCallId ?? `${context.invocationId}-${toolName}`;
|
|
43
|
+
const decision = await decideToolCall(client, { toolName, input: args, callId }, options);
|
|
44
|
+
if (decision.approved)
|
|
45
|
+
return undefined;
|
|
46
|
+
return { status: 'rejected', error: decision.reason };
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async function adkModule() {
|
|
50
|
+
try {
|
|
51
|
+
const specifier = '@google/adk';
|
|
52
|
+
return (await import(specifier));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new Error('The Google ADK integration needs @google/adk. Install it with: npm install @google/adk', { cause: error });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** The blocking approval tool, `m91_request_approval`, as an ADK `FunctionTool`. */
|
|
59
|
+
export async function createM91ApprovalTool(client, defaults = {}) {
|
|
60
|
+
const { FunctionTool } = await adkModule();
|
|
61
|
+
const z = await loadZod('Google ADK');
|
|
62
|
+
return new FunctionTool({
|
|
63
|
+
name: APPROVAL_TOOL_NAME,
|
|
64
|
+
description: APPROVAL_DESCRIPTION,
|
|
65
|
+
parameters: z.object(approvalShape(z)),
|
|
66
|
+
execute: async (args) => ({ result: await runApproval(client, args, defaults) }),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** The fire-and-forget alert tool, `m91_send_alert`, as an ADK `FunctionTool`. */
|
|
70
|
+
export async function createM91AlertTool(client, defaults = {}) {
|
|
71
|
+
const { FunctionTool } = await adkModule();
|
|
72
|
+
const z = await loadZod('Google ADK');
|
|
73
|
+
return new FunctionTool({
|
|
74
|
+
name: ALERT_TOOL_NAME,
|
|
75
|
+
description: ALERT_DESCRIPTION,
|
|
76
|
+
parameters: z.object(alertShape(z)),
|
|
77
|
+
execute: async (args) => ({ result: await runAlert(client, args, defaults) }),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Both tools, for an agent's `tools` array. */
|
|
81
|
+
export async function createM91Tools(client, defaults = {}) {
|
|
82
|
+
return [await createM91ApprovalTool(client, defaults), await createM91AlertTool(client, defaults)];
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=google-adk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-adk.js","sourceRoot":"","sources":["../../../src/integrations/google-adk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework adapters, re-exported.
|
|
3
|
+
*
|
|
4
|
+
* Each one imports its framework lazily, so `npm install @msg91comm/m91` pulls in none of
|
|
5
|
+
* them and using one you have not installed says which package to install
|
|
6
|
+
* rather than failing with a bare module-not-found from three frames down.
|
|
7
|
+
*
|
|
8
|
+
* Importing a specific adapter directly is usually better —
|
|
9
|
+
* `@msg91comm/m91/integrations/langchain` — because it pulls in only what you use.
|
|
10
|
+
*
|
|
11
|
+
* Several adapters export `createM91ApprovalTool`, so
|
|
12
|
+
* they are re-exported under namespaces rather than flattened: two functions of
|
|
13
|
+
* the same name doing the same job for different frameworks is exactly the pair
|
|
14
|
+
* that would silently resolve to the wrong one.
|
|
15
|
+
*/
|
|
16
|
+
export * as langchain from './langchain.js';
|
|
17
|
+
export * as langgraph from './langgraph.js';
|
|
18
|
+
export * as vercelAi from './vercel-ai.js';
|
|
19
|
+
export * as openaiAgents from './openai-agents.js';
|
|
20
|
+
export * as claudeAgentSdk from './claude-agent-sdk.js';
|
|
21
|
+
export * as googleAdk from './google-adk.js';
|
|
22
|
+
export * as mastra from './mastra.js';
|
|
23
|
+
export { M91InterruptNotifier, createM91ApprovalNode } from './langgraph.js';
|
|
24
|
+
export { APPROVED, REJECTED, alertResult, decideToolCall, errorResult } from './specs.js';
|
|
25
|
+
export type { AlertArgs, ApprovalArgs, GateDecision, GateOptions, GatedCall } from './specs.js';
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1F,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework adapters, re-exported.
|
|
3
|
+
*
|
|
4
|
+
* Each one imports its framework lazily, so `npm install @msg91comm/m91` pulls in none of
|
|
5
|
+
* them and using one you have not installed says which package to install
|
|
6
|
+
* rather than failing with a bare module-not-found from three frames down.
|
|
7
|
+
*
|
|
8
|
+
* Importing a specific adapter directly is usually better —
|
|
9
|
+
* `@msg91comm/m91/integrations/langchain` — because it pulls in only what you use.
|
|
10
|
+
*
|
|
11
|
+
* Several adapters export `createM91ApprovalTool`, so
|
|
12
|
+
* they are re-exported under namespaces rather than flattened: two functions of
|
|
13
|
+
* the same name doing the same job for different frameworks is exactly the pair
|
|
14
|
+
* that would silently resolve to the wrong one.
|
|
15
|
+
*/
|
|
16
|
+
export * as langchain from './langchain.js';
|
|
17
|
+
export * as langgraph from './langgraph.js';
|
|
18
|
+
export * as vercelAi from './vercel-ai.js';
|
|
19
|
+
export * as openaiAgents from './openai-agents.js';
|
|
20
|
+
export * as claudeAgentSdk from './claude-agent-sdk.js';
|
|
21
|
+
export * as googleAdk from './google-adk.js';
|
|
22
|
+
export * as mastra from './mastra.js';
|
|
23
|
+
export { M91InterruptNotifier, createM91ApprovalNode } from './langgraph.js';
|
|
24
|
+
export { APPROVED, REJECTED, alertResult, decideToolCall, errorResult } from './specs.js';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangChain tools.
|
|
3
|
+
*
|
|
4
|
+
* Two of them, and the split matters more than it looks:
|
|
5
|
+
*
|
|
6
|
+
* - the approval tool **blocks**. The agent stops until a human answers on
|
|
7
|
+
* their phone. Give it to an agent that can do something irreversible.
|
|
8
|
+
* - the alert tool does not. It wakes somebody and carries on.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { M91Client } from '@msg91comm/m91';
|
|
12
|
+
* import { createM91ApprovalTool, createM91AlertTool } from '@msg91comm/m91/integrations/langchain';
|
|
13
|
+
*
|
|
14
|
+
* const client = new M91Client(process.env.M91_SEND_LINK!);
|
|
15
|
+
* const tools = [
|
|
16
|
+
* await createM91ApprovalTool(client),
|
|
17
|
+
* await createM91AlertTool(client),
|
|
18
|
+
* ...myTools,
|
|
19
|
+
* ];
|
|
20
|
+
* const agent = createAgent({ model: 'openai:gpt-5-mini', tools }); // from 'langchain'
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The factories are **async** because `@langchain/core` is an optional peer
|
|
24
|
+
* dependency, imported only when one of these is called. That keeps `npm
|
|
25
|
+
* install @msg91comm/m91` at zero dependencies, which matters for a package whose job is
|
|
26
|
+
* to work when everything else is on fire.
|
|
27
|
+
*/
|
|
28
|
+
import type { DynamicStructuredTool } from '@langchain/core/tools';
|
|
29
|
+
import type { M91Client } from '../client.js';
|
|
30
|
+
import { type AlertDefaults, type ApprovalDefaults, type GateOptions } from './specs.js';
|
|
31
|
+
/**
|
|
32
|
+
* The blocking approval gate, as a LangChain tool named `m91_request_approval`.
|
|
33
|
+
* Returns `"approved"`, `"rejected"` or `"error: …"` — and nothing but a real
|
|
34
|
+
* human approval returns `"approved"`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createM91ApprovalTool(client: M91Client, defaults?: ApprovalDefaults): Promise<DynamicStructuredTool>;
|
|
37
|
+
/**
|
|
38
|
+
* The fire-and-forget alert, as a LangChain tool named `m91_send_alert`.
|
|
39
|
+
* Returns `"sent:delivered=N"` or `"error: …"`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createM91AlertTool(client: M91Client, defaults?: AlertDefaults): Promise<DynamicStructuredTool>;
|
|
42
|
+
/** Both, in the order an agent's `tools` array usually wants them. */
|
|
43
|
+
export declare function createM91Tools(client: M91Client, defaults?: ApprovalDefaults & AlertDefaults): Promise<DynamicStructuredTool[]>;
|
|
44
|
+
/** One tool call the middleware paused on, as `HITLRequest.actionRequests` carries it. */
|
|
45
|
+
interface ActionRequestLike {
|
|
46
|
+
name: string;
|
|
47
|
+
args: Record<string, unknown>;
|
|
48
|
+
description?: string;
|
|
49
|
+
}
|
|
50
|
+
/** The payload of the interrupt `humanInTheLoopMiddleware` (and Deep Agents' `interruptOn`) raises. */
|
|
51
|
+
export interface HITLRequestLike {
|
|
52
|
+
actionRequests: ActionRequestLike[];
|
|
53
|
+
}
|
|
54
|
+
/** What the middleware expects back: one decision per action request, in order. */
|
|
55
|
+
export interface M91HITLResponse {
|
|
56
|
+
decisions: Array<{
|
|
57
|
+
type: 'approve';
|
|
58
|
+
} | {
|
|
59
|
+
type: 'reject';
|
|
60
|
+
message: string;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Ask a person about every tool call in a `HITLRequest`, one phone alert per
|
|
65
|
+
* call, and build the `{ decisions }` the middleware resumes with.
|
|
66
|
+
*
|
|
67
|
+
* `"approved"` / `"rejected"` strings (what `M91InterruptNotifier` returns) are
|
|
68
|
+
* **not** a valid resume value for the middleware; this is.
|
|
69
|
+
*
|
|
70
|
+
* `threadId` makes each alert's customId stable across a retry of the same
|
|
71
|
+
* pause, so a retried resume does not ring twice.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveHITLWithM91(request: HITLRequestLike, client: M91Client, options?: GateOptions & {
|
|
74
|
+
threadId?: string;
|
|
75
|
+
}): Promise<M91HITLResponse>;
|
|
76
|
+
/** The parts of a LangChain agent / Deep Agent / compiled graph this file calls. */
|
|
77
|
+
export interface InvokableAgentLike {
|
|
78
|
+
invoke(input: any, config?: any): Promise<any>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* `agent.invoke()`, with every `humanInTheLoopMiddleware` pause answered on a
|
|
82
|
+
* phone through M91 and resumed with `Command({ resume: { decisions } })`.
|
|
83
|
+
* Works for `createAgent` with `humanInTheLoopMiddleware` and for Deep Agents
|
|
84
|
+
* created with `interruptOn`. Needs a checkpointer and a `thread_id`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function invokeWithM91Approvals<TResult = any>(agent: InvokableAgentLike, input: unknown, config: {
|
|
87
|
+
configurable: {
|
|
88
|
+
thread_id: string;
|
|
89
|
+
};
|
|
90
|
+
} & Record<string, unknown>, client: M91Client, options?: GateOptions & {
|
|
91
|
+
maxRounds?: number;
|
|
92
|
+
}): Promise<TResult>;
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=langchain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langchain.d.ts","sourceRoot":"","sources":["../../../src/integrations/langchain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAQL,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAIjB,MAAM,YAAY,CAAC;AAmBpB;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAqB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAOhC;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,aAAkB,GAC3B,OAAO,CAAC,qBAAqB,CAAC,CAOhC;AAED,sEAAsE;AACtE,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAgB,GAAG,aAAkB,GAC9C,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAKlC;AAMD,0FAA0F;AAC1F,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,iBAAiB,EAAE,CAAC;CACrC;AAED,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7E;AAMD;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,OAAO,CAAC,eAAe,CAAC,CAY1B;AAED,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CAChD;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,GAAG,GAAG,EACxD,KAAK,EAAE,kBAAkB,EACzB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE;IAAE,YAAY,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzE,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,WAAW,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACjD,OAAO,CAAC,OAAO,CAAC,CAclB"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangChain tools.
|
|
3
|
+
*
|
|
4
|
+
* Two of them, and the split matters more than it looks:
|
|
5
|
+
*
|
|
6
|
+
* - the approval tool **blocks**. The agent stops until a human answers on
|
|
7
|
+
* their phone. Give it to an agent that can do something irreversible.
|
|
8
|
+
* - the alert tool does not. It wakes somebody and carries on.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { M91Client } from '@msg91comm/m91';
|
|
12
|
+
* import { createM91ApprovalTool, createM91AlertTool } from '@msg91comm/m91/integrations/langchain';
|
|
13
|
+
*
|
|
14
|
+
* const client = new M91Client(process.env.M91_SEND_LINK!);
|
|
15
|
+
* const tools = [
|
|
16
|
+
* await createM91ApprovalTool(client),
|
|
17
|
+
* await createM91AlertTool(client),
|
|
18
|
+
* ...myTools,
|
|
19
|
+
* ];
|
|
20
|
+
* const agent = createAgent({ model: 'openai:gpt-5-mini', tools }); // from 'langchain'
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The factories are **async** because `@langchain/core` is an optional peer
|
|
24
|
+
* dependency, imported only when one of these is called. That keeps `npm
|
|
25
|
+
* install @msg91comm/m91` at zero dependencies, which matters for a package whose job is
|
|
26
|
+
* to work when everything else is on fire.
|
|
27
|
+
*/
|
|
28
|
+
import { ALERT_DESCRIPTION, ALERT_SCHEMA, ALERT_TOOL_NAME, APPROVAL_DESCRIPTION, APPROVAL_SCHEMA, APPROVAL_TOOL_NAME, decideToolCall, runAlert, runApproval, } from './specs.js';
|
|
29
|
+
async function langchainTool() {
|
|
30
|
+
try {
|
|
31
|
+
// The specifier is a variable so TypeScript does not try to resolve an
|
|
32
|
+
// optional peer dependency at build time. Compiling this package must not
|
|
33
|
+
// require every framework it can adapt to.
|
|
34
|
+
const specifier = '@langchain/core/tools';
|
|
35
|
+
const mod = (await import(specifier));
|
|
36
|
+
return mod.tool;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw new Error('The LangChain integration needs @langchain/core. Install it with: ' +
|
|
40
|
+
'npm install @langchain/core', { cause: error });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The blocking approval gate, as a LangChain tool named `m91_request_approval`.
|
|
45
|
+
* Returns `"approved"`, `"rejected"` or `"error: …"` — and nothing but a real
|
|
46
|
+
* human approval returns `"approved"`.
|
|
47
|
+
*/
|
|
48
|
+
export async function createM91ApprovalTool(client, defaults = {}) {
|
|
49
|
+
const tool = await langchainTool();
|
|
50
|
+
return tool((args) => runApproval(client, args, defaults), {
|
|
51
|
+
name: APPROVAL_TOOL_NAME,
|
|
52
|
+
description: APPROVAL_DESCRIPTION,
|
|
53
|
+
schema: APPROVAL_SCHEMA,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The fire-and-forget alert, as a LangChain tool named `m91_send_alert`.
|
|
58
|
+
* Returns `"sent:delivered=N"` or `"error: …"`.
|
|
59
|
+
*/
|
|
60
|
+
export async function createM91AlertTool(client, defaults = {}) {
|
|
61
|
+
const tool = await langchainTool();
|
|
62
|
+
return tool((args) => runAlert(client, args, defaults), {
|
|
63
|
+
name: ALERT_TOOL_NAME,
|
|
64
|
+
description: ALERT_DESCRIPTION,
|
|
65
|
+
schema: ALERT_SCHEMA,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Both, in the order an agent's `tools` array usually wants them. */
|
|
69
|
+
export async function createM91Tools(client, defaults = {}) {
|
|
70
|
+
return [
|
|
71
|
+
await createM91ApprovalTool(client, defaults),
|
|
72
|
+
await createM91AlertTool(client, defaults),
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
function isHITLRequest(value) {
|
|
76
|
+
return Array.isArray(value?.actionRequests);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Ask a person about every tool call in a `HITLRequest`, one phone alert per
|
|
80
|
+
* call, and build the `{ decisions }` the middleware resumes with.
|
|
81
|
+
*
|
|
82
|
+
* `"approved"` / `"rejected"` strings (what `M91InterruptNotifier` returns) are
|
|
83
|
+
* **not** a valid resume value for the middleware; this is.
|
|
84
|
+
*
|
|
85
|
+
* `threadId` makes each alert's customId stable across a retry of the same
|
|
86
|
+
* pause, so a retried resume does not ring twice.
|
|
87
|
+
*/
|
|
88
|
+
export async function resolveHITLWithM91(request, client, options = {}) {
|
|
89
|
+
const decisions = [];
|
|
90
|
+
for (const [index, action] of request.actionRequests.entries()) {
|
|
91
|
+
const callId = `${options.threadId ?? 'run'}-${index}`;
|
|
92
|
+
const decision = await decideToolCall(client, { toolName: action.name, input: action.args, callId }, options);
|
|
93
|
+
decisions.push(decision.approved ? { type: 'approve' } : { type: 'reject', message: decision.reason });
|
|
94
|
+
}
|
|
95
|
+
return { decisions };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* `agent.invoke()`, with every `humanInTheLoopMiddleware` pause answered on a
|
|
99
|
+
* phone through M91 and resumed with `Command({ resume: { decisions } })`.
|
|
100
|
+
* Works for `createAgent` with `humanInTheLoopMiddleware` and for Deep Agents
|
|
101
|
+
* created with `interruptOn`. Needs a checkpointer and a `thread_id`.
|
|
102
|
+
*/
|
|
103
|
+
export async function invokeWithM91Approvals(agent, input, config, client, options = {}) {
|
|
104
|
+
const { Command } = await langgraphModule();
|
|
105
|
+
const { maxRounds = 20, ...gate } = options;
|
|
106
|
+
let result = await agent.invoke(input, config);
|
|
107
|
+
for (let round = 0; round < maxRounds; round += 1) {
|
|
108
|
+
const pending = result?.__interrupt__?.[0]?.value;
|
|
109
|
+
if (!isHITLRequest(pending))
|
|
110
|
+
break;
|
|
111
|
+
const response = await resolveHITLWithM91(pending, client, {
|
|
112
|
+
...gate,
|
|
113
|
+
threadId: `${config.configurable.thread_id}-${round}`,
|
|
114
|
+
});
|
|
115
|
+
result = await agent.invoke(new Command({ resume: response }), config);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
async function langgraphModule() {
|
|
120
|
+
try {
|
|
121
|
+
const specifier = '@langchain/langgraph';
|
|
122
|
+
return (await import(specifier));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
throw new Error('Resuming a LangChain / Deep Agents interrupt needs @langchain/langgraph. Install it with: ' +
|
|
126
|
+
'npm install @langchain/langgraph', { cause: error });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=langchain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langchain.js","sourceRoot":"","sources":["../../../src/integrations/langchain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAMlB,cAAc,EACd,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,KAAK,UAAU,aAAa;IAC1B,IAAI,CAAC;QACH,uEAAuE;QACvE,0EAA0E;QAC1E,2CAA2C;QAC3C,MAAM,SAAS,GAAG,uBAAuB,CAAC;QAC1C,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAwD,CAAC;QAC7F,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,oEAAoE;YAClE,6BAA6B,EAC/B,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAiB,EACjB,WAA6B,EAAE;IAE/B,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC,CAAC,IAAkB,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;QACvE,IAAI,EAAE,kBAAkB;QACxB,WAAW,EAAE,oBAAoB;QACjC,MAAM,EAAE,eAAe;KACxB,CAA0B,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAiB,EACjB,WAA0B,EAAE;IAE5B,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC,CAAC,IAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;QACjE,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,iBAAiB;QAC9B,MAAM,EAAE,YAAY;KACrB,CAA0B,CAAC;AAC9B,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAiB,EACjB,WAA6C,EAAE;IAE/C,OAAO;QACL,MAAM,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC7C,MAAM,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC;KAC3C,CAAC;AACJ,CAAC;AAuBD,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,KAAK,CAAC,OAAO,CAAE,KAAqC,EAAE,cAAc,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAwB,EACxB,MAAiB,EACjB,UAA+C,EAAE;IAEjD,MAAM,SAAS,GAAiC,EAAE,CAAC;IACnD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/D,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,MAAM,cAAc,CACnC,MAAM,EACN,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EACrD,OAAO,CACR,CAAC;QACF,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACzG,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC;AAOD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAyB,EACzB,KAAc,EACd,MAAyE,EACzE,MAAiB,EACjB,UAAgD,EAAE;IAElD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,EAAE,CAAC;IAC5C,MAAM,EAAE,SAAS,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAC5C,IAAI,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC/C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,OAAO,GAAI,MAAM,EAAE,aAAuD,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;QAC7F,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,MAAM;QACnC,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE;YACzD,GAAG,IAAI;YACP,QAAQ,EAAE,GAAG,MAAM,CAAC,YAAY,CAAC,SAAS,IAAI,KAAK,EAAE;SACtD,CAAC,CAAC;QACH,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,MAAiB,CAAC;AAC3B,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,sBAAsB,CAAC;QACzC,OAAO,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAA+D,CAAC;IACjG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,4FAA4F;YAC1F,kCAAkC,EACpC,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangGraph helpers for the `interrupt()` pattern.
|
|
3
|
+
*
|
|
4
|
+
* LangGraph already knows how to stop a graph and resume it. What it has no
|
|
5
|
+
* opinion about is **how the human finds out it stopped** — and the default
|
|
6
|
+
* answer is that somebody notices a paused run in a web UI. At 2am, for a graph
|
|
7
|
+
* that paused because it wants to move money, nobody does. M91 fills exactly
|
|
8
|
+
* that gap: the graph pauses, a phone rings until somebody answers, and the
|
|
9
|
+
* answer resumes the graph.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here imports `@langchain/langgraph`. These are plain callables you use
|
|
12
|
+
* *around* your graph, so they work the same with `interrupt()`, with
|
|
13
|
+
* `new Command({ resume })`, or with a node you wrote by hand.
|
|
14
|
+
*/
|
|
15
|
+
import type { M91Client } from '../client.js';
|
|
16
|
+
import type { Severity } from '../models.js';
|
|
17
|
+
import { APPROVED, REJECTED } from './specs.js';
|
|
18
|
+
export interface NotifyOptions {
|
|
19
|
+
title: string;
|
|
20
|
+
customId: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
severity?: Severity;
|
|
23
|
+
approveLabel?: string;
|
|
24
|
+
rejectLabel?: string;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
pollIntervalMs?: number;
|
|
27
|
+
}
|
|
28
|
+
export type NotifyDefaults = Omit<NotifyOptions, 'title' | 'customId'>;
|
|
29
|
+
export type Verdict = typeof APPROVED | typeof REJECTED;
|
|
30
|
+
/**
|
|
31
|
+
* Wake a human about an interrupted graph and return what they said.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* const notifier = new M91InterruptNotifier(client);
|
|
35
|
+
*
|
|
36
|
+
* const result = await graph.invoke(state, config);
|
|
37
|
+
* if (result.__interrupt__) {
|
|
38
|
+
* const data = result.__interrupt__[0].value;
|
|
39
|
+
* const decision = await notifier.notify({
|
|
40
|
+
* title: data.question,
|
|
41
|
+
* customId: data.customId,
|
|
42
|
+
* timeoutMs: 600_000,
|
|
43
|
+
* });
|
|
44
|
+
* await graph.invoke(new Command({ resume: decision }), config);
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* Resolves `"approved"` or `"rejected"` and **never anything else** — a failure
|
|
49
|
+
* to reach anybody comes back as `"rejected"`, because a graph that resumes
|
|
50
|
+
* with an ambiguous value resumes with whatever the node's `if` happens to do
|
|
51
|
+
* with it.
|
|
52
|
+
*/
|
|
53
|
+
export declare class M91InterruptNotifier {
|
|
54
|
+
private readonly client;
|
|
55
|
+
private readonly defaults;
|
|
56
|
+
constructor(client: M91Client, defaults?: NotifyDefaults);
|
|
57
|
+
notify(options: NotifyOptions): Promise<Verdict>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build a graph node that asks a human and writes the answer into state.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* builder.addNode('humanApproval', createM91ApprovalNode(client, {
|
|
64
|
+
* approveLabel: 'Deploy Now',
|
|
65
|
+
* rejectLabel: 'Hold Release',
|
|
66
|
+
* timeoutMs: 600_000,
|
|
67
|
+
* }));
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* The node reads `title` (or `question`) and `customId` from state and returns
|
|
71
|
+
* `{ decision, approved }` for the next edge to branch on. Anything passed here
|
|
72
|
+
* is a default the state can override per run.
|
|
73
|
+
*/
|
|
74
|
+
export declare function createM91ApprovalNode(client: M91Client, defaults?: NotifyDefaults): (state: unknown) => Promise<{
|
|
75
|
+
decision: Verdict;
|
|
76
|
+
approved: boolean;
|
|
77
|
+
error?: string;
|
|
78
|
+
}>;
|
|
79
|
+
//# sourceMappingURL=langgraph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langgraph.d.ts","sourceRoot":"","sources":["../../../src/integrations/langgraph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,aAAa,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC;AAEvE,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,GAAG,OAAO,QAAQ,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;gBAE9B,MAAM,EAAE,SAAS,EAAE,QAAQ,GAAE,cAAmB;IAKtD,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;CAkBvD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,cAAmB,GAC5B,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgCvF"}
|