@watchlight/sdk 0.1.0 → 0.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/README.md +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/langchain.d.ts +32 -0
- package/dist/langchain.js +64 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -81,6 +81,33 @@ for await (const msg of query({ prompt, options: { hooks } })) {
|
|
|
81
81
|
The hook is fail-closed and never throws back to the SDK — a governance error
|
|
82
82
|
denies the call. Every decision is audited.
|
|
83
83
|
|
|
84
|
+
## LangChain / LangGraph.js
|
|
85
|
+
|
|
86
|
+
Govern any LangChain `StructuredTool` (which is what LangGraph.js tools are) — the
|
|
87
|
+
tool is authorized before it runs; denied tools throw and never execute.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { tool } from "@langchain/core/tools";
|
|
91
|
+
import { z } from "zod";
|
|
92
|
+
import { govern, governTool } from "@watchlight/sdk";
|
|
93
|
+
|
|
94
|
+
govern.load("watchlight.policy.json");
|
|
95
|
+
|
|
96
|
+
const search = governTool(
|
|
97
|
+
tool(async ({ query }) => webSearch(query), {
|
|
98
|
+
name: "web_search",
|
|
99
|
+
schema: z.object({ query: z.string() }),
|
|
100
|
+
}),
|
|
101
|
+
{ intent: "research" }
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Pass `search` to your LangGraph ToolNode / createReactAgent as usual.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`governTool(tool, { intent })` returns a governed view (the original tool isn't
|
|
108
|
+
mutated); `governTools(tools, { intentFor })` maps an array. Intent defaults to
|
|
109
|
+
the tool's name. Fail-closed. `@langchain/core` is a peer dependency.
|
|
110
|
+
|
|
84
111
|
## Value-free audit
|
|
85
112
|
|
|
86
113
|
`.watchlight/audit.jsonl` records **who / what intent / which tool / the
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { Scope } from "./attenuation";
|
|
|
2
2
|
export { Scope, DE_MAX_DEPTH, AttenuationDenied, DevEditionCeiling } from "./attenuation";
|
|
3
3
|
export { governedHooks } from "./claude-agent";
|
|
4
4
|
export type { GovernedHooksOptions, GovernedHooksResult } from "./claude-agent";
|
|
5
|
+
export { governTool, governTools } from "./langchain";
|
|
6
|
+
export type { LangChainToolLike, GovernToolOptions, GovernToolsOptions, } from "./langchain";
|
|
5
7
|
export type { GovernanceBackend, Decision, AuthorizeRequest } from "./backend";
|
|
6
8
|
export { InProcessBackend, NetworkedBackend } from "./backend";
|
|
7
9
|
/** Raised when the policy engine refuses a governed tool call (fail-closed). */
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
49
49
|
};
|
|
50
50
|
})();
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
-
exports.govern = exports.Watchlight = exports.Denied = exports.NetworkedBackend = exports.InProcessBackend = exports.governedHooks = exports.DevEditionCeiling = exports.AttenuationDenied = exports.DE_MAX_DEPTH = exports.Scope = void 0;
|
|
52
|
+
exports.govern = exports.Watchlight = exports.Denied = exports.NetworkedBackend = exports.InProcessBackend = exports.governTools = exports.governTool = exports.governedHooks = exports.DevEditionCeiling = exports.AttenuationDenied = exports.DE_MAX_DEPTH = exports.Scope = void 0;
|
|
53
53
|
const fs = __importStar(require("node:fs"));
|
|
54
54
|
const path = __importStar(require("node:path"));
|
|
55
55
|
const attenuation_1 = require("./attenuation");
|
|
@@ -61,6 +61,9 @@ Object.defineProperty(exports, "AttenuationDenied", { enumerable: true, get: fun
|
|
|
61
61
|
Object.defineProperty(exports, "DevEditionCeiling", { enumerable: true, get: function () { return attenuation_2.DevEditionCeiling; } });
|
|
62
62
|
var claude_agent_1 = require("./claude-agent");
|
|
63
63
|
Object.defineProperty(exports, "governedHooks", { enumerable: true, get: function () { return claude_agent_1.governedHooks; } });
|
|
64
|
+
var langchain_1 = require("./langchain");
|
|
65
|
+
Object.defineProperty(exports, "governTool", { enumerable: true, get: function () { return langchain_1.governTool; } });
|
|
66
|
+
Object.defineProperty(exports, "governTools", { enumerable: true, get: function () { return langchain_1.governTools; } });
|
|
64
67
|
var backend_2 = require("./backend");
|
|
65
68
|
Object.defineProperty(exports, "InProcessBackend", { enumerable: true, get: function () { return backend_2.InProcessBackend; } });
|
|
66
69
|
Object.defineProperty(exports, "NetworkedBackend", { enumerable: true, get: function () { return backend_2.NetworkedBackend; } });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Watchlight } from "./index";
|
|
2
|
+
/** The minimal shape of a LangChain `StructuredTool` this adapter needs. */
|
|
3
|
+
export interface LangChainToolLike {
|
|
4
|
+
name: string;
|
|
5
|
+
invoke(input: unknown, config?: unknown): Promise<unknown>;
|
|
6
|
+
[k: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface GovernToolOptions {
|
|
9
|
+
/** The governor to authorize against. Defaults to the shared `govern`. */
|
|
10
|
+
governor?: Watchlight;
|
|
11
|
+
/** Governance intent for this tool. Defaults to the tool's `name`. */
|
|
12
|
+
intent?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface GovernToolsOptions {
|
|
15
|
+
governor?: Watchlight;
|
|
16
|
+
/** Map a tool name to a governance intent. Defaults to identity (intent =
|
|
17
|
+
* tool name). */
|
|
18
|
+
intentFor?: (toolName: string) => string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wrap a LangChain / LangGraph.js tool so its `invoke` is authorized by the
|
|
22
|
+
* in-process engine before it runs. Returns a governed view of the tool (a
|
|
23
|
+
* Proxy) — pass it to your agent / `ToolNode` exactly like the original. The
|
|
24
|
+
* original tool is not mutated. Fail-closed: on anything but ALLOW, `invoke`
|
|
25
|
+
* throws `Denied` and the underlying tool never executes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function governTool<T extends LangChainToolLike>(tool: T, opts?: GovernToolOptions): T;
|
|
28
|
+
/**
|
|
29
|
+
* Govern an array of LangChain / LangGraph.js tools. `intentFor` maps each tool
|
|
30
|
+
* name to an intent (default: the tool name).
|
|
31
|
+
*/
|
|
32
|
+
export declare function governTools<T extends LangChainToolLike>(tools: T[], opts?: GovernToolsOptions): T[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// LangChain / LangGraph.js integration — govern a tool's execution with the
|
|
3
|
+
// in-process engine. The TS counterpart of Python `watchlight.langgraph`.
|
|
4
|
+
//
|
|
5
|
+
// import { tool } from "@langchain/core/tools";
|
|
6
|
+
// import { govern, governTool } from "@watchlight/sdk";
|
|
7
|
+
//
|
|
8
|
+
// govern.load("watchlight.policy.json");
|
|
9
|
+
// const search = governTool(
|
|
10
|
+
// tool(async ({ query }) => webSearch(query), { name: "web_search", schema }),
|
|
11
|
+
// { intent: "research" }
|
|
12
|
+
// );
|
|
13
|
+
// // pass `search` to your LangGraph agent / ToolNode as usual.
|
|
14
|
+
//
|
|
15
|
+
// Before the tool runs, the engine authorizes (agent, intent, tool/<name>). ALLOW
|
|
16
|
+
// runs it; anything else throws `Denied` and the tool body never executes —
|
|
17
|
+
// denied before it runs. Fail-closed. Works for any LangChain `StructuredTool`
|
|
18
|
+
// (which is what LangGraph.js tools are).
|
|
19
|
+
//
|
|
20
|
+
// This is glue: it intercepts the tool's `invoke`; the decision comes from the
|
|
21
|
+
// engine (via the shared governor). No LangChain hard dependency — the adapter is
|
|
22
|
+
// structurally typed against the tool's public shape, so `@langchain/core` stays
|
|
23
|
+
// a peer you already have installed.
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.governTool = governTool;
|
|
26
|
+
exports.governTools = governTools;
|
|
27
|
+
const index_1 = require("./index");
|
|
28
|
+
/**
|
|
29
|
+
* Wrap a LangChain / LangGraph.js tool so its `invoke` is authorized by the
|
|
30
|
+
* in-process engine before it runs. Returns a governed view of the tool (a
|
|
31
|
+
* Proxy) — pass it to your agent / `ToolNode` exactly like the original. The
|
|
32
|
+
* original tool is not mutated. Fail-closed: on anything but ALLOW, `invoke`
|
|
33
|
+
* throws `Denied` and the underlying tool never executes.
|
|
34
|
+
*/
|
|
35
|
+
function governTool(tool, opts = {}) {
|
|
36
|
+
const governor = opts.governor ?? index_1.govern;
|
|
37
|
+
const intent = opts.intent ?? tool.name;
|
|
38
|
+
const name = tool.name;
|
|
39
|
+
return new Proxy(tool, {
|
|
40
|
+
get(target, prop, receiver) {
|
|
41
|
+
if (prop === "invoke") {
|
|
42
|
+
return async (input, config) => {
|
|
43
|
+
const { allowed, reason } = await governor.check(intent, name);
|
|
44
|
+
if (!allowed) {
|
|
45
|
+
throw new index_1.Denied(name, intent, reason || "no matching policy");
|
|
46
|
+
}
|
|
47
|
+
return target.invoke(input, config);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Delegate everything else to the real tool, bound to it so `this` stays
|
|
51
|
+
// correct and internal calls hit the real (un-governed) methods.
|
|
52
|
+
const value = Reflect.get(target, prop, receiver);
|
|
53
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Govern an array of LangChain / LangGraph.js tools. `intentFor` maps each tool
|
|
59
|
+
* name to an intent (default: the tool name).
|
|
60
|
+
*/
|
|
61
|
+
function governTools(tools, opts = {}) {
|
|
62
|
+
const intentFor = opts.intentFor ?? ((n) => n);
|
|
63
|
+
return tools.map((t) => governTool(t, { governor: opts.governor, intent: intentFor(t.name) }));
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@watchlight/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Watchlight Developer Edition govern glue for Node/TypeScript — declare intent, govern a tool with a fail-closed in-process policy decision, and get a value-free audit trail. Glue over @watchlight/engine; zero decision logic in JS.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc -p tsconfig.json",
|
|
18
|
-
"test": "npm run build && node test/govern.test.mjs && node test/claude-agent.test.mjs && node test/graduation.test.mjs",
|
|
18
|
+
"test": "npm run build && node test/govern.test.mjs && node test/claude-agent.test.mjs && node test/graduation.test.mjs && node test/langchain.test.mjs",
|
|
19
19
|
"prepublishOnly": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|