@odingard/guardian-openai-agents 0.1.0 → 0.1.2
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 +13 -4
- package/dist/index.js +41 -18
- package/dist-dts/gate.d.ts +3 -2
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ the agent.
|
|
|
108
108
|
instead.
|
|
109
109
|
- Guardian error, network failure, timeout or a cancelled call — your
|
|
110
110
|
`execute` never runs, and a late answer from Guardian is ignored. The model
|
|
111
|
-
is told `Guardian could not check refund_order, so it did not run.`, and
|
|
111
|
+
is told `Guardian could not check refund_order, so it did not run. Try again later.`, and
|
|
112
112
|
`onGuardianError` is called. `decisionTimeoutMs` (default 30000) bounds the
|
|
113
113
|
wait for Guardian's decision; the Agents SDK's own `timeoutMs` and run
|
|
114
114
|
cancellation stop the wait too, but only while Guardian is still deciding.
|
|
@@ -137,13 +137,22 @@ even with `onAsk: "result"`.
|
|
|
137
137
|
In-process mode is decision-gated: `execute` runs after `ALLOW` and before
|
|
138
138
|
the capability is redeemed, so a revocation at redemption lands after the
|
|
139
139
|
effect. Use `mode: "remote"` where Guardian itself must hold the effect.
|
|
140
|
+
In remote mode, a call Guardian performed whose receipt status is not
|
|
141
|
+
`sent-verified` (for example the upstream answered with an error) is a
|
|
142
|
+
`GuardianExecutionFailed`, and the model is told it did not complete; it is
|
|
143
|
+
never reported as an `ALLOW` result.
|
|
140
144
|
`modes` sets the mode per tool name. The Agents SDK renames tools: every
|
|
141
145
|
character that is not a letter or a digit becomes `_` (`refund-order`
|
|
142
146
|
becomes `refund_order`), and Guardian sees the new name. A `modes` entry
|
|
143
147
|
under the name you wrote is applied to the new name; two entries for the
|
|
144
|
-
same tool that disagree are refused when the tool is built. `installRecordTransport
|
|
145
|
-
adapter propose the record-only entry
|
|
146
|
-
grant that allows it; production usually
|
|
148
|
+
same tool that disagree are refused when the tool is built. `installRecordTransport`
|
|
149
|
+
is off by default. `true` makes the adapter propose the record-only entry
|
|
150
|
+
itself on first use (this needs a grant that allows it; production usually
|
|
151
|
+
does not), and it only works for a tool that has no entry yet: an "already
|
|
152
|
+
registered" answer is refused (`RECORD_TRANSPORT_UNVERIFIED`) and nothing
|
|
153
|
+
runs, including after a restart for an entry the adapter created itself,
|
|
154
|
+
because which entries it registered is kept in memory only. Once the entry
|
|
155
|
+
exists, use it with the option off.
|
|
147
156
|
|
|
148
157
|
Setting the Agents SDK's own `needsApproval` on a tool adds a local approval
|
|
149
158
|
step before Guardian is asked. It never replaces Guardian's decision.
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var CallGate = class {
|
|
|
35
35
|
#signal;
|
|
36
36
|
#onAbort;
|
|
37
37
|
#rejecters = /* @__PURE__ */ new Set();
|
|
38
|
-
#
|
|
38
|
+
#setupIds = /* @__PURE__ */ new Set();
|
|
39
39
|
constructor(args) {
|
|
40
40
|
const { client, toolName, timeoutMs, signal } = args;
|
|
41
41
|
this.#timer = setTimeout(
|
|
@@ -71,29 +71,31 @@ var CallGate = class {
|
|
|
71
71
|
);
|
|
72
72
|
});
|
|
73
73
|
};
|
|
74
|
-
const
|
|
75
|
-
const
|
|
74
|
+
const setupIds = this.#setupIds;
|
|
75
|
+
const noteSetup = (result) => {
|
|
76
76
|
const id = result?.requestId;
|
|
77
|
-
if (typeof id === "string" && id.length > 0)
|
|
77
|
+
if (typeof id === "string" && id.length > 0) setupIds.add(id);
|
|
78
78
|
};
|
|
79
79
|
const request = decide(
|
|
80
80
|
async (i) => {
|
|
81
81
|
const result = await client.request(i);
|
|
82
|
-
if (i.action === "mcp.install")
|
|
82
|
+
if (i.action === "mcp.install" || i.action === "mcp.enable") {
|
|
83
|
+
noteSetup(result);
|
|
84
|
+
}
|
|
83
85
|
return result;
|
|
84
86
|
}
|
|
85
87
|
);
|
|
86
88
|
const claim = decide(async (id) => {
|
|
87
89
|
const result = await client.claim(id);
|
|
88
|
-
if (
|
|
90
|
+
if (setupIds.has(id)) noteSetup(result);
|
|
89
91
|
return result;
|
|
90
92
|
});
|
|
91
|
-
const
|
|
93
|
+
const executeSetup = decide(
|
|
92
94
|
(input) => client.execute(input)
|
|
93
95
|
);
|
|
94
96
|
const execute = (input) => {
|
|
95
|
-
if (
|
|
96
|
-
return
|
|
97
|
+
if (setupIds.has(input.requestId)) {
|
|
98
|
+
return executeSetup(input);
|
|
97
99
|
}
|
|
98
100
|
this.assertOpen();
|
|
99
101
|
this.commit();
|
|
@@ -171,12 +173,15 @@ function denyMessage(err) {
|
|
|
171
173
|
return `Guardian denied ${err.toolName} (${err.reasonCode}). It did not run. Do not retry the same call.`;
|
|
172
174
|
}
|
|
173
175
|
function unavailableMessage(toolName) {
|
|
174
|
-
return `Guardian could not check ${toolName}, so it did not run.`;
|
|
176
|
+
return `Guardian could not check ${toolName}, so it did not run. Try again later.`;
|
|
175
177
|
}
|
|
176
178
|
function executionFailedMessage(err) {
|
|
177
179
|
if (err.localEffectRan) {
|
|
178
180
|
return `${err.toolName} ran, but Guardian could not record it (${err.reasonCode}). Do not call it again; tell the user it ran without a receipt.`;
|
|
179
181
|
}
|
|
182
|
+
if (err.outcomeUnknown) {
|
|
183
|
+
return `Guardian could not confirm that ${err.toolName} completed (${err.reasonCode}, requestId ${err.requestId}). It may have run. Do not call it again; tell the user so they can check.`;
|
|
184
|
+
}
|
|
180
185
|
return `Guardian could not complete ${err.toolName} (${err.reasonCode}). Do not call it again; tell the user so they can check.`;
|
|
181
186
|
}
|
|
182
187
|
function remapModes(modes, writtenName, builtName) {
|
|
@@ -220,13 +225,20 @@ function buildTool(shared, toolOptions) {
|
|
|
220
225
|
});
|
|
221
226
|
const ctx = {
|
|
222
227
|
options: { ...coreOptions, client: gate.client },
|
|
223
|
-
ensured: shared.ensured
|
|
228
|
+
ensured: shared.ensured,
|
|
229
|
+
installed: shared.installed,
|
|
230
|
+
heldSetup: shared.heldSetup
|
|
224
231
|
};
|
|
225
232
|
try {
|
|
226
|
-
const output = await runThroughGuardian(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
233
|
+
const output = await runThroughGuardian(
|
|
234
|
+
ctx,
|
|
235
|
+
toolName,
|
|
236
|
+
input,
|
|
237
|
+
(approved) => {
|
|
238
|
+
gate.commit();
|
|
239
|
+
return userExecute(approved, runContext, details);
|
|
240
|
+
}
|
|
241
|
+
);
|
|
230
242
|
gate.settle();
|
|
231
243
|
return output;
|
|
232
244
|
} catch (err) {
|
|
@@ -242,7 +254,8 @@ function buildTool(shared, toolOptions) {
|
|
|
242
254
|
}
|
|
243
255
|
throw err;
|
|
244
256
|
}
|
|
245
|
-
|
|
257
|
+
const guardianDidNotAnswer = err instanceof GuardianDenied && err.reasonCode === "SERVICE_UNAVAILABLE" && !startedBeforeError;
|
|
258
|
+
if (err instanceof GuardianDenied && !guardianDidNotAnswer) {
|
|
246
259
|
if (onDeny === "result" && !hasOutputSchema) {
|
|
247
260
|
return denyMessage(err);
|
|
248
261
|
}
|
|
@@ -299,12 +312,22 @@ function buildTool(shared, toolOptions) {
|
|
|
299
312
|
}
|
|
300
313
|
function guardianTool(toolOptions, options) {
|
|
301
314
|
return buildTool(
|
|
302
|
-
{
|
|
315
|
+
{
|
|
316
|
+
options,
|
|
317
|
+
ensured: /* @__PURE__ */ new Set(),
|
|
318
|
+
installed: /* @__PURE__ */ new Set(),
|
|
319
|
+
heldSetup: /* @__PURE__ */ new Map()
|
|
320
|
+
},
|
|
303
321
|
toolOptions
|
|
304
322
|
);
|
|
305
323
|
}
|
|
306
324
|
function createGuardianTool(options) {
|
|
307
|
-
const shared = {
|
|
325
|
+
const shared = {
|
|
326
|
+
options,
|
|
327
|
+
ensured: /* @__PURE__ */ new Set(),
|
|
328
|
+
installed: /* @__PURE__ */ new Set(),
|
|
329
|
+
heldSetup: /* @__PURE__ */ new Map()
|
|
330
|
+
};
|
|
308
331
|
return (toolOptions) => buildTool(shared, toolOptions);
|
|
309
332
|
}
|
|
310
333
|
function findApprovalPending(err) {
|
package/dist-dts/gate.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ export declare class GuardianUnavailable extends Error {
|
|
|
21
21
|
*
|
|
22
22
|
* Once the tool starts (or Guardian is asked to execute the tool call) the
|
|
23
23
|
* gate is committed and stops interfering, so a started action is always
|
|
24
|
-
* recorded. Executing a record-transport
|
|
25
|
-
*
|
|
24
|
+
* recorded. Executing a record-transport setup step (mcp.install or
|
|
25
|
+
* mcp.enable) does not commit the gate: the deadline and the abort signal
|
|
26
|
+
* still apply to the tool call after it.
|
|
26
27
|
* Once the call has settled (for example an ASK handed back to the app),
|
|
27
28
|
* the gate opens so `resume()` can claim the approval later.
|
|
28
29
|
*/
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odingard/guardian-openai-agents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenAI Agents SDK adapter — route an agent's tool calls through Guardian for a deterministic ALLOW/ASK/DENY decision before anything executes.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"author": "Odingard Security LLC",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
@@ -26,11 +27,11 @@
|
|
|
26
27
|
"prepublishOnly": "npm run build && npm test"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
|
-
"@odingard/guardian-sdk": "^0.3.
|
|
30
|
+
"@odingard/guardian-sdk": "^0.3.9",
|
|
30
31
|
"@openai/agents": ">=0.21.0 <0.22.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@odingard/guardian-sdk": "^0.3.
|
|
34
|
+
"@odingard/guardian-sdk": "^0.3.9",
|
|
34
35
|
"@openai/agents": "0.21.0",
|
|
35
36
|
"@types/node": "^22.20.5",
|
|
36
37
|
"esbuild": "^0.28.2",
|