@agent-assembly/sdk 0.0.1-beta.3 → 0.0.1-beta.5
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 +36 -23
- package/dist/cjs/core/gateway-resolver.js +73 -3
- package/dist/cjs/core/init-assembly.js +155 -33
- package/dist/cjs/core/redact.js +63 -0
- package/dist/cjs/gateway/client.js +63 -1
- package/dist/cjs/gateway/index.js +2 -1
- package/dist/cjs/hooks/ai-sdk.js +46 -10
- package/dist/cjs/hooks/langchain.js +12 -3
- package/dist/cjs/hooks/mastra.js +10 -6
- package/dist/cjs/hooks/openai-agents.js +1 -3
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/native/client.js +94 -25
- package/dist/cjs/op-control.js +159 -17
- package/dist/cjs/runtime.js +73 -7
- package/dist/cjs/wrappers/with-assembly.js +89 -32
- package/dist/esm/core/gateway-resolver.js +72 -3
- package/dist/esm/core/gateway-resolver.js.map +1 -1
- package/dist/esm/core/init-assembly.js +154 -32
- package/dist/esm/core/init-assembly.js.map +1 -1
- package/dist/esm/core/redact.js +59 -0
- package/dist/esm/core/redact.js.map +1 -0
- package/dist/esm/gateway/client.js +62 -1
- package/dist/esm/gateway/client.js.map +1 -1
- package/dist/esm/gateway/index.js +1 -1
- package/dist/esm/gateway/index.js.map +1 -1
- package/dist/esm/hooks/ai-sdk.js +46 -10
- package/dist/esm/hooks/ai-sdk.js.map +1 -1
- package/dist/esm/hooks/langchain.js +12 -3
- package/dist/esm/hooks/langchain.js.map +1 -1
- package/dist/esm/hooks/mastra.js +10 -6
- package/dist/esm/hooks/mastra.js.map +1 -1
- package/dist/esm/hooks/openai-agents.js +1 -3
- package/dist/esm/hooks/openai-agents.js.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/native/client.js +91 -24
- package/dist/esm/native/client.js.map +1 -1
- package/dist/esm/op-control.js +124 -17
- package/dist/esm/op-control.js.map +1 -1
- package/dist/esm/runtime.js +72 -7
- package/dist/esm/runtime.js.map +1 -1
- package/dist/esm/wrappers/with-assembly.js +89 -32
- package/dist/esm/wrappers/with-assembly.js.map +1 -1
- package/dist/types/core/gateway-resolver.d.ts +18 -1
- package/dist/types/core/gateway-resolver.d.ts.map +1 -1
- package/dist/types/core/init-assembly.d.ts +2 -1
- package/dist/types/core/init-assembly.d.ts.map +1 -1
- package/dist/types/core/redact.d.ts +28 -0
- package/dist/types/core/redact.d.ts.map +1 -0
- package/dist/types/gateway/client.d.ts +17 -0
- package/dist/types/gateway/client.d.ts.map +1 -1
- package/dist/types/gateway/index.d.ts +1 -1
- package/dist/types/gateway/index.d.ts.map +1 -1
- package/dist/types/hooks/ai-sdk.d.ts +13 -0
- package/dist/types/hooks/ai-sdk.d.ts.map +1 -1
- package/dist/types/hooks/langchain.d.ts +11 -0
- package/dist/types/hooks/langchain.d.ts.map +1 -1
- package/dist/types/hooks/mastra.d.ts.map +1 -1
- package/dist/types/hooks/openai-agents.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/native/client.d.ts +44 -0
- package/dist/types/native/client.d.ts.map +1 -1
- package/dist/types/op-control.d.ts +71 -7
- package/dist/types/op-control.d.ts.map +1 -1
- package/dist/types/runtime.d.ts +27 -5
- package/dist/types/runtime.d.ts.map +1 -1
- package/dist/types/types/assembly-config.d.ts +6 -0
- package/dist/types/types/assembly-config.d.ts.map +1 -1
- package/dist/types/wrappers/index.d.ts +1 -1
- package/dist/types/wrappers/index.d.ts.map +1 -1
- package/dist/types/wrappers/with-assembly.d.ts +25 -0
- package/dist/types/wrappers/with-assembly.d.ts.map +1 -1
- package/native/aa-ffi-node/index.d.ts +82 -1
- package/package.json +18 -5
package/dist/cjs/hooks/ai-sdk.js
CHANGED
|
@@ -45,16 +45,15 @@ const agent_context_store_js_1 = require("../lineage/agent-context-store.js");
|
|
|
45
45
|
exports.vercelAiSdkPatchState = {
|
|
46
46
|
isPatched: false,
|
|
47
47
|
originalToolFactory: undefined,
|
|
48
|
-
patchedModule: undefined
|
|
48
|
+
patchedModule: undefined,
|
|
49
|
+
mutatedOriginal: false
|
|
49
50
|
};
|
|
50
51
|
function captureOriginalToolFactory(module) {
|
|
51
52
|
const candidate = module.tool;
|
|
52
53
|
if (typeof candidate !== "function") {
|
|
53
54
|
return undefined;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
exports.vercelAiSdkPatchState.originalToolFactory = candidate;
|
|
57
|
-
}
|
|
56
|
+
exports.vercelAiSdkPatchState.originalToolFactory ??= candidate;
|
|
58
57
|
return exports.vercelAiSdkPatchState.originalToolFactory;
|
|
59
58
|
}
|
|
60
59
|
function recordToolResultNonBlocking(gatewayClient, runId, output) {
|
|
@@ -76,7 +75,7 @@ function createWrappedExecute(originalExecute, description, gatewayClient, optio
|
|
|
76
75
|
decision = await gatewayClient.check({
|
|
77
76
|
action: "tool_call",
|
|
78
77
|
toolName: description,
|
|
79
|
-
args
|
|
78
|
+
args,
|
|
80
79
|
runId
|
|
81
80
|
});
|
|
82
81
|
}
|
|
@@ -114,6 +113,35 @@ function createPatchedToolFactory(originalToolFactory, gatewayClient, options) {
|
|
|
114
113
|
};
|
|
115
114
|
};
|
|
116
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Install `governed` as the module's `tool` factory without ever assigning to a
|
|
118
|
+
* frozen ESM namespace.
|
|
119
|
+
*
|
|
120
|
+
* A real `ai` package loaded via `import()` is an ES module: its namespace is an
|
|
121
|
+
* exotic object whose named exports are non-writable, so `module.tool = …` throws
|
|
122
|
+
* `Cannot assign to read only property 'tool'` (AAASM-3532). We therefore attempt
|
|
123
|
+
* the in-place assignment only as a fast path for writable plain objects (the
|
|
124
|
+
* shape used by the unit suite's `loadModule` fakes) and fall back to a mutable
|
|
125
|
+
* **shim copy** for the frozen-namespace case — the same `{ tool: aiModule.tool }`
|
|
126
|
+
* shim the AAASM-3525 integration driver proved works. The returned module is what
|
|
127
|
+
* downstream consumers read the governed factory from (`patchedModule.tool`).
|
|
128
|
+
*/
|
|
129
|
+
function applyGovernedToolFactory(module, governed) {
|
|
130
|
+
if (Object.isExtensible(module)) {
|
|
131
|
+
try {
|
|
132
|
+
module.tool = governed;
|
|
133
|
+
return { patchedModule: module, mutatedOriginal: true };
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Some non-extensible-but-reported-extensible exotic objects still reject
|
|
137
|
+
// the write; fall through to the shim copy below.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
patchedModule: { ...module, tool: governed },
|
|
142
|
+
mutatedOriginal: false
|
|
143
|
+
};
|
|
144
|
+
}
|
|
117
145
|
async function loadVercelAiSdkModule() {
|
|
118
146
|
try {
|
|
119
147
|
const moduleName = "ai";
|
|
@@ -137,13 +165,15 @@ async function patchVercelAiSdk(options) {
|
|
|
137
165
|
if (!originalToolFactory) {
|
|
138
166
|
return false;
|
|
139
167
|
}
|
|
140
|
-
|
|
168
|
+
const governed = createPatchedToolFactory(originalToolFactory, options.gatewayClient, {
|
|
141
169
|
approvalTimeoutMs: options.approvalTimeoutMs ?? 30_000,
|
|
142
170
|
fallbackRunId: options.fallbackRunId ?? "vercel-ai-sdk",
|
|
143
|
-
...(options.agentId
|
|
171
|
+
...(options.agentId === undefined ? {} : { agentId: options.agentId })
|
|
144
172
|
});
|
|
173
|
+
const { patchedModule, mutatedOriginal } = applyGovernedToolFactory(module, governed);
|
|
145
174
|
exports.vercelAiSdkPatchState.isPatched = true;
|
|
146
|
-
exports.vercelAiSdkPatchState.patchedModule =
|
|
175
|
+
exports.vercelAiSdkPatchState.patchedModule = patchedModule;
|
|
176
|
+
exports.vercelAiSdkPatchState.mutatedOriginal = mutatedOriginal;
|
|
147
177
|
return true;
|
|
148
178
|
}
|
|
149
179
|
function unpatchVercelAiSdk() {
|
|
@@ -156,9 +186,15 @@ function unpatchVercelAiSdk() {
|
|
|
156
186
|
if (!exports.vercelAiSdkPatchState.originalToolFactory) {
|
|
157
187
|
return false;
|
|
158
188
|
}
|
|
159
|
-
|
|
160
|
-
|
|
189
|
+
// Only restore when we mutated a writable module in place. For the frozen-ESM
|
|
190
|
+
// shim path the original `ai` namespace was never touched, so there is nothing
|
|
191
|
+
// to write back — and attempting it would re-throw the AAASM-3532 crash.
|
|
192
|
+
if (exports.vercelAiSdkPatchState.mutatedOriginal) {
|
|
193
|
+
exports.vercelAiSdkPatchState.patchedModule.tool =
|
|
194
|
+
exports.vercelAiSdkPatchState.originalToolFactory;
|
|
195
|
+
}
|
|
161
196
|
exports.vercelAiSdkPatchState.isPatched = false;
|
|
162
197
|
exports.vercelAiSdkPatchState.patchedModule = undefined;
|
|
198
|
+
exports.vercelAiSdkPatchState.mutatedOriginal = false;
|
|
163
199
|
return true;
|
|
164
200
|
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.patchLangChain = patchLangChain;
|
|
4
|
+
/**
|
|
5
|
+
* Intentional no-op stub: native-transport LangChain patching is not
|
|
6
|
+
* implemented. LangChain enforcement is performed in the SDK's callback layer
|
|
7
|
+
* (post-execution redaction) and wrapper layer (pre-execution deny) wired by
|
|
8
|
+
* `initAssembly`, not through this native hook — so there is nothing to patch
|
|
9
|
+
* here yet. Returns `false` (nothing patched) for every mode.
|
|
10
|
+
*
|
|
11
|
+
* The `client` parameter is retained to keep the adapter-registry hook
|
|
12
|
+
* signature (and the public `patchLangChain` export) uniform with the other
|
|
13
|
+
* `patch*` hooks; it is deliberately unused until native patching lands.
|
|
14
|
+
*/
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- stub param kept for signature stability; see TSDoc above
|
|
4
16
|
async function patchLangChain(client) {
|
|
5
|
-
if (client.mode === "grpc-sidecar" || client.mode === "napi-inprocess") {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
17
|
return false;
|
|
9
18
|
}
|
package/dist/cjs/hooks/mastra.js
CHANGED
|
@@ -75,15 +75,18 @@ async function patchMastra(options) {
|
|
|
75
75
|
exports.mastraPatchState.originalGenerate = originalGenerate;
|
|
76
76
|
exports.mastraPatchState.patchedAgentClass = module.Agent;
|
|
77
77
|
module.Agent.prototype.generate = function patchedGenerate(...args) {
|
|
78
|
-
|
|
78
|
+
// The try guards only the *synchronous* setup (entering the async-context
|
|
79
|
+
// store and invoking the original); the returned promise is handed to the
|
|
80
|
+
// caller, which awaits it, so its rejection is the caller's to handle.
|
|
81
|
+
// Returning it directly (rather than via a local inside the try) avoids an
|
|
82
|
+
// unhandled-rejection footgun without changing timing or call count.
|
|
79
83
|
try {
|
|
80
|
-
|
|
84
|
+
return (0, agent_context_store_js_1.runWithAgentId)(agentId, () => originalGenerate.apply(this, args));
|
|
81
85
|
}
|
|
82
86
|
catch (e) {
|
|
83
87
|
console.warn("[assembly] Mastra lineage patch error on generate; falling back:", e);
|
|
84
88
|
return originalGenerate.apply(this, args);
|
|
85
89
|
}
|
|
86
|
-
return result;
|
|
87
90
|
};
|
|
88
91
|
// Wrap Workflow.prototype.execute if present
|
|
89
92
|
if (module.Workflow?.prototype?.execute) {
|
|
@@ -91,15 +94,16 @@ async function patchMastra(options) {
|
|
|
91
94
|
exports.mastraPatchState.originalExecute = originalExecute;
|
|
92
95
|
exports.mastraPatchState.patchedWorkflowClass = module.Workflow;
|
|
93
96
|
module.Workflow.prototype.execute = function patchedExecute(...args) {
|
|
94
|
-
|
|
97
|
+
// See patchedGenerate above: the try guards only the synchronous setup;
|
|
98
|
+
// the returned promise is awaited by the caller, so returning it directly
|
|
99
|
+
// (not via a local) handles its rejection without altering behaviour.
|
|
95
100
|
try {
|
|
96
|
-
|
|
101
|
+
return (0, agent_context_store_js_1.runWithAgentId)(agentId, () => originalExecute.apply(this, args));
|
|
97
102
|
}
|
|
98
103
|
catch (e) {
|
|
99
104
|
console.warn("[assembly] Mastra lineage patch error on execute; falling back:", e);
|
|
100
105
|
return originalExecute.apply(this, args);
|
|
101
106
|
}
|
|
102
|
-
return result;
|
|
103
107
|
};
|
|
104
108
|
}
|
|
105
109
|
exports.mastraPatchState.isPatched = true;
|
|
@@ -54,9 +54,7 @@ function captureOriginalRunTool(agentClass) {
|
|
|
54
54
|
if (!candidate) {
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
exports.openAIAgentsPatchState.originalRunTool = candidate;
|
|
59
|
-
}
|
|
57
|
+
exports.openAIAgentsPatchState.originalRunTool ??= candidate;
|
|
60
58
|
return exports.openAIAgentsPatchState.originalRunTool;
|
|
61
59
|
}
|
|
62
60
|
function parseToolCallArguments(toolCall) {
|
package/dist/cjs/index.js
CHANGED
|
@@ -14,11 +14,19 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.PolicyViolationError = exports.createNoopGatewayClient = exports.findAasmBinary = exports.INSTALL_HINT = exports.runWithAgentId = exports.currentAgentId = exports.encodeCallStackNode = exports.encodeAuditEvent = exports.decodeCallStackNode = exports.decodeAuditEvent = exports.ENFORCEMENT_MODES = exports.withAssembly = exports.initAssembly = void 0;
|
|
17
|
+
exports.PolicyViolationError = exports.createNoopGatewayClient = exports.findAasmBinary = exports.INSTALL_HINT = exports.runWithAgentId = exports.currentAgentId = exports.encodeCallStackNode = exports.encodeAuditEvent = exports.decodeCallStackNode = exports.decodeAuditEvent = exports.ENFORCEMENT_MODES = exports.OpTerminatedError = exports.OpControlSubscriber = exports.withAssembly = exports.initAssembly = void 0;
|
|
18
18
|
var init_assembly_js_1 = require("./core/init-assembly.js");
|
|
19
19
|
Object.defineProperty(exports, "initAssembly", { enumerable: true, get: function () { return init_assembly_js_1.initAssembly; } });
|
|
20
20
|
var index_js_1 = require("./wrappers/index.js");
|
|
21
21
|
Object.defineProperty(exports, "withAssembly", { enumerable: true, get: function () { return index_js_1.withAssembly; } });
|
|
22
|
+
// Live op-control consumer (AAASM-3491). Subscribes to the gateway's
|
|
23
|
+
// OpControlStream and exposes per-op cooperative-pause / fast-fail-terminate;
|
|
24
|
+
// pass the subscriber as `withAssembly(..., { opControl })` so an operator
|
|
25
|
+
// terminate/pause reaches a running tool through the SDK tool path.
|
|
26
|
+
var op_control_js_1 = require("./op-control.js");
|
|
27
|
+
Object.defineProperty(exports, "OpControlSubscriber", { enumerable: true, get: function () { return op_control_js_1.OpControlSubscriber; } });
|
|
28
|
+
var op_terminated_error_js_1 = require("./errors/op-terminated-error.js");
|
|
29
|
+
Object.defineProperty(exports, "OpTerminatedError", { enumerable: true, get: function () { return op_terminated_error_js_1.OpTerminatedError; } });
|
|
22
30
|
var index_js_2 = require("./types/index.js");
|
|
23
31
|
Object.defineProperty(exports, "ENFORCEMENT_MODES", { enumerable: true, get: function () { return index_js_2.ENFORCEMENT_MODES; } });
|
|
24
32
|
var index_js_3 = require("./audit/index.js");
|
|
@@ -3,14 +3,36 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.NativeDisconnectError = exports.NativeQueryPolicyError = exports.NativeSendEventError = exports.NativeConnectError = void 0;
|
|
6
|
+
exports.NativeDisconnectError = exports.NativeRegisterError = exports.NativeQueryPolicyError = exports.NativeSendEventError = exports.NativeConnectError = void 0;
|
|
7
|
+
exports.resolveSdkVersion = resolveSdkVersion;
|
|
7
8
|
exports.createNativeClient = createNativeClient;
|
|
8
9
|
const node_module_1 = require("node:module");
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the installed `@agent-assembly/sdk` package version, or `undefined`.
|
|
13
|
+
*
|
|
14
|
+
* Forwarded into the native `connect` so the user-facing npm package version —
|
|
15
|
+
* not the shared `aa-sdk-client` crate version — is what gets signed into the
|
|
16
|
+
* runtime handshake, giving accurate downgrade detection (AAASM-3683).
|
|
17
|
+
* `undefined` lets the native shim fall back to the crate version (no
|
|
18
|
+
* regression vs AAASM-3666). Uses `createRequire(<cwd>/package.json)`, the same
|
|
19
|
+
* ESM/CJS-safe pattern as the native-binding and runtime-binary resolvers.
|
|
20
|
+
*/
|
|
21
|
+
function resolveSdkVersion() {
|
|
22
|
+
try {
|
|
23
|
+
const requireFromHere = (0, node_module_1.createRequire)(node_path_1.default.resolve(process.cwd(), "package.json"));
|
|
24
|
+
const pkg = requireFromHere("@agent-assembly/sdk/package.json");
|
|
25
|
+
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
10
31
|
const NATIVE_BINDING_SINGLETON_KEY = Symbol.for("@agent-assembly/sdk/native-binding");
|
|
11
32
|
const ERROR_CONNECT = "AA_ERR_CONNECT";
|
|
12
33
|
const ERROR_SEND_EVENT = "AA_ERR_SEND_EVENT";
|
|
13
34
|
const ERROR_QUERY_POLICY = "AA_ERR_QUERY_POLICY";
|
|
35
|
+
const ERROR_REGISTER = "AA_ERR_REGISTER";
|
|
14
36
|
const ERROR_DISCONNECT = "AA_ERR_DISCONNECT";
|
|
15
37
|
class NativeConnectError extends Error {
|
|
16
38
|
code = ERROR_CONNECT;
|
|
@@ -24,10 +46,33 @@ class NativeQueryPolicyError extends Error {
|
|
|
24
46
|
code = ERROR_QUERY_POLICY;
|
|
25
47
|
}
|
|
26
48
|
exports.NativeQueryPolicyError = NativeQueryPolicyError;
|
|
49
|
+
class NativeRegisterError extends Error {
|
|
50
|
+
code = ERROR_REGISTER;
|
|
51
|
+
}
|
|
52
|
+
exports.NativeRegisterError = NativeRegisterError;
|
|
27
53
|
class NativeDisconnectError extends Error {
|
|
28
54
|
code = ERROR_DISCONNECT;
|
|
29
55
|
}
|
|
30
56
|
exports.NativeDisconnectError = NativeDisconnectError;
|
|
57
|
+
/**
|
|
58
|
+
* Translate the native `{decision, reason}` verdict into the SDK's
|
|
59
|
+
* `PolicyResult`. Only `"deny"` blocks; `"pending"` routes to the approval
|
|
60
|
+
* path; `"allow"` / `"redact"` / any unrecognized value proceed. This mirrors
|
|
61
|
+
* the shared enforcement contract across the Python / Go / Node SDKs.
|
|
62
|
+
*
|
|
63
|
+
* The native primitive already fails open (returns `"allow"`) when the runtime
|
|
64
|
+
* is unreachable or too slow, so a missing or degraded runtime never blocks.
|
|
65
|
+
*/
|
|
66
|
+
function mapDecisionToPolicyResult(verdict) {
|
|
67
|
+
switch (verdict.decision) {
|
|
68
|
+
case "deny":
|
|
69
|
+
return { denied: true, pending: false, reason: verdict.reason };
|
|
70
|
+
case "pending":
|
|
71
|
+
return { denied: false, pending: true, reason: verdict.reason };
|
|
72
|
+
default:
|
|
73
|
+
return { denied: false, pending: false };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
31
76
|
function mapNativeError(error) {
|
|
32
77
|
if (!(error instanceof Error)) {
|
|
33
78
|
return new Error(String(error));
|
|
@@ -43,6 +88,9 @@ function mapNativeError(error) {
|
|
|
43
88
|
if (code === ERROR_QUERY_POLICY) {
|
|
44
89
|
return new NativeQueryPolicyError(detail);
|
|
45
90
|
}
|
|
91
|
+
if (code === ERROR_REGISTER) {
|
|
92
|
+
return new NativeRegisterError(detail);
|
|
93
|
+
}
|
|
46
94
|
if (code === ERROR_DISCONNECT) {
|
|
47
95
|
return new NativeDisconnectError(detail);
|
|
48
96
|
}
|
|
@@ -51,9 +99,7 @@ function mapNativeError(error) {
|
|
|
51
99
|
function loadNativeBinding() {
|
|
52
100
|
const shouldUseCache = process.env.VITEST !== "true";
|
|
53
101
|
const globalObject = globalThis;
|
|
54
|
-
const cachedBinding = shouldUseCache
|
|
55
|
-
? globalObject[NATIVE_BINDING_SINGLETON_KEY]
|
|
56
|
-
: undefined;
|
|
102
|
+
const cachedBinding = shouldUseCache ? globalObject[NATIVE_BINDING_SINGLETON_KEY] : undefined;
|
|
57
103
|
if (cachedBinding) {
|
|
58
104
|
return cachedBinding;
|
|
59
105
|
}
|
|
@@ -85,7 +131,11 @@ function createNativeClient(options) {
|
|
|
85
131
|
mode,
|
|
86
132
|
close: async () => undefined,
|
|
87
133
|
sendEvent: () => undefined,
|
|
88
|
-
queryPolicy: async () => ({ denied: false, pending: false })
|
|
134
|
+
queryPolicy: async () => ({ denied: false, pending: false }),
|
|
135
|
+
// No native session to register against off the in-process path; the
|
|
136
|
+
// gRPC sidecar registers the agent in its own process. Resolve neutrally
|
|
137
|
+
// so init never blocks on a transport that does not own a handle.
|
|
138
|
+
register: async () => ""
|
|
89
139
|
};
|
|
90
140
|
}
|
|
91
141
|
const binding = loadNativeBinding();
|
|
@@ -93,20 +143,21 @@ function createNativeClient(options) {
|
|
|
93
143
|
let handlePromise;
|
|
94
144
|
let activeHandle;
|
|
95
145
|
let pendingSendError;
|
|
146
|
+
const sdkVersion = resolveSdkVersion();
|
|
96
147
|
const getHandle = async () => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
148
|
+
handlePromise ??= binding
|
|
149
|
+
// Forward the npm package version so it is signed into the handshake
|
|
150
|
+
// (AAASM-3683); agent id is wired at register time, not connect.
|
|
151
|
+
.connect(socketPath, undefined, sdkVersion)
|
|
152
|
+
.then((handle) => {
|
|
153
|
+
activeHandle = handle;
|
|
154
|
+
return handle;
|
|
155
|
+
})
|
|
156
|
+
.catch((error) => {
|
|
157
|
+
handlePromise = undefined;
|
|
158
|
+
activeHandle = undefined;
|
|
159
|
+
throw mapNativeError(error);
|
|
160
|
+
});
|
|
110
161
|
return handlePromise;
|
|
111
162
|
};
|
|
112
163
|
return {
|
|
@@ -145,18 +196,36 @@ function createNativeClient(options) {
|
|
|
145
196
|
pendingSendError = mapNativeError(error);
|
|
146
197
|
});
|
|
147
198
|
},
|
|
148
|
-
queryPolicy: async () => {
|
|
199
|
+
queryPolicy: async (action) => {
|
|
149
200
|
if (pendingSendError) {
|
|
150
201
|
const error = pendingSendError;
|
|
151
202
|
pendingSendError = undefined;
|
|
152
203
|
throw error;
|
|
153
204
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
205
|
+
// Connect (surfacing any connect error as a genuine local fault), then
|
|
206
|
+
// ask the runtime for an authoritative verdict via the native primitive.
|
|
207
|
+
// The native `queryPolicy` is async — it offloads its blocking wait to a
|
|
208
|
+
// worker thread, so awaiting it never blocks the Node event loop — and it
|
|
209
|
+
// already fails open (returns `"allow"`) when the runtime is unreachable
|
|
210
|
+
// or too slow, so a missing or degraded runtime never blocks the agent.
|
|
211
|
+
const handle = await getHandle();
|
|
212
|
+
const verdict = await binding.queryPolicy(handle, action);
|
|
213
|
+
return mapDecisionToPolicyResult(verdict);
|
|
214
|
+
},
|
|
215
|
+
register: async (options) => {
|
|
216
|
+
// Register on the same session the queryPolicy path uses, so the token
|
|
217
|
+
// the gateway issues is stored on this handle and attached to every
|
|
218
|
+
// subsequent query. This is the only direct SDK→gateway gRPC call
|
|
219
|
+
// (ADR 0004); CheckAction still flows through aa-runtime.
|
|
220
|
+
if (binding.register === undefined) {
|
|
221
|
+
// A binding without `register` predates AAASM-3400; the agent simply
|
|
222
|
+
// runs unregistered rather than failing init.
|
|
223
|
+
return "";
|
|
224
|
+
}
|
|
225
|
+
const handle = await getHandle();
|
|
226
|
+
return binding.register(handle, options).catch((error) => {
|
|
227
|
+
throw mapNativeError(error);
|
|
228
|
+
});
|
|
160
229
|
}
|
|
161
230
|
};
|
|
162
231
|
}
|
package/dist/cjs/op-control.js
CHANGED
|
@@ -20,39 +20,178 @@
|
|
|
20
20
|
* - Auto-wiring into the existing `GatewayClient` / adapter hooks
|
|
21
21
|
* (separate sub-task when the adapter surface is stable).
|
|
22
22
|
*/
|
|
23
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
26
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
27
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
28
|
+
}
|
|
29
|
+
Object.defineProperty(o, k2, desc);
|
|
30
|
+
}) : (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
o[k2] = m[k];
|
|
33
|
+
}));
|
|
34
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
35
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
36
|
+
}) : function(o, v) {
|
|
37
|
+
o["default"] = v;
|
|
38
|
+
});
|
|
39
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
40
|
+
var ownKeys = function(o) {
|
|
41
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
42
|
+
var ar = [];
|
|
43
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
44
|
+
return ar;
|
|
45
|
+
};
|
|
46
|
+
return ownKeys(o);
|
|
47
|
+
};
|
|
48
|
+
return function (mod) {
|
|
49
|
+
if (mod && mod.__esModule) return mod;
|
|
50
|
+
var result = {};
|
|
51
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
52
|
+
__setModuleDefault(result, mod);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
23
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
57
|
exports.OpControlSubscriber = void 0;
|
|
25
|
-
|
|
58
|
+
exports.gatewayHostOf = gatewayHostOf;
|
|
59
|
+
exports.resolveOpControlCredentials = resolveOpControlCredentials;
|
|
26
60
|
const op_terminated_error_js_1 = require("./errors/op-terminated-error.js");
|
|
27
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Numeric `OpControlSignal` values, inlined to keep this module grpc-free at load.
|
|
63
|
+
*
|
|
64
|
+
* `OpControlSignal` lives in `./proto/generated/policy.js`, which imports
|
|
65
|
+
* `@grpc/grpc-js` at module scope; importing the enum as a *value* would defeat
|
|
66
|
+
* the lazy-load. These are the stable protobuf wire numbers (UNSPECIFIED=0,
|
|
67
|
+
* PAUSE=1, RESUME=2, TERMINATE=3) — `msg.signal` is compared against them
|
|
68
|
+
* numerically in {@link OpControlSubscriber.dispatch}. The `OpControlSignal`
|
|
69
|
+
* type is still imported (type-only) for signatures.
|
|
70
|
+
*/
|
|
71
|
+
const SIGNAL_PAUSE = 1;
|
|
72
|
+
const SIGNAL_RESUME = 2;
|
|
73
|
+
const SIGNAL_TERMINATE = 3;
|
|
74
|
+
/**
|
|
75
|
+
* Hosts treated as loopback for the secure-by-default transport decision.
|
|
76
|
+
* A loopback gateway is the local dev-mode CP, where plaintext gRPC is the
|
|
77
|
+
* documented default; anything else is presumed remote and must be encrypted.
|
|
78
|
+
*/
|
|
79
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
80
|
+
/**
|
|
81
|
+
* Extract the bare host from a gRPC target (`host:port`, a bare host, or a
|
|
82
|
+
* URL-style `scheme://host:port`). Returns the lowercased host with any
|
|
83
|
+
* surrounding IPv6 brackets preserved so it can be matched against
|
|
84
|
+
* {@link LOOPBACK_HOSTS}.
|
|
85
|
+
*/
|
|
86
|
+
function gatewayHostOf(gatewayUrl) {
|
|
87
|
+
let target = gatewayUrl.trim();
|
|
88
|
+
const schemeIdx = target.indexOf("://");
|
|
89
|
+
if (schemeIdx !== -1)
|
|
90
|
+
target = target.slice(schemeIdx + 3);
|
|
91
|
+
// Drop a path/query suffix if a URL form was passed.
|
|
92
|
+
const slashIdx = target.indexOf("/");
|
|
93
|
+
if (slashIdx !== -1)
|
|
94
|
+
target = target.slice(0, slashIdx);
|
|
95
|
+
if (target.startsWith("[")) {
|
|
96
|
+
// Bracketed IPv6: keep the bracketed form, strip only the trailing :port.
|
|
97
|
+
const close = target.indexOf("]");
|
|
98
|
+
return close === -1 ? target.toLowerCase() : target.slice(0, close + 1).toLowerCase();
|
|
99
|
+
}
|
|
100
|
+
const colonIdx = target.indexOf(":");
|
|
101
|
+
if (colonIdx !== -1)
|
|
102
|
+
target = target.slice(0, colonIdx);
|
|
103
|
+
return target.toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
function isLoopbackTarget(gatewayUrl) {
|
|
106
|
+
return LOOPBACK_HOSTS.has(gatewayHostOf(gatewayUrl));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Pick channel credentials for the op-control stream, secure by default.
|
|
110
|
+
*
|
|
111
|
+
* Precedence: an explicit `credentials` override wins; otherwise a loopback
|
|
112
|
+
* target gets plaintext (local dev gateway), a remote target gets TLS, and a
|
|
113
|
+
* remote target is only allowed plaintext when the caller sets `allowInsecure`.
|
|
114
|
+
*
|
|
115
|
+
* `grpcCredentials` is injected (rather than imported at module scope) so this
|
|
116
|
+
* module does not eagerly load `@grpc/grpc-js` — see the module header. The
|
|
117
|
+
* real-connect path passes the lazily-imported `credentials` namespace.
|
|
118
|
+
*
|
|
119
|
+
* @throws never — returns the chosen {@link ChannelCredentials}.
|
|
120
|
+
*/
|
|
121
|
+
function resolveOpControlCredentials(gatewayUrl, opts, grpcCredentials) {
|
|
122
|
+
if (opts.credentials)
|
|
123
|
+
return opts.credentials;
|
|
124
|
+
if (isLoopbackTarget(gatewayUrl))
|
|
125
|
+
return grpcCredentials.createInsecure();
|
|
126
|
+
if (opts.allowInsecure)
|
|
127
|
+
return grpcCredentials.createInsecure();
|
|
128
|
+
return grpcCredentials.createSsl();
|
|
129
|
+
}
|
|
28
130
|
class OpControlSubscriber {
|
|
29
|
-
|
|
131
|
+
/**
|
|
132
|
+
* `null` until the channel is opened. On the test-seam (`clientFactory`) path
|
|
133
|
+
* it is set synchronously in {@link connect}; on the real-connect path it is
|
|
134
|
+
* set asynchronously once `@grpc/grpc-js` + `PolicyServiceClient` have been
|
|
135
|
+
* lazily imported (see {@link openRealChannel}).
|
|
136
|
+
*/
|
|
137
|
+
client = null;
|
|
30
138
|
agent;
|
|
31
139
|
ops = new Map();
|
|
32
140
|
call = null;
|
|
33
141
|
alive = true;
|
|
34
|
-
|
|
35
|
-
|
|
142
|
+
/** Set once {@link close} is called before the async channel finishes opening. */
|
|
143
|
+
closed = false;
|
|
144
|
+
constructor(agent) {
|
|
36
145
|
this.agent = agent;
|
|
37
146
|
}
|
|
38
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Open the gRPC channel + subscription stream and start the reader.
|
|
149
|
+
*
|
|
150
|
+
* Returns synchronously. On the real-connect path the channel is opened
|
|
151
|
+
* asynchronously — `@grpc/grpc-js` and `PolicyServiceClient` are loaded lazily
|
|
152
|
+
* (`await import`) so that importing this module never eagerly pulls grpc (see
|
|
153
|
+
* the module header). The test seam (`clientFactory`) opens synchronously and
|
|
154
|
+
* never touches grpc.
|
|
155
|
+
*/
|
|
39
156
|
static connect(gatewayUrl, opts) {
|
|
40
157
|
const agent = {
|
|
41
158
|
orgId: opts.orgId,
|
|
42
159
|
teamId: opts.teamId,
|
|
43
|
-
agentId: opts.agentId
|
|
160
|
+
agentId: opts.agentId
|
|
44
161
|
};
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
162
|
+
const subscriber = new OpControlSubscriber(agent);
|
|
163
|
+
if (opts.clientFactory) {
|
|
164
|
+
subscriber.client = opts.clientFactory();
|
|
165
|
+
subscriber.start();
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Real channel: defer grpc loading to the dynamic-import path. Errors
|
|
169
|
+
// surface as a dead stream so callers see `streamAlive() === false`.
|
|
170
|
+
void subscriber.openRealChannel(gatewayUrl, opts).catch(() => {
|
|
171
|
+
subscriber.markStreamDead();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
50
174
|
return subscriber;
|
|
51
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Lazily import grpc + the policy client, build the real client, and start the
|
|
178
|
+
* reader. Kept off the module's import graph so `import '@agent-assembly/sdk'`
|
|
179
|
+
* stays grpc-free until a subscriber actually opens a live channel.
|
|
180
|
+
*/
|
|
181
|
+
async openRealChannel(gatewayUrl, opts) {
|
|
182
|
+
const { credentials } = await Promise.resolve().then(() => __importStar(require("@grpc/grpc-js")));
|
|
183
|
+
const { PolicyServiceClient } = await Promise.resolve().then(() => __importStar(require("./proto/generated/policy.js")));
|
|
184
|
+
if (this.closed)
|
|
185
|
+
return; // close() raced ahead of the async open.
|
|
186
|
+
this.client = new PolicyServiceClient(gatewayUrl, resolveOpControlCredentials(gatewayUrl, opts, credentials));
|
|
187
|
+
this.start();
|
|
188
|
+
}
|
|
52
189
|
/** Open the stream and wire reader handlers. Public so tests can call
|
|
53
190
|
* directly after constructing with a hand-rolled client.
|
|
54
191
|
*/
|
|
55
192
|
start() {
|
|
193
|
+
if (!this.client)
|
|
194
|
+
return;
|
|
56
195
|
this.call = this.client.opControlStream({ agentId: this.agent });
|
|
57
196
|
this.call.on("data", (msg) => this.dispatch(msg));
|
|
58
197
|
this.call.on("error", () => this.markStreamDead());
|
|
@@ -61,14 +200,14 @@ class OpControlSubscriber {
|
|
|
61
200
|
dispatch(msg) {
|
|
62
201
|
const state = this.slot(msg.opId);
|
|
63
202
|
switch (msg.signal) {
|
|
64
|
-
case
|
|
203
|
+
case SIGNAL_PAUSE:
|
|
65
204
|
state.paused = true;
|
|
66
205
|
break;
|
|
67
|
-
case
|
|
206
|
+
case SIGNAL_RESUME:
|
|
68
207
|
state.paused = false;
|
|
69
208
|
this.flushResolvers(state);
|
|
70
209
|
break;
|
|
71
|
-
case
|
|
210
|
+
case SIGNAL_TERMINATE:
|
|
72
211
|
state.terminated = true;
|
|
73
212
|
this.flushResolvers(state);
|
|
74
213
|
break;
|
|
@@ -140,10 +279,13 @@ class OpControlSubscriber {
|
|
|
140
279
|
streamAlive() {
|
|
141
280
|
return this.alive;
|
|
142
281
|
}
|
|
143
|
-
/** Cancel the stream and clean up.
|
|
282
|
+
/** Cancel the stream and clean up. Safe to call before the async real-channel
|
|
283
|
+
* open has completed — it flags `closed` so the pending open bails out.
|
|
284
|
+
*/
|
|
144
285
|
close() {
|
|
286
|
+
this.closed = true;
|
|
145
287
|
this.call?.cancel();
|
|
146
|
-
this.client
|
|
288
|
+
this.client?.close?.();
|
|
147
289
|
this.markStreamDead();
|
|
148
290
|
}
|
|
149
291
|
}
|