@ory/cloudflare-agents 0.10.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 +37 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +84 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @ory/cloudflare-agents
|
|
2
|
+
|
|
3
|
+
Ory Agent Security for the [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/).
|
|
4
|
+
|
|
5
|
+
Cloudflare Agents drive tool calls through the Vercel AI SDK (`streamText({ tools })`), so
|
|
6
|
+
Ory wraps each tool's `execute` — authorizing every call against Ory Permissions, tracing it,
|
|
7
|
+
and propagating user → agent identity — built on [`@ory/argus`](../core).
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @ory/cloudflare-agents
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { AIChatAgent } from "agents/ai-chat-agent";
|
|
15
|
+
import { streamText } from "ai";
|
|
16
|
+
import { withOry } from "@ory/cloudflare-agents";
|
|
17
|
+
|
|
18
|
+
export class MyAgent extends AIChatAgent<Env> {
|
|
19
|
+
async onChatMessage(onFinish) {
|
|
20
|
+
return streamText({
|
|
21
|
+
model: this.model,
|
|
22
|
+
messages: this.messages,
|
|
23
|
+
tools: withOry({ ...this.tools, ...this.mcp.getAITools() }), // gate every tool
|
|
24
|
+
onFinish,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
In **enforce** mode (`ORY_PERMISSION_MODE=enforce`) a denied tool throws — the error surfaces
|
|
31
|
+
to the model and the tool never runs. In **observe** mode (default) the tool runs and a
|
|
32
|
+
`permission.observe_deny` span is recorded. Spans are tagged with the `cloudflare-agents`
|
|
33
|
+
integration so the audit trail distinguishes Cloudflare from a plain Vercel app.
|
|
34
|
+
|
|
35
|
+
Depends only on `@ory/argus` (not `ai`/`agents`); tools are duck-typed. Credentials come from
|
|
36
|
+
the shared `~/.config/ory-agent-plugins/config.json`. See
|
|
37
|
+
[docs/sdk-integrations.md](../../docs/sdk-integrations.md).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ory Agent Security for the Cloudflare Agents SDK.
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare Agents drive their LLM tool calls through the **Vercel AI SDK** — inside an
|
|
5
|
+
* `AIChatAgent.onChatMessage` you call `streamText({ tools })` with AI-SDK tools (and
|
|
6
|
+
* `this.mcp.getAITools()` for MCP tools). The interception point is therefore the same:
|
|
7
|
+
* wrap each tool's `execute`. {@link withOry} authorizes every call against Ory Permissions,
|
|
8
|
+
* traces it, and (on the first call) runs the Ory session gates — under the
|
|
9
|
+
* `cloudflare-agents` harness identity so the audit trail attributes Cloudflare distinctly
|
|
10
|
+
* from a plain Vercel app.
|
|
11
|
+
*
|
|
12
|
+
* import { withOry } from "@ory/cloudflare-agents";
|
|
13
|
+
* const result = streamText({
|
|
14
|
+
* model, messages,
|
|
15
|
+
* tools: withOry({ ...this.tools, ...this.mcp.getAITools() }),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* Depends only on `@ory/argus` — the `ai` / `agents` packages are not dependencies; tools are
|
|
19
|
+
* duck-typed so any version works.
|
|
20
|
+
*/
|
|
21
|
+
import { OryAgentClient } from "@ory/argus";
|
|
22
|
+
/** A Vercel AI SDK tool (as used by Cloudflare Agents) — duck-typed. */
|
|
23
|
+
export interface AiTool {
|
|
24
|
+
execute?: (input: unknown, options?: unknown) => unknown;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
export type ToolSet = Record<string, AiTool>;
|
|
28
|
+
export interface WithOryOptions {
|
|
29
|
+
/** Client to use. Defaults to `OryAgentClient.fromEnv("cloudflare-agents")`. */
|
|
30
|
+
client?: OryAgentClient;
|
|
31
|
+
/** Override the Ory project URL for the session gates. */
|
|
32
|
+
projectUrl?: string;
|
|
33
|
+
/** Whether a denied tool is hard-blocked (throws). Default true. */
|
|
34
|
+
canBlock?: boolean;
|
|
35
|
+
/** Derive the permission subject from the AI SDK call options (e.g. a thread/user id). */
|
|
36
|
+
subjectFromOptions?: (options: unknown) => string | undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wrap every tool in `tools` so its `execute` is gated through Ory. Tools without an
|
|
40
|
+
* `execute` (client-side / provider-defined) are passed through untouched. Apply it to the
|
|
41
|
+
* tool map you hand to `streamText` — including `this.mcp.getAITools()`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function withOry(tools: ToolSet, options?: WithOryOptions): ToolSet;
|
|
44
|
+
export { OryAgentClient, OryDenialError } from "@ory/argus";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ory Agent Security for the Cloudflare Agents SDK.
|
|
4
|
+
*
|
|
5
|
+
* Cloudflare Agents drive their LLM tool calls through the **Vercel AI SDK** — inside an
|
|
6
|
+
* `AIChatAgent.onChatMessage` you call `streamText({ tools })` with AI-SDK tools (and
|
|
7
|
+
* `this.mcp.getAITools()` for MCP tools). The interception point is therefore the same:
|
|
8
|
+
* wrap each tool's `execute`. {@link withOry} authorizes every call against Ory Permissions,
|
|
9
|
+
* traces it, and (on the first call) runs the Ory session gates — under the
|
|
10
|
+
* `cloudflare-agents` harness identity so the audit trail attributes Cloudflare distinctly
|
|
11
|
+
* from a plain Vercel app.
|
|
12
|
+
*
|
|
13
|
+
* import { withOry } from "@ory/cloudflare-agents";
|
|
14
|
+
* const result = streamText({
|
|
15
|
+
* model, messages,
|
|
16
|
+
* tools: withOry({ ...this.tools, ...this.mcp.getAITools() }),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* Depends only on `@ory/argus` — the `ai` / `agents` packages are not dependencies; tools are
|
|
20
|
+
* duck-typed so any version works.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.OryDenialError = exports.OryAgentClient = void 0;
|
|
24
|
+
exports.withOry = withOry;
|
|
25
|
+
const argus_1 = require("@ory/argus");
|
|
26
|
+
const HARNESS = "cloudflare-agents";
|
|
27
|
+
/**
|
|
28
|
+
* Wrap every tool in `tools` so its `execute` is gated through Ory. Tools without an
|
|
29
|
+
* `execute` (client-side / provider-defined) are passed through untouched. Apply it to the
|
|
30
|
+
* tool map you hand to `streamText` — including `this.mcp.getAITools()`.
|
|
31
|
+
*/
|
|
32
|
+
function withOry(tools, options = {}) {
|
|
33
|
+
const client = options.client ?? argus_1.OryAgentClient.fromEnv(HARNESS);
|
|
34
|
+
const canBlock = options.canBlock ?? true;
|
|
35
|
+
let sessionStarted = false;
|
|
36
|
+
const ensureSession = async () => {
|
|
37
|
+
if (sessionStarted)
|
|
38
|
+
return;
|
|
39
|
+
sessionStarted = true;
|
|
40
|
+
try {
|
|
41
|
+
await (0, argus_1.sessionStart)(client, { harness: HARNESS, projectUrl: options.projectUrl });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
client.logger.warn("session_start.failed", {
|
|
45
|
+
message: err instanceof Error ? err.message : String(err),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const wrapped = {};
|
|
50
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
51
|
+
if (!tool || typeof tool.execute !== "function") {
|
|
52
|
+
wrapped[name] = tool;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const originalExecute = tool.execute.bind(tool);
|
|
56
|
+
wrapped[name] = {
|
|
57
|
+
...tool,
|
|
58
|
+
execute: async (input, callOptions) => {
|
|
59
|
+
await ensureSession();
|
|
60
|
+
const result = await (0, argus_1.gate)(client, {
|
|
61
|
+
harness: HARNESS,
|
|
62
|
+
toolName: name,
|
|
63
|
+
toolArgs: input,
|
|
64
|
+
subjectFallback: options.subjectFromOptions?.(callOptions),
|
|
65
|
+
canBlock,
|
|
66
|
+
});
|
|
67
|
+
if (result.blocked) {
|
|
68
|
+
throw new argus_1.OryDenialError({
|
|
69
|
+
tool: name,
|
|
70
|
+
subjectId: result.subject,
|
|
71
|
+
namespace: result.namespace,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const output = await originalExecute(input, callOptions);
|
|
75
|
+
(0, argus_1.complete)(client, { toolName: name, output });
|
|
76
|
+
return output;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return wrapped;
|
|
81
|
+
}
|
|
82
|
+
var argus_2 = require("@ory/argus");
|
|
83
|
+
Object.defineProperty(exports, "OryAgentClient", { enumerable: true, get: function () { return argus_2.OryAgentClient; } });
|
|
84
|
+
Object.defineProperty(exports, "OryDenialError", { enumerable: true, get: function () { return argus_2.OryDenialError; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ory/cloudflare-agents",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Ory Agent Security for the Cloudflare Agents SDK — per-tool authorization, tracing, and identity propagation by wrapping the AI SDK tools passed to streamText. Built on @ory/argus.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"!dist/**/*.tsbuildinfo"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ory",
|
|
20
|
+
"cloudflare",
|
|
21
|
+
"agents",
|
|
22
|
+
"workers",
|
|
23
|
+
"authorization",
|
|
24
|
+
"agent",
|
|
25
|
+
"ai"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@ory/argus": "0.10.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^6.0.2",
|
|
32
|
+
"vitest": "4.1.4"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
}
|
|
41
|
+
}
|