shrike-guard 4.1.0 → 4.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 +59 -0
- package/README.md +66 -0
- package/dist/ai/index.d.mts +60 -0
- package/dist/ai/index.d.ts +60 -0
- package/dist/ai/index.js +476 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/index.mjs +472 -0
- package/dist/ai/index.mjs.map +1 -0
- package/dist/anthropic/index.d.mts +1 -1
- package/dist/anthropic/index.d.ts +1 -1
- package/dist/anthropic/index.js +1 -1
- package/dist/anthropic/index.js.map +1 -1
- package/dist/anthropic/index.mjs +1 -1
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/api/index.d.mts +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +1 -1
- package/dist/api/index.mjs.map +1 -1
- package/dist/claude-agent/index.d.mts +91 -0
- package/dist/claude-agent/index.d.ts +91 -0
- package/dist/claude-agent/index.js +492 -0
- package/dist/claude-agent/index.js.map +1 -0
- package/dist/claude-agent/index.mjs +485 -0
- package/dist/claude-agent/index.mjs.map +1 -0
- package/dist/gemini/index.d.mts +1 -1
- package/dist/gemini/index.d.ts +1 -1
- package/dist/gemini/index.js +1 -1
- package/dist/gemini/index.js.map +1 -1
- package/dist/gemini/index.mjs +1 -1
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/govern-R_Lx6i1l.d.ts +202 -0
- package/dist/govern-rW3QdqsQ.d.mts +202 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +40 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +40 -6
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.d.mts +75 -0
- package/dist/langchain/index.d.ts +75 -0
- package/dist/langchain/index.js +484 -0
- package/dist/langchain/index.js.map +1 -0
- package/dist/langchain/index.mjs +479 -0
- package/dist/langchain/index.mjs.map +1 -0
- package/dist/openai/index.d.mts +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.js +1 -1
- package/dist/openai/index.js.map +1 -1
- package/dist/openai/index.mjs +1 -1
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.d.mts +66 -0
- package/dist/openai-agents/index.d.ts +66 -0
- package/dist/openai-agents/index.js +499 -0
- package/dist/openai-agents/index.js.map +1 -0
- package/dist/openai-agents/index.mjs +492 -0
- package/dist/openai-agents/index.mjs.map +1 -0
- package/dist/{scanner-z9r8BJvO.d.mts → scanner-U1SygSM2.d.mts} +21 -2
- package/dist/{scanner-z9r8BJvO.d.ts → scanner-U1SygSM2.d.ts} +21 -2
- package/package.json +62 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.2.0] - 2026-09-17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **An unmapped tool can still be judged by its name.** A tool with no
|
|
7
|
+
mapping has no readable surface, so the content plane has nothing to say
|
|
8
|
+
about it. The authorization plane still does: the operator's declared scope
|
|
9
|
+
judges a tool by NAME, which is the one thing every tool call has.
|
|
10
|
+
`onUnmapped: 'authorize'` sends the tool's name (and nothing else) to the
|
|
11
|
+
backend, so a tool outside the allowlist is refused even though nothing
|
|
12
|
+
read what it was carrying, and an expired or exhausted scope holds it. The
|
|
13
|
+
permit is narrower than a mapped tool's and the record says so: the
|
|
14
|
+
decision's surface is `authorization`, never a scanned surface. A client
|
|
15
|
+
too old to ask fails according to `failMode` rather than assuming.
|
|
16
|
+
`ShrikeGuard.authorizeTool` is the call underneath.
|
|
17
|
+
- **Observe-plane scans say so.** `ShrikeGuard.scan` takes an options argument
|
|
18
|
+
carrying `plane`, and `observePrompt` sends `plane: 'observe'`. A verdict on a prompt nobody is
|
|
19
|
+
gated on is advice: it is recorded and returned, and the model still reads
|
|
20
|
+
the note, but it is no longer filed as an action that was stopped.
|
|
21
|
+
|
|
22
|
+
- **Framework starters over one core.** `govern.ts` is the framework-free
|
|
23
|
+
core of a governed agent: a tool mapping table (`mapTool`, `exempt`),
|
|
24
|
+
`evaluate` (every scan a tool call needs, stopping at the first refusal,
|
|
25
|
+
into one `Outcome`: allow, warn, hold or deny, with the message written
|
|
26
|
+
for the model), `observePrompt` (never blocks), `requestScope` (the
|
|
27
|
+
backend decides; a widening is refused unless an operator grants it), and
|
|
28
|
+
the record (`decisions`, `onDecision`). Each starter is a subpath and a
|
|
29
|
+
thin translation of one framework's hooks:
|
|
30
|
+
- `shrike-guard/claude-agent` (Claude Agent SDK): `PreToolUse` and
|
|
31
|
+
`UserPromptSubmit` hooks, an in-process MCP tool. Ships mappings for the
|
|
32
|
+
SDK's built-in tools. ESM, as the SDK is.
|
|
33
|
+
- `shrike-guard/openai-agents` (OpenAI Agents SDK): a tool input guardrail
|
|
34
|
+
attached to every function tool (a refused call is answered with
|
|
35
|
+
`rejectContent`, so the model reads the reason as the tool's output), a
|
|
36
|
+
non-tripping input guardrail for the observe plane, and `request_scope`
|
|
37
|
+
as a function tool.
|
|
38
|
+
- `shrike-guard/ai` (Vercel AI SDK): `governTools` wraps each tool's
|
|
39
|
+
`execute` so a refused call returns the reason as the tool's result, a
|
|
40
|
+
language-model middleware whose `transformParams` scans a new user
|
|
41
|
+
message and prepends a note, and `request_scope` as an AI SDK tool.
|
|
42
|
+
- `shrike-guard/langchain` (LangChain `createAgent` and LangGraph): an
|
|
43
|
+
agent middleware whose `wrapToolCall` answers a refused call with a
|
|
44
|
+
`ToolMessage`, or `governTools` for a hand-built `ToolNode`, and
|
|
45
|
+
`request_scope` as a LangChain tool.
|
|
46
|
+
- **Tool mappings.** A tool is a surface (`command`, `file`, `file_path`,
|
|
47
|
+
`sql`, `web_search`, `rag_context`, `a2a_message`, `agent_card`, or
|
|
48
|
+
`none`) and the argument that carries its payload. A tool with no mapping
|
|
49
|
+
is refused with a message that says how to map it (`onUnmapped: 'deny'`),
|
|
50
|
+
or allowed and recorded (`'allow'`), or has its arguments scanned as text
|
|
51
|
+
(`'scan'`), or judged by name alone (`'authorize'`, see below). The Claude
|
|
52
|
+
Agent SDK starter defaults to `'authorize'`, and its hook now sees every
|
|
53
|
+
tool the agent can call rather than only the mapped ones, because a tool
|
|
54
|
+
with no reader is exactly the one whose authorization nobody has checked.
|
|
55
|
+
Pass an explicit tool list to `hooksFor` to narrow the matcher.
|
|
56
|
+
- **A conformance suite.** `tests/unit/frameworks.test.ts` drives every
|
|
57
|
+
starter through the same table (allow, warn, block, hold, backend down,
|
|
58
|
+
unmapped, a refused widening); each starter must answer it the same way.
|
|
59
|
+
The frameworks are optional peer dependencies; the base install is
|
|
60
|
+
unchanged.
|
|
61
|
+
|
|
3
62
|
## [4.1.0] - 2026-09-09
|
|
4
63
|
|
|
5
64
|
### Added
|
package/README.md
CHANGED
|
@@ -347,6 +347,72 @@ for (const tool of tools) {
|
|
|
347
347
|
|
|
348
348
|
Screening happens once per tool at registration, not on every call.
|
|
349
349
|
|
|
350
|
+
## Governing an agent: framework starters
|
|
351
|
+
|
|
352
|
+
Shrike governs an agent from inside its tool loop: every tool call is judged
|
|
353
|
+
before it executes, the person's prompt is scanned on the way in and never
|
|
354
|
+
blocked, and the agent gets one tool, `request_scope`, to ask for more than
|
|
355
|
+
it holds. One framework-free core carries that behaviour; each starter is a
|
|
356
|
+
thin translation of one framework's hooks. Every starter is a subpath of this
|
|
357
|
+
package, and each framework is an optional peer dependency (`zod` too).
|
|
358
|
+
|
|
359
|
+
| Framework | Import | What it wires |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| Claude Agent SDK | `shrike-guard/claude-agent` | `PreToolUse` and `UserPromptSubmit` hooks, an in-process MCP tool |
|
|
362
|
+
| OpenAI Agents SDK | `shrike-guard/openai-agents` | a tool input guardrail on every function tool, a non-tripping input guardrail, a function tool |
|
|
363
|
+
| Vercel AI SDK | `shrike-guard/ai` | wrapped `execute` on every tool, a language-model middleware for the observe plane, an AI SDK tool |
|
|
364
|
+
| LangChain `createAgent` / LangGraph | `shrike-guard/langchain` | an agent middleware (`wrapToolCall`, `beforeAgent`) or wrapped tools for a `ToolNode` |
|
|
365
|
+
|
|
366
|
+
Every starter has the same shape. Build it, map your tools to Shrike's
|
|
367
|
+
surfaces, hand it to the framework:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import { Agent } from '@openai/agents';
|
|
371
|
+
import { ScanClient } from 'shrike-guard';
|
|
372
|
+
import { govern } from 'shrike-guard/openai-agents'; // or claude-agent, ai, langchain
|
|
373
|
+
|
|
374
|
+
const guard = new ScanClient({ apiKey: 'shrike-...' });
|
|
375
|
+
const gov = govern(guard, { agentId: 'invoice-agent' });
|
|
376
|
+
await gov.declare(['file_path', 'file_content', 'sql'], 'Reconcile Q3 vendor invoices');
|
|
377
|
+
|
|
378
|
+
gov.mapTool('run_query', { surface: 'sql', arg: 'query' });
|
|
379
|
+
gov.mapTool('save_report', { surface: 'file', pathArg: 'path', contentArg: 'text' });
|
|
380
|
+
gov.exempt('get_time');
|
|
381
|
+
|
|
382
|
+
const agent = gov.governAgent(new Agent({ name: 'invoices', tools: [runQuery, saveReport, getTime] }));
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
A tool mapping says which surface a tool is and which argument carries the
|
|
386
|
+
payload: `command` (with an optional `cwdArg`), `file` (a path and the
|
|
387
|
+
content written to it), `file_path`, `sql`, `web_search`, `rag_context`,
|
|
388
|
+
`a2a_message`, `agent_card`, or `none` for a tool allowed without a scan.
|
|
389
|
+
The Claude Agent SDK starter ships mappings for the SDK's built-in tools;
|
|
390
|
+
the others govern the tools you write, so they need your mappings.
|
|
391
|
+
|
|
392
|
+
A tool with no mapping is refused, with a message that says how to map it,
|
|
393
|
+
until you map it or choose what an unmapped tool gets. `onUnmapped: 'authorize'` asks the
|
|
394
|
+
backend whether the agent may call it at all: the arguments stay put, and the
|
|
395
|
+
tool's NAME goes up, where the operator's declared scope answers. A tool
|
|
396
|
+
outside the allowlist is refused by name even though nothing read what it was
|
|
397
|
+
carrying, and an expired or exhausted scope holds it. That permit is narrower
|
|
398
|
+
than a mapped tool's and the record says so, with the decision's surface
|
|
399
|
+
recorded as `authorization`. `'allow'` records the call and moves on without
|
|
400
|
+
asking; `'scan'` sends the arguments to be read as text.
|
|
401
|
+
|
|
402
|
+
What the model sees is the same everywhere: an allowed call runs; a warned
|
|
403
|
+
call runs with the advisory; a blocked call does not run and the model gets
|
|
404
|
+
the reason; a held call (an action outside the declared scope) does not run
|
|
405
|
+
and the model gets the reason, the recovery, and the instruction to ask with
|
|
406
|
+
`request_scope` and then stop. An action Shrike could not check is refused
|
|
407
|
+
(`failMode: 'closed'`). Every decision lands on `gov.decisions` and on your
|
|
408
|
+
`onDecision` callback, with the axis that objected.
|
|
409
|
+
|
|
410
|
+
Where a framework can route a hold to a person, `onHold: 'ask'` does that
|
|
411
|
+
(the Claude Agent SDK's `canUseTool`). Where it cannot, a hold is answered
|
|
412
|
+
like a refusal and the model is told an operator can grant it on the Shrike
|
|
413
|
+
Agents screen. The `claude-agent` subpath is ESM, because the Claude Agent
|
|
414
|
+
SDK is.
|
|
415
|
+
|
|
350
416
|
## Who is answerable: `content_origin`
|
|
351
417
|
|
|
352
418
|
Every verdict carries `content_origin`, which says where the scanned content
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ToolSet, Tool, LanguageModelMiddleware } from 'ai';
|
|
2
|
+
import { G as Governance$1, a as Guard, b as GovernanceOptions } from '../govern-rW3QdqsQ.mjs';
|
|
3
|
+
export { D as Decision, O as Outcome, T as ToolMapping } from '../govern-rW3QdqsQ.mjs';
|
|
4
|
+
import { S as ScanClient } from '../scanner-U1SygSM2.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Govern an agent built with the Vercel AI SDK (`ai`).
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { generateText, wrapLanguageModel } from 'ai';
|
|
11
|
+
* import { ScanClient } from 'shrike-guard';
|
|
12
|
+
* import { govern } from 'shrike-guard/ai';
|
|
13
|
+
*
|
|
14
|
+
* const guard = new ScanClient({ apiKey: KEY });
|
|
15
|
+
* const gov = govern(guard, { agentId: 'invoice-agent' }).mapTool('runQuery', { surface: 'sql', arg: 'query' });
|
|
16
|
+
* const result = await generateText({
|
|
17
|
+
* model: wrapLanguageModel({ model, middleware: gov.middleware }),
|
|
18
|
+
* tools: gov.governTools({ runQuery, saveReport, request_scope: gov.tool }),
|
|
19
|
+
* prompt,
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* A thin adapter over `shrike-guard`'s framework-free core (`govern.ts`).
|
|
24
|
+
*
|
|
25
|
+
* The act plane wraps each tool's `execute`: Shrike judges the call through
|
|
26
|
+
* the tool's mapping before the inner `execute` runs. A refused or held call
|
|
27
|
+
* never runs; the wrapper returns Shrike's reason and the recovery as the
|
|
28
|
+
* tool's result, so the model reads why and what to do next. Tools with no
|
|
29
|
+
* mapping are refused by default (`onUnmapped: 'deny'`).
|
|
30
|
+
*
|
|
31
|
+
* The observe plane is a language-model middleware whose `transformParams`
|
|
32
|
+
* scans the latest user message on the way in, once per message, and never
|
|
33
|
+
* blocks. A finding is prepended to the prompt as a system message.
|
|
34
|
+
*
|
|
35
|
+
* `request_scope` is available as `gov.tool`; add it to the tool set. A held
|
|
36
|
+
* action is answered like a refusal whatever `onHold` says; use the SDK's
|
|
37
|
+
* `needsApproval` if the run itself should pause.
|
|
38
|
+
*
|
|
39
|
+
* Requires `ai` and `zod`.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** The latest user message's text in a language-model prompt. */
|
|
43
|
+
declare function lastUserText(prompt: unknown): string;
|
|
44
|
+
declare class Governance extends Governance$1 {
|
|
45
|
+
private toolCache?;
|
|
46
|
+
private middlewareCache?;
|
|
47
|
+
private lastObserved?;
|
|
48
|
+
/** Wrap each tool's `execute` so Shrike judges its calls. Returns a new tool set. */
|
|
49
|
+
governTools<T extends ToolSet>(tools: T): T;
|
|
50
|
+
/** `request_scope` as an AI SDK tool; add it to your tool set under that name. */
|
|
51
|
+
get tool(): Tool;
|
|
52
|
+
/** Scan a new user message in a prompt, once. Returns the note to prepend, or empty. */
|
|
53
|
+
observePromptMessages(prompt: unknown): Promise<string>;
|
|
54
|
+
/** The observe-plane middleware for `wrapLanguageModel({ model, middleware })`. */
|
|
55
|
+
get middleware(): LanguageModelMiddleware;
|
|
56
|
+
}
|
|
57
|
+
/** Build the governance for one agent. */
|
|
58
|
+
declare function govern(guard: Guard | ScanClient, options: GovernanceOptions): Governance;
|
|
59
|
+
|
|
60
|
+
export { Governance, GovernanceOptions, govern, lastUserText };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ToolSet, Tool, LanguageModelMiddleware } from 'ai';
|
|
2
|
+
import { G as Governance$1, a as Guard, b as GovernanceOptions } from '../govern-R_Lx6i1l.js';
|
|
3
|
+
export { D as Decision, O as Outcome, T as ToolMapping } from '../govern-R_Lx6i1l.js';
|
|
4
|
+
import { S as ScanClient } from '../scanner-U1SygSM2.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Govern an agent built with the Vercel AI SDK (`ai`).
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { generateText, wrapLanguageModel } from 'ai';
|
|
11
|
+
* import { ScanClient } from 'shrike-guard';
|
|
12
|
+
* import { govern } from 'shrike-guard/ai';
|
|
13
|
+
*
|
|
14
|
+
* const guard = new ScanClient({ apiKey: KEY });
|
|
15
|
+
* const gov = govern(guard, { agentId: 'invoice-agent' }).mapTool('runQuery', { surface: 'sql', arg: 'query' });
|
|
16
|
+
* const result = await generateText({
|
|
17
|
+
* model: wrapLanguageModel({ model, middleware: gov.middleware }),
|
|
18
|
+
* tools: gov.governTools({ runQuery, saveReport, request_scope: gov.tool }),
|
|
19
|
+
* prompt,
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* A thin adapter over `shrike-guard`'s framework-free core (`govern.ts`).
|
|
24
|
+
*
|
|
25
|
+
* The act plane wraps each tool's `execute`: Shrike judges the call through
|
|
26
|
+
* the tool's mapping before the inner `execute` runs. A refused or held call
|
|
27
|
+
* never runs; the wrapper returns Shrike's reason and the recovery as the
|
|
28
|
+
* tool's result, so the model reads why and what to do next. Tools with no
|
|
29
|
+
* mapping are refused by default (`onUnmapped: 'deny'`).
|
|
30
|
+
*
|
|
31
|
+
* The observe plane is a language-model middleware whose `transformParams`
|
|
32
|
+
* scans the latest user message on the way in, once per message, and never
|
|
33
|
+
* blocks. A finding is prepended to the prompt as a system message.
|
|
34
|
+
*
|
|
35
|
+
* `request_scope` is available as `gov.tool`; add it to the tool set. A held
|
|
36
|
+
* action is answered like a refusal whatever `onHold` says; use the SDK's
|
|
37
|
+
* `needsApproval` if the run itself should pause.
|
|
38
|
+
*
|
|
39
|
+
* Requires `ai` and `zod`.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** The latest user message's text in a language-model prompt. */
|
|
43
|
+
declare function lastUserText(prompt: unknown): string;
|
|
44
|
+
declare class Governance extends Governance$1 {
|
|
45
|
+
private toolCache?;
|
|
46
|
+
private middlewareCache?;
|
|
47
|
+
private lastObserved?;
|
|
48
|
+
/** Wrap each tool's `execute` so Shrike judges its calls. Returns a new tool set. */
|
|
49
|
+
governTools<T extends ToolSet>(tools: T): T;
|
|
50
|
+
/** `request_scope` as an AI SDK tool; add it to your tool set under that name. */
|
|
51
|
+
get tool(): Tool;
|
|
52
|
+
/** Scan a new user message in a prompt, once. Returns the note to prepend, or empty. */
|
|
53
|
+
observePromptMessages(prompt: unknown): Promise<string>;
|
|
54
|
+
/** The observe-plane middleware for `wrapLanguageModel({ model, middleware })`. */
|
|
55
|
+
get middleware(): LanguageModelMiddleware;
|
|
56
|
+
}
|
|
57
|
+
/** Build the governance for one agent. */
|
|
58
|
+
declare function govern(guard: Guard | ScanClient, options: GovernanceOptions): Governance;
|
|
59
|
+
|
|
60
|
+
export { Governance, GovernanceOptions, govern, lastUserText };
|