@ory/claude-agent-sdk 0.13.9 → 1.0.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 CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Ory Agent Security for the [Anthropic Claude Agent SDK](https://docs.claude.com/en/docs/claude-code/sdk).
4
4
 
5
- Authorizes every tool call against Ory Permissions, traces invocations, and propagates
6
- user → agent → sub-agent identity — built on [`@ory/argus`](../core).
5
+ Authorizes every tool call against Ory Permissions, records invocation activity, and propagates
6
+ user → agent → sub-agent identity — built on [`@ory/argus`](https://www.npmjs.com/package/@ory/argus).
7
7
 
8
8
  ```bash
9
9
  npm install @ory/claude-agent-sdk
@@ -15,7 +15,7 @@ Two interchangeable wiring points:
15
15
  import { query } from "@anthropic-ai/claude-agent-sdk";
16
16
  import { createOryHooks, oryCanUseTool } from "@ory/claude-agent-sdk";
17
17
 
18
- // (a) hooks: SessionStart (auth gates) + PreToolUse (authorize) + PostToolUse (trace)
18
+ // (a) hooks: SessionStart (root auth) + SubagentStart (child auth) + tool authorization/activity
19
19
  query({ prompt, options: { hooks: createOryHooks() } });
20
20
 
21
21
  // (b) or the permission callback
@@ -24,10 +24,11 @@ query({ prompt, options: { canUseTool: oryCanUseTool() } });
24
24
 
25
25
  `createOryHooks` runs the Ory session gates on `SessionStart`, authorizes on `PreToolUse`
26
26
  (a deny returns `permissionDecision: "deny"`, blocking the tool), records `tool.complete` on
27
- `PostToolUse`, and writes an agent→subagent delegation permission when the `Task` tool spawns a
28
- typed sub-agent. In **observe** mode (default) denies are logged but tools run; in **enforce**
29
- mode (`ORY_PERMISSION_MODE=enforce`) they're blocked.
27
+ `PostToolUse`, and enrolls each typed child from the authoritative `SubagentStart` event using
28
+ its `session_id`, `agent_type`, and `agent_id`. Later hooks executing inside that child reload
29
+ its scoped credential before permission and event requests. In **observe** mode (default) denies
30
+ are logged but tools run; in **enforce** mode they're blocked.
30
31
 
31
32
  This package depends only on `@ory/argus` — the hook and permission types are defined locally
32
33
  (no SDK dependency). Credentials come from the shared
33
- `~/.config/ory-agent-plugins/config.json`. See [docs/sdk-integrations.md](../../docs/sdk-integrations.md).
34
+ the operating system credential store; plaintext configuration contains no runtime secrets.
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  * Two interchangeable wiring points, both built on the shared `@ory/argus` adapters:
5
5
  *
6
6
  * - {@link createOryHooks} — a `hooks` config with `SessionStart` (auth gates),
7
- * `PreToolUse` (authorize; `permissionDecision: "deny"` blocks; `Task` sub-agents get a
8
- * delegation tuple), and `PostToolUse` (`tool.complete`).
7
+ * `SubagentStart` (child identity enrollment), `PreToolUse` (authorize;
8
+ * `permissionDecision: "deny"` blocks), and `PostToolUse` (`tool.complete`).
9
9
  * - {@link oryCanUseTool} — a `canUseTool` callback returning allow/deny, for callers who
10
10
  * prefer the single permission-callback surface.
11
11
  *
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@
5
5
  * Two interchangeable wiring points, both built on the shared `@ory/argus` adapters:
6
6
  *
7
7
  * - {@link createOryHooks} — a `hooks` config with `SessionStart` (auth gates),
8
- * `PreToolUse` (authorize; `permissionDecision: "deny"` blocks; `Task` sub-agents get a
9
- * delegation tuple), and `PostToolUse` (`tool.complete`).
8
+ * `SubagentStart` (child identity enrollment), `PreToolUse` (authorize;
9
+ * `permissionDecision: "deny"` blocks), and `PostToolUse` (`tool.complete`).
10
10
  * - {@link oryCanUseTool} — a `canUseTool` callback returning allow/deny, for callers who
11
11
  * prefer the single permission-callback surface.
12
12
  *
@@ -18,91 +18,124 @@ exports.createOryHooks = createOryHooks;
18
18
  exports.oryCanUseTool = oryCanUseTool;
19
19
  const argus_1 = require("@ory/argus");
20
20
  const HARNESS = "claude-agent-sdk";
21
- /**
22
- * Extract the sub-agent type from a `Task`/`Agent` PreToolUse payload.
23
- *
24
- * In the real Claude PreToolUse contract `subagent_type` is a field of the
25
- * Task tool's `tool_input`, so that is the primary location. A top-level
26
- * `subagent_type` is kept as a harmless fallback for older payload shapes.
27
- */
28
- function resolveSubAgentType(input) {
29
- const toolInput = input.tool_input;
30
- if (typeof toolInput === "object" &&
31
- toolInput !== null &&
32
- typeof toolInput.subagent_type === "string") {
33
- return toolInput.subagent_type;
34
- }
35
- return typeof input.subagent_type === "string" ? input.subagent_type : undefined;
36
- }
37
- function lazySession(client, projectUrl) {
38
- let sessionPromise;
39
- // Cache the in-flight promise (not a boolean) so concurrent first calls all
40
- // await the same session start instead of racing past a still-running gate.
41
- // Fail-open: a rejection is logged, never rethrown, and clears the cache so
42
- // a later call can retry.
43
- return () => {
44
- sessionPromise ??= (0, argus_1.sessionStart)(client, { harness: HARNESS, projectUrl }).then(() => undefined, (err) => {
45
- sessionPromise = undefined;
46
- client.logger.warn("session_start.failed", {
47
- message: err instanceof Error ? err.message : String(err),
48
- });
49
- });
50
- return sessionPromise;
51
- };
52
- }
53
21
  /**
54
22
  * Build a `hooks` config object for the Claude Agent SDK's `options.hooks`.
55
23
  */
56
24
  function createOryHooks(options = {}) {
57
25
  const client = options.client ?? argus_1.OryAgentClient.fromEnv(HARNESS);
58
26
  const canBlock = options.canBlock ?? true;
59
- const ensureSession = lazySession(client, options.projectUrl);
60
- const sessionStartHook = async () => {
61
- await ensureSession();
62
- return {};
27
+ const ensureSession = (0, argus_1.createSessionStarter)(client, {
28
+ harness: HARNESS,
29
+ projectUrl: options.projectUrl,
30
+ });
31
+ const sessionStartHook = async (raw) => {
32
+ const sessionId = typeof raw.session_id === "string" ? raw.session_id : undefined;
33
+ return (0, argus_1.withHookContext)(client, { sessionId }, async () => {
34
+ await ensureSession();
35
+ return {};
36
+ });
63
37
  };
64
38
  const preToolUse = async (raw) => {
65
- await ensureSession();
66
39
  const input = raw;
67
- const toolName = input.tool_name;
68
- if (toolName === "Task" || toolName === "Agent") {
69
- const subAgentType = resolveSubAgentType(input);
70
- if (subAgentType) {
40
+ return (0, argus_1.withHookContext)(client, { sessionId: input.session_id }, async () => {
41
+ await ensureSession();
42
+ const toolName = input.tool_name;
43
+ const active = await activateSubagent(client, input);
44
+ const result = await (0, argus_1.withHookContext)(client, { runtimeCredential: active.runtimeCredential }, () => (0, argus_1.gate)(client, {
45
+ harness: HARNESS,
46
+ toolName,
47
+ toolArgs: input.tool_input,
48
+ canBlock,
49
+ principals: active.principals,
50
+ }));
51
+ if (result.kind === "deny" && result.blocked) {
52
+ return {
53
+ hookSpecificOutput: {
54
+ hookEventName: "PreToolUse",
55
+ permissionDecision: "deny",
56
+ permissionDecisionReason: result.denialMessage ?? "Ory: permission denied",
57
+ },
58
+ };
59
+ }
60
+ return {};
61
+ });
62
+ };
63
+ const subagentStart = async (raw, _toolUseId, context) => {
64
+ const input = raw;
65
+ return (0, argus_1.withHookContext)(client, { sessionId: input.session_id }, async () => {
66
+ await ensureSession();
67
+ const subAgentType = input.agent_type ?? input.subagent_type;
68
+ if (subAgentType && input.agent_id) {
71
69
  await (0, argus_1.registerSubagent)(client, {
72
70
  harness: HARNESS,
73
71
  subAgentType,
74
72
  projectUrl: options.projectUrl,
73
+ sessionId: input.session_id,
74
+ perSpawnId: input.agent_id,
75
+ signal: context.signal,
75
76
  }).catch(() => undefined);
76
77
  }
77
- }
78
- const result = await (0, argus_1.gate)(client, {
79
- harness: HARNESS,
80
- toolName,
81
- toolArgs: input.tool_input,
82
- canBlock,
78
+ return {};
83
79
  });
84
- if (result.kind === "deny" && result.blocked) {
85
- return {
86
- hookSpecificOutput: {
87
- hookEventName: "PreToolUse",
88
- permissionDecision: "deny",
89
- permissionDecisionReason: result.denialMessage ?? "Ory: permission denied",
90
- },
91
- };
92
- }
93
- return {};
94
80
  };
95
81
  const postToolUse = async (raw) => {
96
82
  const input = raw;
97
- (0, argus_1.complete)(client, { toolName: input.tool_name, output: input.tool_response });
98
- return {};
83
+ return (0, argus_1.withHookContext)(client, { sessionId: input.session_id }, async () => {
84
+ const active = await activateSubagent(client, input);
85
+ return (0, argus_1.withHookContext)(client, { runtimeCredential: active.runtimeCredential }, () => {
86
+ (0, argus_1.complete)(client, { toolName: input.tool_name, output: input.tool_response });
87
+ return {};
88
+ });
89
+ });
99
90
  };
100
91
  return {
101
92
  SessionStart: [{ hooks: [sessionStartHook] }],
93
+ SubagentStart: [{ hooks: [subagentStart] }],
102
94
  PreToolUse: [{ hooks: [preToolUse] }],
103
95
  PostToolUse: [{ hooks: [postToolUse] }],
104
96
  };
105
97
  }
98
+ async function activateSubagent(client, input) {
99
+ const type = input.agent_type ?? input.subagent_type;
100
+ if (!input.agent_id || !type || !(0, argus_1.isSecurityConnected)()) {
101
+ return { principals: actingSubagentPrincipals(input) };
102
+ }
103
+ const identity = await (0, argus_1.ensureSubAgentIdentity)(client, {
104
+ harness: HARNESS,
105
+ sessionId: input.session_id,
106
+ subAgentType: type,
107
+ perSpawnId: input.agent_id,
108
+ allowEnrollment: false,
109
+ emitActivity: false,
110
+ }).catch(() => undefined);
111
+ if (!identity?.runtimeCredential)
112
+ return { principals: actingSubagentPrincipals(input) };
113
+ return {
114
+ runtimeCredential: identity.runtimeCredential,
115
+ principals: {
116
+ subAgentClientId: identity.subject,
117
+ subAgentType: type,
118
+ perSpawnId: input.agent_id,
119
+ sessionId: input.session_id,
120
+ },
121
+ };
122
+ }
123
+ function actingSubagentPrincipals(input) {
124
+ const type = input.agent_type;
125
+ if (!input.agent_id || !type)
126
+ return undefined;
127
+ const credential = input.session_id
128
+ ? (0, argus_1.loadSubAgentDynamicCredentials)(HARNESS, input.session_id, type)
129
+ : (0, argus_1.loadSubAgentDynamicCredentials)(HARNESS, type);
130
+ if (!credential)
131
+ return undefined;
132
+ return {
133
+ subAgentClientId: credential.clientId,
134
+ subAgentType: type,
135
+ perSpawnId: input.agent_id,
136
+ sessionId: input.session_id,
137
+ };
138
+ }
106
139
  /**
107
140
  * Build a `canUseTool` callback gated through Ory. Returns `{ behavior: "deny" }` when the
108
141
  * tool is hard-denied, otherwise `{ behavior: "allow" }`.
@@ -110,7 +143,10 @@ function createOryHooks(options = {}) {
110
143
  function oryCanUseTool(options = {}) {
111
144
  const client = options.client ?? argus_1.OryAgentClient.fromEnv(HARNESS);
112
145
  const canBlock = options.canBlock ?? true;
113
- const ensureSession = lazySession(client, options.projectUrl);
146
+ const ensureSession = (0, argus_1.createSessionStarter)(client, {
147
+ harness: HARNESS,
148
+ projectUrl: options.projectUrl,
149
+ });
114
150
  return async (toolName, input) => {
115
151
  await ensureSession();
116
152
  const result = await (0, argus_1.gate)(client, {
package/dist/types.d.ts CHANGED
@@ -20,6 +20,10 @@ export interface PreToolUseHookInput {
20
20
  */
21
21
  subagent_type?: string;
22
22
  session_id?: string;
23
+ /** Present on hooks executing inside a spawned sub-agent. */
24
+ agent_id?: string;
25
+ /** Type of the sub-agent currently executing this hook. */
26
+ agent_type?: string;
23
27
  [key: string]: unknown;
24
28
  }
25
29
  export interface PostToolUseHookInput {
@@ -27,6 +31,10 @@ export interface PostToolUseHookInput {
27
31
  tool_name: string;
28
32
  tool_input?: unknown;
29
33
  tool_response?: unknown;
34
+ session_id?: string;
35
+ agent_id?: string;
36
+ agent_type?: string;
37
+ subagent_type?: string;
30
38
  [key: string]: unknown;
31
39
  }
32
40
  export interface HookJSONOutput {
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "@ory/claude-agent-sdk",
3
- "version": "0.13.9",
4
- "description": "Ory Agent Security for the Anthropic Claude Agent SDK — per-tool authorization, tracing, and identity propagation via PreToolUse hooks and canUseTool. Built on @ory/argus.",
3
+ "version": "1.0.0",
4
+ "description": "Ory Agent Security for the Anthropic Claude Agent SDK — per-tool authorization, activity auditing, and identity propagation via PreToolUse hooks and canUseTool. Built on @ory/argus.",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public",
8
8
  "registry": "https://registry.npmjs.org/",
9
9
  "provenance": true
10
10
  },
11
+ "reova": {
12
+ "enabled": true,
13
+ "endpoint": "https://telemetry.reo.dev/data"
14
+ },
11
15
  "main": "dist/index.js",
12
16
  "types": "dist/index.d.ts",
13
17
  "exports": {
@@ -30,8 +34,8 @@
30
34
  "ai"
31
35
  ],
32
36
  "dependencies": {
33
- "reo-census": "^1.2.8",
34
- "@ory/argus": "0.13.9"
37
+ "reova": "^0.7.0",
38
+ "@ory/argus": "1.0.0"
35
39
  },
36
40
  "devDependencies": {
37
41
  "typescript": "^6.0.2",