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.
Files changed (61) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +66 -0
  3. package/dist/ai/index.d.mts +60 -0
  4. package/dist/ai/index.d.ts +60 -0
  5. package/dist/ai/index.js +476 -0
  6. package/dist/ai/index.js.map +1 -0
  7. package/dist/ai/index.mjs +472 -0
  8. package/dist/ai/index.mjs.map +1 -0
  9. package/dist/anthropic/index.d.mts +1 -1
  10. package/dist/anthropic/index.d.ts +1 -1
  11. package/dist/anthropic/index.js +1 -1
  12. package/dist/anthropic/index.js.map +1 -1
  13. package/dist/anthropic/index.mjs +1 -1
  14. package/dist/anthropic/index.mjs.map +1 -1
  15. package/dist/api/index.d.mts +1 -1
  16. package/dist/api/index.d.ts +1 -1
  17. package/dist/api/index.js +1 -1
  18. package/dist/api/index.js.map +1 -1
  19. package/dist/api/index.mjs +1 -1
  20. package/dist/api/index.mjs.map +1 -1
  21. package/dist/claude-agent/index.d.mts +91 -0
  22. package/dist/claude-agent/index.d.ts +91 -0
  23. package/dist/claude-agent/index.js +492 -0
  24. package/dist/claude-agent/index.js.map +1 -0
  25. package/dist/claude-agent/index.mjs +485 -0
  26. package/dist/claude-agent/index.mjs.map +1 -0
  27. package/dist/gemini/index.d.mts +1 -1
  28. package/dist/gemini/index.d.ts +1 -1
  29. package/dist/gemini/index.js +1 -1
  30. package/dist/gemini/index.js.map +1 -1
  31. package/dist/gemini/index.mjs +1 -1
  32. package/dist/gemini/index.mjs.map +1 -1
  33. package/dist/govern-R_Lx6i1l.d.ts +202 -0
  34. package/dist/govern-rW3QdqsQ.d.mts +202 -0
  35. package/dist/index.d.mts +2 -2
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.js +40 -6
  38. package/dist/index.js.map +1 -1
  39. package/dist/index.mjs +40 -6
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/langchain/index.d.mts +75 -0
  42. package/dist/langchain/index.d.ts +75 -0
  43. package/dist/langchain/index.js +484 -0
  44. package/dist/langchain/index.js.map +1 -0
  45. package/dist/langchain/index.mjs +479 -0
  46. package/dist/langchain/index.mjs.map +1 -0
  47. package/dist/openai/index.d.mts +1 -1
  48. package/dist/openai/index.d.ts +1 -1
  49. package/dist/openai/index.js +1 -1
  50. package/dist/openai/index.js.map +1 -1
  51. package/dist/openai/index.mjs +1 -1
  52. package/dist/openai/index.mjs.map +1 -1
  53. package/dist/openai-agents/index.d.mts +66 -0
  54. package/dist/openai-agents/index.d.ts +66 -0
  55. package/dist/openai-agents/index.js +499 -0
  56. package/dist/openai-agents/index.js.map +1 -0
  57. package/dist/openai-agents/index.mjs +492 -0
  58. package/dist/openai-agents/index.mjs.map +1 -0
  59. package/dist/{scanner-z9r8BJvO.d.mts → scanner-U1SygSM2.d.mts} +21 -2
  60. package/dist/{scanner-z9r8BJvO.d.ts → scanner-U1SygSM2.d.ts} +21 -2
  61. 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 };