@beignet/agent-auth-better-auth 0.0.35

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 ADDED
@@ -0,0 +1,15 @@
1
+ # @beignet/agent-auth-better-auth
2
+
3
+ ## 0.0.35
4
+
5
+ ### Patch Changes
6
+
7
+ - 2101386: Authorize agent capabilities against their exact parsed input and keep custom Agent Auth locations outside the Beignet execution bridge.
8
+ - 2290c4a: Add typed, validated agent capability registries and execution, plus a Better Auth Agent Auth bridge with constraint-safe validation, verified principals, grants, and redacted protocol errors.
9
+ - 42921da: Keep agent capability registries type-safe, observe pre-context failures, and preserve application error codes across Agent Auth execution modes.
10
+
11
+ ## 0.0.34
12
+
13
+ ### Patch Changes
14
+
15
+ - Add the initial Better Auth Agent Auth adapter for typed Beignet agent capabilities.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Taylor Bryant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @beignet/agent-auth-better-auth
2
+
3
+ > [!CAUTION]
4
+ > Beignet is experimental alpha software. The `0.0.x` package line is for early
5
+ > evaluation, and APIs may change between releases while the framework settles.
6
+
7
+ Expose a typed Beignet agent capability registry through Better Auth's Agent
8
+ Auth plugin. Beignet owns capability validation, application context, use-case
9
+ execution, tracing, and instrumentation. Agent Auth owns registration, JWT
10
+ verification, grants, constraints, approval, and protocol persistence.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ bun add @beignet/core @beignet/agent-auth-better-auth \
16
+ @better-auth/agent-auth better-auth zod
17
+ ```
18
+
19
+ ## Define capabilities
20
+
21
+ ```typescript
22
+ // lib/agent-capabilities.ts
23
+ import { createAgentCapabilities } from "@beignet/core/agent-capabilities";
24
+ import type { AppContext } from "@/app-context";
25
+
26
+ export type AgentPrincipal = {
27
+ agentId: string;
28
+ userId: string;
29
+ };
30
+
31
+ export const { defineAgentCapability, defineAgentCapabilityRegistry } =
32
+ createAgentCapabilities<AppContext, AgentPrincipal>();
33
+ ```
34
+
35
+ ```typescript
36
+ // features/issues/agent-capabilities.ts
37
+ import { z } from "zod";
38
+ import { createIssueUseCase } from "./use-cases";
39
+ import { defineAgentCapability } from "@/lib/agent-capabilities";
40
+
41
+ export const createIssueCapability = defineAgentCapability("issues.create", {
42
+ description: "Create an issue in one workspace.",
43
+ input: z.object({
44
+ workspaceId: z.string().min(1),
45
+ title: z.string().min(1),
46
+ }),
47
+ output: z.object({ id: z.string(), title: z.string() }),
48
+ async handle({ ctx, input }) {
49
+ const { workspaceId: _workspaceId, ...useCaseInput } = input;
50
+ return createIssueUseCase.run({ ctx, input: useCaseInput });
51
+ },
52
+ });
53
+ ```
54
+
55
+ ## Create the registry and executor
56
+
57
+ ```typescript
58
+ import { createAgentCapabilityExecutor } from "@beignet/core/agent-capabilities";
59
+ import { appError } from "@/features/shared/errors";
60
+ import { createIssueCapability } from "@/features/issues/agent-capabilities";
61
+ import { defineAgentCapabilityRegistry } from "@/lib/agent-capabilities";
62
+ import { getServer } from "@/server";
63
+
64
+ export const agentCapabilityRegistry =
65
+ defineAgentCapabilityRegistry([createIssueCapability]);
66
+
67
+ export const agentCapabilityExecutor = createAgentCapabilityExecutor({
68
+ registry: agentCapabilityRegistry,
69
+ async createContext({ principal, input }) {
70
+ const server = await getServer();
71
+ const membership = await server.ports.members.findMembership({
72
+ workspaceId: input.workspaceId,
73
+ userId: principal.userId,
74
+ });
75
+ if (!membership) throw appError("NotAWorkspaceMember");
76
+
77
+ return server.createServiceContext({
78
+ asUser: { id: principal.userId, role: membership.role },
79
+ tenantId: input.workspaceId,
80
+ });
81
+ },
82
+ });
83
+ ```
84
+
85
+ The executor validates arguments before context construction and validates the
86
+ handler result before returning it to Agent Auth. The context resolver must
87
+ derive tenant membership from authoritative app state rather than agent claims.
88
+
89
+ ## Add the Agent Auth bridge
90
+
91
+ ```typescript
92
+ import { agentAuth } from "@better-auth/agent-auth";
93
+ import { betterAuth } from "better-auth";
94
+ import { APIError } from "better-auth/api";
95
+ import { createBetterAuthAgentCapabilityAdapter } from
96
+ "@beignet/agent-auth-better-auth";
97
+ import {
98
+ agentCapabilityExecutor,
99
+ agentCapabilityRegistry,
100
+ } from "@/server/agent-capabilities";
101
+
102
+ const agentCapabilities = createBetterAuthAgentCapabilityAdapter({
103
+ registry: agentCapabilityRegistry,
104
+ executor: agentCapabilityExecutor,
105
+ principal({ agentSession }) {
106
+ if (!agentSession.userId) {
107
+ throw new APIError("FORBIDDEN", {
108
+ message: "A delegated user is required.",
109
+ });
110
+ }
111
+ return {
112
+ agentId: agentSession.agentId,
113
+ userId: agentSession.userId,
114
+ };
115
+ },
116
+ metadata: {
117
+ "issues.create": {
118
+ approvalStrength: "session",
119
+ requiredConstraints: ["workspaceId"],
120
+ },
121
+ },
122
+ });
123
+
124
+ export const auth = betterAuth({
125
+ // database, sessions, and other app-owned configuration
126
+ plugins: [
127
+ agentAuth({
128
+ ...agentCapabilities,
129
+ providerName: "example",
130
+ modes: ["delegated"],
131
+ }),
132
+ ],
133
+ });
134
+ ```
135
+
136
+ Zod schemas are converted to protocol JSON Schema by default. Pass a Beignet
137
+ `SchemaConverter` through `schemaConverter` when using another Standard Schema
138
+ library. Conversion runs at adapter creation so unsupported schemas fail during
139
+ boot instead of on the first agent request.
140
+
141
+ Agent Auth authorizes grant constraints against raw arguments before invoking
142
+ the adapter. Beignet therefore rejects an invocation when schema validation
143
+ changes a field constrained by the active grant. Keep constrained identifiers
144
+ and amounts non-transforming; normalization remains available for unconstrained
145
+ fields. The adapter snapshots active constrained values before validation,
146
+ parses once, and checks the exact parsed input subsequently used for context
147
+ construction and execution.
148
+
149
+ This bridge intentionally uses Agent Auth's default capability execution
150
+ endpoint. It does not expose custom capability `location` metadata because
151
+ custom locations send requests to an app-owned route and bypass this bridge's
152
+ validation, context, constraint-stability, tracing, and output guarantees.
153
+
154
+ The adapter preserves intentional Better Auth `APIError` failures. Declared
155
+ Beignet `AppError` failures use their stable application code as the Agent Auth
156
+ error code. Individual execution also preserves the AppError HTTP status;
157
+ batch execution reports each item as failed and preserves the code without a
158
+ per-item HTTP status. Invalid arguments become Agent Auth `invalid_request`
159
+ errors; unknown capabilities use `capability_not_found`; invalid outputs and
160
+ unexpected failures become redacted `internal_error` responses. Throw
161
+ `APIError` or a declared `AppError` only when a failure is intentionally safe
162
+ for an agent to receive.
163
+
164
+ Only synchronous request/response capability results are supported in this
165
+ release. Agent Auth asynchronous polling and streaming results remain
166
+ transport-owned and are not produced by the Beignet executor.
167
+
168
+ ## Agent skills
169
+
170
+ This package ships a TanStack Intent skill:
171
+ `@beignet/agent-auth-better-auth#agent-capabilities`. Load it when defining a
172
+ capability registry, constructing delegated service contexts, or wiring Better
173
+ Auth Agent Auth.
@@ -0,0 +1,31 @@
1
+ import type { AgentCapabilityExecutor, AgentCapabilityPrincipal, AgentCapabilityRegistry, AnyAgentCapabilityDef, MaybePromise } from "@beignet/core/agent-capabilities";
2
+ import { type SchemaConverter } from "@beignet/core/openapi";
3
+ import { type AgentAuthOptions, type Capability } from "@better-auth/agent-auth";
4
+ /** Agent Auth metadata that does not replace Beignet-owned capability fields. */
5
+ export type BetterAuthAgentCapabilityMetadata = Pick<Capability, "approvalStrength" | "grantTTL" | "requiredConstraints">;
6
+ /** Error raised while adapting a Beignet capability registry. */
7
+ export declare class BetterAuthAgentCapabilityAdapterError extends Error {
8
+ readonly capabilityName: string;
9
+ readonly cause?: unknown;
10
+ constructor(args: {
11
+ capabilityName: string;
12
+ message: string;
13
+ cause?: unknown;
14
+ });
15
+ }
16
+ /** Options for adapting a Beignet registry to Better Auth Agent Auth. */
17
+ export interface CreateBetterAuthAgentCapabilityAdapterOptions<Definitions extends readonly AnyAgentCapabilityDef[]> {
18
+ registry: AgentCapabilityRegistry<Definitions>;
19
+ executor: AgentCapabilityExecutor<AgentCapabilityPrincipal<Definitions[number]>, Definitions>;
20
+ principal(context: Parameters<NonNullable<AgentAuthOptions["onExecute"]>>[0]): MaybePromise<AgentCapabilityPrincipal<Definitions[number]>>;
21
+ schemaConverter?: SchemaConverter;
22
+ metadata?: Partial<Record<Definitions[number]["name"], BetterAuthAgentCapabilityMetadata>>;
23
+ }
24
+ /** Better Auth options produced from a Beignet capability registry. */
25
+ export type BetterAuthAgentCapabilityAdapter = Pick<AgentAuthOptions, "capabilities" | "onExecute">;
26
+ /**
27
+ * Convert a Beignet capability registry and executor into options accepted by
28
+ * Better Auth's `agentAuth(...)` plugin.
29
+ */
30
+ export declare function createBetterAuthAgentCapabilityAdapter<const Definitions extends readonly AnyAgentCapabilityDef[]>(options: CreateBetterAuthAgentCapabilityAdapterOptions<Definitions>): BetterAuthAgentCapabilityAdapter;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,YAAY,EACb,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,UAAU,EAChB,MAAM,yBAAyB,CAAC;AAGjC,iFAAiF;AACjF,MAAM,MAAM,iCAAiC,GAAG,IAAI,CAClD,UAAU,EACV,kBAAkB,GAAG,UAAU,GAAG,qBAAqB,CACxD,CAAC;AAEF,iEAAiE;AACjE,qBAAa,qCAAsC,SAAQ,KAAK;IAC9D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,SAAkB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEtB,IAAI,EAAE;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CAMF;AAED,yEAAyE;AACzE,MAAM,WAAW,6CAA6C,CAC5D,WAAW,SAAS,SAAS,qBAAqB,EAAE;IAEpD,QAAQ,EAAE,uBAAuB,CAAC,WAAW,CAAC,CAAC;IAC/C,QAAQ,EAAE,uBAAuB,CAC/B,wBAAwB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EAC7C,WAAW,CACZ,CAAC;IACF,SAAS,CACP,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GACjE,YAAY,CAAC,wBAAwB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC/D,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAChB,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,iCAAiC,CAAC,CACvE,CAAC;CACH;AAED,uEAAuE;AACvE,MAAM,MAAM,gCAAgC,GAAG,IAAI,CACjD,gBAAgB,EAChB,cAAc,GAAG,WAAW,CAC7B,CAAC;AAmIF;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,KAAK,CAAC,WAAW,SAAS,SAAS,qBAAqB,EAAE,EAE1D,OAAO,EAAE,6CAA6C,CAAC,WAAW,CAAC,GAClE,gCAAgC,CAgDlC"}
package/dist/index.js ADDED
@@ -0,0 +1,153 @@
1
+ import { AgentCapabilityError } from "@beignet/core/agent-capabilities";
2
+ import { isAppError } from "@beignet/core/errors";
3
+ import { createZodSchemaConverter, } from "@beignet/core/openapi";
4
+ import { AGENT_AUTH_ERROR_CODES, agentError, } from "@better-auth/agent-auth";
5
+ import { APIError } from "better-auth/api";
6
+ /** Error raised while adapting a Beignet capability registry. */
7
+ export class BetterAuthAgentCapabilityAdapterError extends Error {
8
+ constructor(args) {
9
+ super(args.message);
10
+ this.name = "BetterAuthAgentCapabilityAdapterError";
11
+ this.capabilityName = args.capabilityName;
12
+ this.cause = args.cause;
13
+ }
14
+ }
15
+ function convertSchema(args) {
16
+ if (!args.converter.canConvert(args.schema)) {
17
+ throw new BetterAuthAgentCapabilityAdapterError({
18
+ capabilityName: args.capabilityName,
19
+ message: `Schema converter "${args.converter.name}" cannot convert the ${args.io} schema for agent capability "${args.capabilityName}".`,
20
+ });
21
+ }
22
+ try {
23
+ return args.converter.toJSONSchema(args.schema, {
24
+ io: args.io,
25
+ nameHint: `${args.capabilityName}.${args.io}`,
26
+ });
27
+ }
28
+ catch (error) {
29
+ throw new BetterAuthAgentCapabilityAdapterError({
30
+ capabilityName: args.capabilityName,
31
+ message: `Schema converter "${args.converter.name}" failed to convert the ${args.io} schema for agent capability "${args.capabilityName}".`,
32
+ cause: error,
33
+ });
34
+ }
35
+ }
36
+ function throwAppError(error) {
37
+ throw agentError(error.status, {
38
+ code: error.code,
39
+ message: error.message,
40
+ }, undefined, error.headers, {
41
+ ...(error.details === undefined ? {} : { details: error.details }),
42
+ });
43
+ }
44
+ function mapExecutionError(error) {
45
+ if (isBetterAuthApiError(error))
46
+ throw error;
47
+ if (isAppError(error))
48
+ return throwAppError(error);
49
+ if (!(error instanceof AgentCapabilityError)) {
50
+ throw agentError("INTERNAL_SERVER_ERROR", AGENT_AUTH_ERROR_CODES.INTERNAL_ERROR);
51
+ }
52
+ if (error.code === "execution_failed") {
53
+ if (isBetterAuthApiError(error.cause))
54
+ throw error.cause;
55
+ if (isAppError(error.cause))
56
+ return throwAppError(error.cause);
57
+ }
58
+ if (error.code === "unknown_capability") {
59
+ throw agentError("BAD_REQUEST", AGENT_AUTH_ERROR_CODES.CAPABILITY_NOT_FOUND);
60
+ }
61
+ if (error.code === "invalid_input") {
62
+ throw agentError("BAD_REQUEST", AGENT_AUTH_ERROR_CODES.INVALID_REQUEST);
63
+ }
64
+ throw agentError("INTERNAL_SERVER_ERROR", AGENT_AUTH_ERROR_CODES.INTERNAL_ERROR);
65
+ }
66
+ function isBetterAuthApiError(value) {
67
+ if (value instanceof APIError)
68
+ return true;
69
+ if (!(value instanceof Error) || value.name !== "APIError")
70
+ return false;
71
+ if (!("statusCode" in value) || typeof value.statusCode !== "number") {
72
+ return false;
73
+ }
74
+ return "body" in value && (value.body === undefined || value.body !== null);
75
+ }
76
+ function assertConstrainedFieldsAreStable(args) {
77
+ const constraints = args.constraints;
78
+ if (!constraints)
79
+ return;
80
+ const constrainedFields = Object.keys(constraints);
81
+ if (constrainedFields.length === 0)
82
+ return;
83
+ const parsedInput = args.parsedInput && typeof args.parsedInput === "object"
84
+ ? args.parsedInput
85
+ : undefined;
86
+ for (const field of constrainedFields) {
87
+ if (!args.constrainedInput ||
88
+ !parsedInput ||
89
+ !Object.is(args.constrainedInput[field], parsedInput[field])) {
90
+ throw agentError("FORBIDDEN", AGENT_AUTH_ERROR_CODES.CONSTRAINT_VIOLATED, `Constrained capability argument "${field}" changed during validation.`);
91
+ }
92
+ }
93
+ }
94
+ function snapshotConstrainedInput(args) {
95
+ if (!args.constraints)
96
+ return undefined;
97
+ const rawInput = args.rawInput && typeof args.rawInput === "object"
98
+ ? args.rawInput
99
+ : undefined;
100
+ return Object.fromEntries(Object.keys(args.constraints).map((field) => [field, rawInput?.[field]]));
101
+ }
102
+ /**
103
+ * Convert a Beignet capability registry and executor into options accepted by
104
+ * Better Auth's `agentAuth(...)` plugin.
105
+ */
106
+ export function createBetterAuthAgentCapabilityAdapter(options) {
107
+ const converter = options.schemaConverter ?? createZodSchemaConverter();
108
+ const capabilities = options.registry.definitions.map((capability) => ({
109
+ name: capability.name,
110
+ description: capability.description,
111
+ input: convertSchema({
112
+ capabilityName: capability.name,
113
+ schema: capability.input,
114
+ io: "input",
115
+ converter,
116
+ }),
117
+ output: convertSchema({
118
+ capabilityName: capability.name,
119
+ schema: capability.output,
120
+ io: "output",
121
+ converter,
122
+ }),
123
+ ...options.metadata?.[capability.name],
124
+ }));
125
+ return {
126
+ capabilities,
127
+ async onExecute(context) {
128
+ try {
129
+ const rawInput = context.arguments ?? {};
130
+ const constrainedInput = snapshotConstrainedInput({
131
+ constraints: context.grant.constraints,
132
+ rawInput,
133
+ });
134
+ return await options.executor.executeDynamic({
135
+ name: context.capability,
136
+ principal: await options.principal(context),
137
+ input: rawInput,
138
+ authorize({ input }) {
139
+ assertConstrainedFieldsAreStable({
140
+ constraints: context.grant.constraints,
141
+ constrainedInput,
142
+ parsedInput: input,
143
+ });
144
+ },
145
+ });
146
+ }
147
+ catch (error) {
148
+ return mapExecutionError(error);
149
+ }
150
+ },
151
+ };
152
+ }
153
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@beignet/agent-auth-better-auth",
3
+ "version": "0.0.35",
4
+ "type": "module",
5
+ "description": "Better Auth Agent Auth adapter for Beignet agent capabilities",
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
+ "src",
17
+ "!src/**/*.test.ts",
18
+ "!src/**/*.test.tsx",
19
+ "!src/**/*.test-d.ts",
20
+ "skills",
21
+ "README.md",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "prepack": "bun run build",
27
+ "dev": "tsc --watch",
28
+ "clean": "rm -rf dist coverage .turbo",
29
+ "test": "bun test && bun run typecheck:types",
30
+ "test:coverage": "bun test --coverage",
31
+ "typecheck:types": "tsc -p tsconfig.type-tests.json",
32
+ "lint": "biome check ."
33
+ },
34
+ "keywords": [
35
+ "beignet",
36
+ "agent",
37
+ "capabilities",
38
+ "better-auth",
39
+ "ai"
40
+ ],
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/taylorbryant/beignet.git",
45
+ "directory": "packages/agent-auth-better-auth"
46
+ },
47
+ "author": "Taylor Bryant",
48
+ "homepage": "https://github.com/taylorbryant/beignet#readme",
49
+ "bugs": {
50
+ "url": "https://github.com/taylorbryant/beignet/issues"
51
+ },
52
+ "sideEffects": false,
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ },
59
+ "peerDependencies": {
60
+ "@beignet/core": ">=0.0.3 <1.0.0",
61
+ "@better-auth/agent-auth": ">=0.6.2 <0.7.0",
62
+ "better-auth": ">=1.4.0 <1.7.0",
63
+ "zod": "^4.0.0"
64
+ },
65
+ "devDependencies": {
66
+ "@beignet/core": "*",
67
+ "@better-auth/agent-auth": "0.6.2",
68
+ "@types/bun": "^1.3.13",
69
+ "@types/node": "^20.10.0",
70
+ "better-auth": "1.6.20",
71
+ "jose": "^6.1.3",
72
+ "typescript": "^5.3.0",
73
+ "zod": "^4.0.0"
74
+ }
75
+ }
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: agent-capabilities
3
+ description: "Expose Beignet use cases to authenticated AI agents with @beignet/core/agent-capabilities and @beignet/agent-auth-better-auth. Use when defining typed capabilities, composing registries, resolving authoritative delegated service contexts, converting schemas, configuring Better Auth Agent Auth grants, or testing agent execution."
4
+ ---
5
+
6
+ # Beignet Agent Capabilities
7
+
8
+ Use `createAgentCapabilities<AppContext, AgentPrincipal>()` once in
9
+ `lib/agent-capabilities.ts` and export both returned definition helpers. Keep
10
+ capability definitions feature-owned and compose them explicitly with the
11
+ context-bound `defineAgentCapabilityRegistry(...)`; it rejects definitions
12
+ from another app context or principal binding.
13
+
14
+ Capabilities are entrypoint adapters, not business workflows. Validate their
15
+ agent-facing input and output with Standard Schema, then call existing use
16
+ cases from `handle(...)`.
17
+
18
+ Create one executor with `createAgentCapabilityExecutor(...)`. Its
19
+ `createContext(...)` callback receives parsed input and a principal derived
20
+ from a verified Agent Auth session. Re-read membership and roles from app
21
+ ports, then call `server.createServiceContext(...)`. Never trust role or tenant
22
+ authorization from agent claims and never assemble `AppContext` manually.
23
+
24
+ Use `createBetterAuthAgentCapabilityAdapter(...)` to produce `capabilities`
25
+ and `onExecute` for `agentAuth(...)`. The adapter converts Zod schemas by
26
+ default; pass a `SchemaConverter` for another schema library. Put Agent Auth
27
+ approval strength, required constraints, and grant TTL in the adapter's typed
28
+ `metadata` map. Do not configure a custom capability location: custom routes
29
+ bypass the bridge and must implement Agent Auth verification, grant enforcement,
30
+ Beignet execution, and output validation independently.
31
+
32
+ Grant constraints are checked by Agent Auth against raw arguments. Do not
33
+ transform constrained fields such as tenant IDs, resource IDs, or amounts in
34
+ the capability input schema. The adapter snapshots active constrained values
35
+ before its single validation pass and rejects executions where validation
36
+ changes one; normalization is fine for unconstrained fields.
37
+
38
+ Better Auth owns registration, JWT verification, grants, constraints,
39
+ approval, and protocol persistence. Beignet owns validation, context, use-case
40
+ execution, tracing, and bounded instrumentation.
41
+
42
+ Unexpected errors are redacted as `internal_error`. Throw a Better Auth
43
+ `APIError` or declared Beignet `AppError` only for failures intentionally safe
44
+ to return to the agent. AppError codes are preserved by individual and batch
45
+ execution; only individual execution carries the AppError HTTP status.
46
+
47
+ Test malformed inputs, missing tenant membership, authorization denial,
48
+ output validation, and successful query/command execution. Do not record raw
49
+ principal data, arguments, or results in instrumentation.
package/src/index.ts ADDED
@@ -0,0 +1,254 @@
1
+ import type {
2
+ AgentCapabilityExecutor,
3
+ AgentCapabilityPrincipal,
4
+ AgentCapabilityRegistry,
5
+ AnyAgentCapabilityDef,
6
+ MaybePromise,
7
+ } from "@beignet/core/agent-capabilities";
8
+ import { AgentCapabilityError } from "@beignet/core/agent-capabilities";
9
+ import { type AppError, isAppError } from "@beignet/core/errors";
10
+ import {
11
+ createZodSchemaConverter,
12
+ type SchemaConverter,
13
+ } from "@beignet/core/openapi";
14
+ import {
15
+ AGENT_AUTH_ERROR_CODES,
16
+ type AgentAuthOptions,
17
+ agentError,
18
+ type Capability,
19
+ } from "@better-auth/agent-auth";
20
+ import { APIError } from "better-auth/api";
21
+
22
+ /** Agent Auth metadata that does not replace Beignet-owned capability fields. */
23
+ export type BetterAuthAgentCapabilityMetadata = Pick<
24
+ Capability,
25
+ "approvalStrength" | "grantTTL" | "requiredConstraints"
26
+ >;
27
+
28
+ /** Error raised while adapting a Beignet capability registry. */
29
+ export class BetterAuthAgentCapabilityAdapterError extends Error {
30
+ readonly capabilityName: string;
31
+ override readonly cause?: unknown;
32
+
33
+ constructor(args: {
34
+ capabilityName: string;
35
+ message: string;
36
+ cause?: unknown;
37
+ }) {
38
+ super(args.message);
39
+ this.name = "BetterAuthAgentCapabilityAdapterError";
40
+ this.capabilityName = args.capabilityName;
41
+ this.cause = args.cause;
42
+ }
43
+ }
44
+
45
+ /** Options for adapting a Beignet registry to Better Auth Agent Auth. */
46
+ export interface CreateBetterAuthAgentCapabilityAdapterOptions<
47
+ Definitions extends readonly AnyAgentCapabilityDef[],
48
+ > {
49
+ registry: AgentCapabilityRegistry<Definitions>;
50
+ executor: AgentCapabilityExecutor<
51
+ AgentCapabilityPrincipal<Definitions[number]>,
52
+ Definitions
53
+ >;
54
+ principal(
55
+ context: Parameters<NonNullable<AgentAuthOptions["onExecute"]>>[0],
56
+ ): MaybePromise<AgentCapabilityPrincipal<Definitions[number]>>;
57
+ schemaConverter?: SchemaConverter;
58
+ metadata?: Partial<
59
+ Record<Definitions[number]["name"], BetterAuthAgentCapabilityMetadata>
60
+ >;
61
+ }
62
+
63
+ /** Better Auth options produced from a Beignet capability registry. */
64
+ export type BetterAuthAgentCapabilityAdapter = Pick<
65
+ AgentAuthOptions,
66
+ "capabilities" | "onExecute"
67
+ >;
68
+
69
+ function convertSchema(args: {
70
+ capabilityName: string;
71
+ schema: unknown;
72
+ io: "input" | "output";
73
+ converter: SchemaConverter;
74
+ }): Record<string, unknown> {
75
+ if (!args.converter.canConvert(args.schema)) {
76
+ throw new BetterAuthAgentCapabilityAdapterError({
77
+ capabilityName: args.capabilityName,
78
+ message: `Schema converter "${args.converter.name}" cannot convert the ${args.io} schema for agent capability "${args.capabilityName}".`,
79
+ });
80
+ }
81
+
82
+ try {
83
+ return args.converter.toJSONSchema(args.schema, {
84
+ io: args.io,
85
+ nameHint: `${args.capabilityName}.${args.io}`,
86
+ });
87
+ } catch (error) {
88
+ throw new BetterAuthAgentCapabilityAdapterError({
89
+ capabilityName: args.capabilityName,
90
+ message: `Schema converter "${args.converter.name}" failed to convert the ${args.io} schema for agent capability "${args.capabilityName}".`,
91
+ cause: error,
92
+ });
93
+ }
94
+ }
95
+
96
+ function throwAppError(error: AppError): never {
97
+ throw agentError(
98
+ error.status as ConstructorParameters<typeof APIError>[0],
99
+ {
100
+ code: error.code,
101
+ message: error.message,
102
+ },
103
+ undefined,
104
+ error.headers,
105
+ {
106
+ ...(error.details === undefined ? {} : { details: error.details }),
107
+ },
108
+ );
109
+ }
110
+
111
+ function mapExecutionError(error: unknown): never {
112
+ if (isBetterAuthApiError(error)) throw error;
113
+ if (isAppError(error)) return throwAppError(error);
114
+
115
+ if (!(error instanceof AgentCapabilityError)) {
116
+ throw agentError(
117
+ "INTERNAL_SERVER_ERROR",
118
+ AGENT_AUTH_ERROR_CODES.INTERNAL_ERROR,
119
+ );
120
+ }
121
+
122
+ if (error.code === "execution_failed") {
123
+ if (isBetterAuthApiError(error.cause)) throw error.cause;
124
+ if (isAppError(error.cause)) return throwAppError(error.cause);
125
+ }
126
+
127
+ if (error.code === "unknown_capability") {
128
+ throw agentError(
129
+ "BAD_REQUEST",
130
+ AGENT_AUTH_ERROR_CODES.CAPABILITY_NOT_FOUND,
131
+ );
132
+ }
133
+
134
+ if (error.code === "invalid_input") {
135
+ throw agentError("BAD_REQUEST", AGENT_AUTH_ERROR_CODES.INVALID_REQUEST);
136
+ }
137
+
138
+ throw agentError(
139
+ "INTERNAL_SERVER_ERROR",
140
+ AGENT_AUTH_ERROR_CODES.INTERNAL_ERROR,
141
+ );
142
+ }
143
+
144
+ function isBetterAuthApiError(value: unknown): value is APIError {
145
+ if (value instanceof APIError) return true;
146
+ if (!(value instanceof Error) || value.name !== "APIError") return false;
147
+ if (!("statusCode" in value) || typeof value.statusCode !== "number") {
148
+ return false;
149
+ }
150
+ return "body" in value && (value.body === undefined || value.body !== null);
151
+ }
152
+
153
+ function assertConstrainedFieldsAreStable(args: {
154
+ constraints: Record<string, unknown> | null | undefined;
155
+ constrainedInput: Readonly<Record<string, unknown>> | undefined;
156
+ parsedInput: unknown;
157
+ }): void {
158
+ const constraints = args.constraints;
159
+ if (!constraints) return;
160
+
161
+ const constrainedFields = Object.keys(constraints);
162
+ if (constrainedFields.length === 0) return;
163
+
164
+ const parsedInput =
165
+ args.parsedInput && typeof args.parsedInput === "object"
166
+ ? (args.parsedInput as Record<string, unknown>)
167
+ : undefined;
168
+
169
+ for (const field of constrainedFields) {
170
+ if (
171
+ !args.constrainedInput ||
172
+ !parsedInput ||
173
+ !Object.is(args.constrainedInput[field], parsedInput[field])
174
+ ) {
175
+ throw agentError(
176
+ "FORBIDDEN",
177
+ AGENT_AUTH_ERROR_CODES.CONSTRAINT_VIOLATED,
178
+ `Constrained capability argument "${field}" changed during validation.`,
179
+ );
180
+ }
181
+ }
182
+ }
183
+
184
+ function snapshotConstrainedInput(args: {
185
+ constraints: Record<string, unknown> | null | undefined;
186
+ rawInput: unknown;
187
+ }): Readonly<Record<string, unknown>> | undefined {
188
+ if (!args.constraints) return undefined;
189
+ const rawInput =
190
+ args.rawInput && typeof args.rawInput === "object"
191
+ ? (args.rawInput as Record<string, unknown>)
192
+ : undefined;
193
+ return Object.fromEntries(
194
+ Object.keys(args.constraints).map((field) => [field, rawInput?.[field]]),
195
+ );
196
+ }
197
+
198
+ /**
199
+ * Convert a Beignet capability registry and executor into options accepted by
200
+ * Better Auth's `agentAuth(...)` plugin.
201
+ */
202
+ export function createBetterAuthAgentCapabilityAdapter<
203
+ const Definitions extends readonly AnyAgentCapabilityDef[],
204
+ >(
205
+ options: CreateBetterAuthAgentCapabilityAdapterOptions<Definitions>,
206
+ ): BetterAuthAgentCapabilityAdapter {
207
+ const converter = options.schemaConverter ?? createZodSchemaConverter();
208
+ const capabilities: Capability[] = options.registry.definitions.map(
209
+ (capability) => ({
210
+ name: capability.name,
211
+ description: capability.description,
212
+ input: convertSchema({
213
+ capabilityName: capability.name,
214
+ schema: capability.input,
215
+ io: "input",
216
+ converter,
217
+ }),
218
+ output: convertSchema({
219
+ capabilityName: capability.name,
220
+ schema: capability.output,
221
+ io: "output",
222
+ converter,
223
+ }),
224
+ ...options.metadata?.[capability.name as Definitions[number]["name"]],
225
+ }),
226
+ );
227
+
228
+ return {
229
+ capabilities,
230
+ async onExecute(context) {
231
+ try {
232
+ const rawInput = context.arguments ?? {};
233
+ const constrainedInput = snapshotConstrainedInput({
234
+ constraints: context.grant.constraints,
235
+ rawInput,
236
+ });
237
+ return await options.executor.executeDynamic({
238
+ name: context.capability,
239
+ principal: await options.principal(context),
240
+ input: rawInput,
241
+ authorize({ input }) {
242
+ assertConstrainedFieldsAreStable({
243
+ constraints: context.grant.constraints,
244
+ constrainedInput,
245
+ parsedInput: input,
246
+ });
247
+ },
248
+ });
249
+ } catch (error) {
250
+ return mapExecutionError(error);
251
+ }
252
+ },
253
+ };
254
+ }