@openbrf/plugin-sdk 0.0.0-stage → 1.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/LICENSE +661 -0
- package/LICENSE-EXCEPTION.md +28 -0
- package/README.md +94 -2
- package/dist/abort-signal.d.ts +23 -0
- package/dist/abort-signal.d.ts.map +1 -0
- package/dist/abort-signal.js +2 -0
- package/dist/abort-signal.js.map +1 -0
- package/dist/action-refusal.d.ts +101 -0
- package/dist/action-refusal.d.ts.map +1 -0
- package/dist/action-refusal.js +96 -0
- package/dist/action-refusal.js.map +1 -0
- package/dist/action-schema.d.ts +42 -0
- package/dist/action-schema.d.ts.map +1 -0
- package/dist/action-schema.js +67 -0
- package/dist/action-schema.js.map +1 -0
- package/dist/actions.d.ts +287 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +68 -0
- package/dist/actions.js.map +1 -0
- package/dist/api-version.d.ts +23 -0
- package/dist/api-version.d.ts.map +1 -0
- package/dist/api-version.js +27 -0
- package/dist/api-version.js.map +1 -0
- package/dist/blocks.d.ts +310 -0
- package/dist/blocks.d.ts.map +1 -0
- package/dist/blocks.js +185 -0
- package/dist/blocks.js.map +1 -0
- package/dist/catalog.d.ts +707 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +567 -0
- package/dist/catalog.js.map +1 -0
- package/dist/define-plugin.d.ts +14 -0
- package/dist/define-plugin.d.ts.map +1 -0
- package/dist/define-plugin.js +15 -0
- package/dist/define-plugin.js.map +1 -0
- package/dist/entitlements.d.ts +117 -0
- package/dist/entitlements.d.ts.map +1 -0
- package/dist/entitlements.js +62 -0
- package/dist/entitlements.js.map +1 -0
- package/dist/extensions.d.ts +183 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +67 -0
- package/dist/extensions.js.map +1 -0
- package/dist/finances.d.ts +294 -0
- package/dist/finances.d.ts.map +1 -0
- package/dist/finances.js +56 -0
- package/dist/finances.js.map +1 -0
- package/dist/findings.d.ts +156 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +146 -0
- package/dist/findings.js.map +1 -0
- package/dist/host.d.ts +827 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +80 -0
- package/dist/host.js.map +1 -0
- package/dist/imports.d.ts +104 -0
- package/dist/imports.d.ts.map +1 -0
- package/dist/imports.js +70 -0
- package/dist/imports.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/integrity.d.ts +38 -0
- package/dist/integrity.d.ts.map +1 -0
- package/dist/integrity.js +105 -0
- package/dist/integrity.js.map +1 -0
- package/dist/localized-text.d.ts +18 -0
- package/dist/localized-text.d.ts.map +1 -0
- package/dist/localized-text.js +17 -0
- package/dist/localized-text.js.map +1 -0
- package/dist/manifest.d.ts +1122 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +1118 -0
- package/dist/manifest.js.map +1 -0
- package/dist/module.d.ts +35 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +2 -0
- package/dist/module.js.map +1 -0
- package/dist/package-check.d.ts +66 -0
- package/dist/package-check.d.ts.map +1 -0
- package/dist/package-check.js +755 -0
- package/dist/package-check.js.map +1 -0
- package/dist/permissions.d.ts +224 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +277 -0
- package/dist/permissions.js.map +1 -0
- package/dist/recipients.d.ts +139 -0
- package/dist/recipients.d.ts.map +1 -0
- package/dist/recipients.js +223 -0
- package/dist/recipients.js.map +1 -0
- package/dist/records.d.ts +130 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +85 -0
- package/dist/records.js.map +1 -0
- package/dist/require-capability.d.ts +37 -0
- package/dist/require-capability.d.ts.map +1 -0
- package/dist/require-capability.js +88 -0
- package/dist/require-capability.js.map +1 -0
- package/dist/settings-schema.d.ts +130 -0
- package/dist/settings-schema.d.ts.map +1 -0
- package/dist/settings-schema.js +272 -0
- package/dist/settings-schema.js.map +1 -0
- package/dist/standard-schema.d.ts +43 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +2 -0
- package/dist/standard-schema.js.map +1 -0
- package/package.json +55 -3
- package/src/abort-signal.ts +22 -0
- package/src/action-refusal.ts +178 -0
- package/src/action-schema.ts +89 -0
- package/src/actions.ts +344 -0
- package/src/api-version.ts +28 -0
- package/src/blocks.ts +289 -0
- package/src/catalog.ts +679 -0
- package/src/define-plugin.ts +18 -0
- package/src/entitlements.ts +150 -0
- package/src/extensions.ts +212 -0
- package/src/finances.ts +317 -0
- package/src/findings.ts +163 -0
- package/src/host.ts +894 -0
- package/src/imports.ts +135 -0
- package/src/index.ts +316 -0
- package/src/integrity.ts +125 -0
- package/src/localized-text.ts +19 -0
- package/src/manifest.ts +1412 -0
- package/src/module.ts +38 -0
- package/src/package-check.ts +894 -0
- package/src/permissions.ts +295 -0
- package/src/recipients.ts +294 -0
- package/src/records.ts +172 -0
- package/src/require-capability.ts +106 -0
- package/src/settings-schema.ts +334 -0
- package/src/standard-schema.ts +39 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Open BRF Module Exception
|
|
2
|
+
|
|
3
|
+
Version 1.0
|
|
4
|
+
|
|
5
|
+
## The exception
|
|
6
|
+
|
|
7
|
+
As an additional permission under section 7 of the GNU Affero General Public License, version 3 ("AGPL-3.0"), the copyright holders of Open BRF give you permission to combine Open BRF with modules.
|
|
8
|
+
|
|
9
|
+
A "module" is a plugin, theme, or other extension package that interacts with the Open BRF core exclusively through the documented public extension interfaces: the plugin API, the theme/token contract, the public HTTP API, and the extension points declared in the module manifest format (together, the "Module API").
|
|
10
|
+
|
|
11
|
+
If you create and convey a module:
|
|
12
|
+
|
|
13
|
+
1. The module is **not** considered a work based on Open BRF for the purposes of AGPL-3.0, regardless of the linking or loading mechanism.
|
|
14
|
+
2. You may license the module under terms of your own choosing, including proprietary terms; nothing requires you to convey the module's source code.
|
|
15
|
+
3. Running a combined work consisting of an unmodified Open BRF core and one or more modules, including offering it to users over a network, does not extend the obligations of AGPL-3.0 (including section 13) to the modules.
|
|
16
|
+
|
|
17
|
+
This exception does **not** apply to:
|
|
18
|
+
|
|
19
|
+
- Modifications of the Open BRF core itself, which remain governed by AGPL-3.0 in full. A "module" that patches, replaces, or bundles modified core code is a modification of the core, not a module.
|
|
20
|
+
- The obligations of AGPL-3.0 regarding the core: anyone conveying or network-hosting a modified core must still offer its Corresponding Source.
|
|
21
|
+
|
|
22
|
+
As provided by section 7 of AGPL-3.0, you may remove this additional permission from your copy, but you may not add further restrictions.
|
|
23
|
+
|
|
24
|
+
## Why this exists
|
|
25
|
+
|
|
26
|
+
The legal status of in-process plugins to (A)GPL software is a decades-old gray zone. Rather than relying on it, Open BRF states the rule explicitly, in both directions: the core is and remains copyleft, and the module ecosystem - community and commercial alike - is legally safe to build in. Vendure pioneered this model for GPL; Open BRF applies it to AGPL.
|
|
27
|
+
|
|
28
|
+
This exception is granted by Apteo AB as copyright holder of Open BRF. Contributions are accepted under a [CLA](CLA.md) precisely so that this grant remains valid for the whole codebase.
|
package/README.md
CHANGED
|
@@ -1,3 +1,95 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @openbrf/plugin-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The plugin contract of [Open BRF](https://github.com/openbrf/openbrf): the
|
|
4
|
+
manifest schema, the permission set, the interfaces of the services the host
|
|
5
|
+
hands a plugin, and the schema of the catalog that lists plugins and themes.
|
|
6
|
+
|
|
7
|
+
The major version is the plugin API version. Anything a plugin could notice
|
|
8
|
+
raises it, and an additive change keeps it. 1.x is API version 1.
|
|
9
|
+
|
|
10
|
+
## Installing
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install --save-dev @openbrf/plugin-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
A development dependency, never a dependency. An instance installs a plugin
|
|
17
|
+
offline from its verified tarball and refuses a `package.json` that declares
|
|
18
|
+
any `dependencies`. Nothing in this package has to resolve at runtime: the host
|
|
19
|
+
passes everything a plugin uses in through `PluginHost`, so a plugin imports
|
|
20
|
+
the SDK for types and for the checks its own CI runs.
|
|
21
|
+
|
|
22
|
+
`@nestjs/common` 12 is a peer dependency. A plugin declares `@nestjs/common`,
|
|
23
|
+
`@nestjs/core` and `zod` as peer dependencies too, and never bundles them; the
|
|
24
|
+
host supplies the running copies.
|
|
25
|
+
|
|
26
|
+
## The server entry
|
|
27
|
+
|
|
28
|
+
A prebuilt CommonJS bundle that exports `createPlugin`. It receives the host
|
|
29
|
+
object and returns a NestJS module, whose controllers the host mounts under
|
|
30
|
+
`/api/plugin/<id>/`, inside the application's authorization guard.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Controller, Get, Injectable, Module } from "@nestjs/common";
|
|
34
|
+
import type { PluginModuleFactory } from "@openbrf/plugin-sdk";
|
|
35
|
+
|
|
36
|
+
export const createPlugin: PluginModuleFactory = (host) => {
|
|
37
|
+
@Injectable()
|
|
38
|
+
class OccupancyService {
|
|
39
|
+
async summary() {
|
|
40
|
+
return host.addressBook.summary();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Controller()
|
|
45
|
+
class OccupancyController {
|
|
46
|
+
constructor(private readonly service: OccupancyService) {}
|
|
47
|
+
|
|
48
|
+
@Get("summary")
|
|
49
|
+
async summary() {
|
|
50
|
+
return this.service.summary();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Module({})
|
|
55
|
+
class OccupancyModule {}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
module: OccupancyModule,
|
|
59
|
+
controllers: [OccupancyController],
|
|
60
|
+
providers: [OccupancyService],
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Checking a plugin before it is published
|
|
66
|
+
|
|
67
|
+
The checks an instance runs at install and at boot are exported, so a
|
|
68
|
+
plugin's CI refuses what an instance would refuse:
|
|
69
|
+
|
|
70
|
+
- `assertPluginPackage(packageJson)` throws with every problem in the
|
|
71
|
+
manifest at once; `parsePluginPackage` returns them instead.
|
|
72
|
+
- `pluginPackageProblems(contents)` takes the parsed `package.json`, the file
|
|
73
|
+
list, the server bundle's source and the two locale files, and returns every
|
|
74
|
+
problem that would stop the package installing or loading, one sentence
|
|
75
|
+
each.
|
|
76
|
+
- `catalogEntrySchema` is the schema of the entry a listing adds to the
|
|
77
|
+
catalog.
|
|
78
|
+
|
|
79
|
+
## Documentation
|
|
80
|
+
|
|
81
|
+
- [The plugin contract](https://github.com/openbrf/openbrf/blob/main/docs/plugin-contract.md):
|
|
82
|
+
the manifest, permissions, personal data, the server and client entries,
|
|
83
|
+
settings, translations, distribution and installation.
|
|
84
|
+
- [ADR 0003](https://github.com/openbrf/openbrf/blob/main/docs/adr/0003-plugin-loading-and-module-resolution.md):
|
|
85
|
+
how a plugin is loaded and how it resolves the host's packages.
|
|
86
|
+
- [ADR 0020](https://github.com/openbrf/openbrf/blob/main/docs/adr/0020-the-public-catalog-and-the-published-packages.md):
|
|
87
|
+
the public catalog and the published packages.
|
|
88
|
+
|
|
89
|
+
## Licence
|
|
90
|
+
|
|
91
|
+
AGPL-3.0-only, with the
|
|
92
|
+
[Open BRF Module Exception](https://github.com/openbrf/openbrf/blob/main/LICENSE-EXCEPTION.md).
|
|
93
|
+
A plugin that reaches the core only through the documented extension
|
|
94
|
+
interfaces is a module under that exception and may be licensed on any terms.
|
|
95
|
+
Both texts are in the package.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `AbortSignal` the host hands a plugin, and takes from one.
|
|
3
|
+
*
|
|
4
|
+
* The host passes a real `AbortSignal`. This package compiles against the
|
|
5
|
+
* language alone and cannot name the platform's type, so it is looked up in
|
|
6
|
+
* the plugin's own program: where that has an `AbortSignal`, from the DOM
|
|
7
|
+
* library or from Node's types, this is it, and the signal can be passed on to
|
|
8
|
+
* `fetch` and to anything else that takes one. Where it has none, this is the
|
|
9
|
+
* part of one a plugin can use without them.
|
|
10
|
+
*/
|
|
11
|
+
export type PluginAbortSignal = typeof globalThis extends {
|
|
12
|
+
readonly AbortSignal: {
|
|
13
|
+
readonly prototype: infer Signal;
|
|
14
|
+
};
|
|
15
|
+
} ? Signal : PluginAbortSignalShape;
|
|
16
|
+
/** What every `AbortSignal` has, for a program without the platform's types. */
|
|
17
|
+
interface PluginAbortSignalShape {
|
|
18
|
+
readonly aborted: boolean;
|
|
19
|
+
addEventListener(type: "abort", listener: () => void): void;
|
|
20
|
+
removeEventListener(type: "abort", listener: () => void): void;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=abort-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-signal.d.ts","sourceRoot":"","sources":["../src/abort-signal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,UAAU,SAAS;IACxD,QAAQ,CAAC,WAAW,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,MAAM,CAAA;KAAE,CAAC;CAC5D,GACG,MAAM,GACN,sBAAsB,CAAC;AAE3B,gFAAgF;AAChF,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC5D,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAChE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-signal.js","sourceRoot":"","sources":["../src/abort-signal.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ActionRetry } from "./actions.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Why the registry refused a call, in a form a caller can act on.
|
|
4
|
+
*
|
|
5
|
+
* A caller here is often a model rather than a person, which changes what a
|
|
6
|
+
* refusal has to carry. A person reads a sentence and decides what to do; a
|
|
7
|
+
* model needs to know whether trying again could ever work, and what to change
|
|
8
|
+
* if it could. So every refusal has a code, a status, and a published rule for
|
|
9
|
+
* how a connector should render it.
|
|
10
|
+
*
|
|
11
|
+
* The vocabulary lives here rather than in the API because a plugin dispatching
|
|
12
|
+
* through `host.actions.invoke` has no other way to read it: the catalogue
|
|
13
|
+
* endpoint serves the same table over HTTP, and a plugin in the same process
|
|
14
|
+
* has no route to that. The API imports it back, so there is one list.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ACTION_ERROR_REASONS: readonly ["unknown-action", "forbidden-capability", "forbidden-surface", "insufficient-scope", "not-serving", "read-only", "confirmation-required", "output-invalid", "rate-limited", "internal"];
|
|
17
|
+
export type ActionErrorReason = (typeof ACTION_ERROR_REASONS)[number];
|
|
18
|
+
/**
|
|
19
|
+
* How a connector should tell its caller about a refusal.
|
|
20
|
+
*
|
|
21
|
+
* A protocol error means the call never happened; a tool result means it
|
|
22
|
+
* happened and was refused, and only the second is something to tell the
|
|
23
|
+
* person about.
|
|
24
|
+
*/
|
|
25
|
+
export type ActionRefusalTransport = "protocol" | "tool-result";
|
|
26
|
+
/**
|
|
27
|
+
* How a connector should render each refusal, published as data.
|
|
28
|
+
*
|
|
29
|
+
* Served on the catalogue endpoint as well, so that two connectors - one
|
|
30
|
+
* reading it over HTTP, one in process - cannot disagree about whether a 403
|
|
31
|
+
* is worth retrying. Keyed by the registry's reasons and by the two refusals
|
|
32
|
+
* that are not the registry's own: a body the action's input schema refuses,
|
|
33
|
+
* and a refusal raised by the service behind the action.
|
|
34
|
+
*/
|
|
35
|
+
export declare const ACTION_ERROR_MCP: Readonly<Record<ActionErrorReason | "invalid-body" | "domain", {
|
|
36
|
+
readonly transport: ActionRefusalTransport;
|
|
37
|
+
readonly retry: ActionRetry;
|
|
38
|
+
}>>;
|
|
39
|
+
/**
|
|
40
|
+
* Which of the three ways a call was refused.
|
|
41
|
+
*
|
|
42
|
+
* "registry" is the registry's own refusal, and `reason` is then one of
|
|
43
|
+
* ACTION_ERROR_REASONS. "invalid-body" is an input the action's schema
|
|
44
|
+
* refused, and `reason` is "invalid-body". "domain" is the service behind the
|
|
45
|
+
* action refusing on the merits, and `reason` is one the action declared in
|
|
46
|
+
* its `errors`.
|
|
47
|
+
*/
|
|
48
|
+
export type ActionRefusalKind = "registry" | "invalid-body" | "domain";
|
|
49
|
+
/**
|
|
50
|
+
* What `host.actions.invoke` rejects with when the call was refused.
|
|
51
|
+
*
|
|
52
|
+
* One shape for all three kinds, so that a connector renders a refusal from
|
|
53
|
+
* the fields it carries rather than by recognising the host's classes, which a
|
|
54
|
+
* plugin cannot import. Recognise one with `isActionRefusal`, not with
|
|
55
|
+
* `instanceof`: the host builds it, and a plugin's bundled copy of this
|
|
56
|
+
* package is not the host's.
|
|
57
|
+
*
|
|
58
|
+
* A plugin route that rethrows one answers its caller with the same status,
|
|
59
|
+
* body and headers the registry would have.
|
|
60
|
+
*/
|
|
61
|
+
export interface ActionRefusal extends Error {
|
|
62
|
+
readonly name: "ActionRefusal";
|
|
63
|
+
readonly kind: ActionRefusalKind;
|
|
64
|
+
readonly reason: string;
|
|
65
|
+
/** The HTTP status the refusal answers with. */
|
|
66
|
+
readonly status: number;
|
|
67
|
+
readonly transport: ActionRefusalTransport;
|
|
68
|
+
/**
|
|
69
|
+
* From ACTION_ERROR_MCP for the registry's refusals and an invalid body. For
|
|
70
|
+
* a domain refusal, the verdict the action declared for that reason in its
|
|
71
|
+
* `errors`, or "never" for a reason it did not declare.
|
|
72
|
+
*/
|
|
73
|
+
readonly retry: ActionRetry;
|
|
74
|
+
/**
|
|
75
|
+
* The particulars the refusal publishes, keyed by the field they travel in.
|
|
76
|
+
*
|
|
77
|
+
* `scopes` on an insufficient-scope refusal names the scope the call needed.
|
|
78
|
+
* `issues` on an invalid body lists `{ path, message }` for each failing
|
|
79
|
+
* field, and never the submitted value.
|
|
80
|
+
*/
|
|
81
|
+
details(): Record<string, readonly unknown[]>;
|
|
82
|
+
/**
|
|
83
|
+
* The bearer challenge to answer a connected app with, as the value of a
|
|
84
|
+
* `WWW-Authenticate` header (RFC 6750, ADR 0009).
|
|
85
|
+
*
|
|
86
|
+
* Set on an insufficient-scope refusal, where it names every scope the
|
|
87
|
+
* instance issues so the client can ask for what it is missing in one round
|
|
88
|
+
* trip. Absent wherever no challenge would help.
|
|
89
|
+
*/
|
|
90
|
+
readonly challenge?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Whether a value `host.actions.invoke` rejected with is a refusal.
|
|
94
|
+
*
|
|
95
|
+
* Structural rather than `instanceof`, for the reason on ActionRefusal. False
|
|
96
|
+
* means the call failed rather than being refused - the host could not be
|
|
97
|
+
* reached, or something broke - and a connector answers that as an internal
|
|
98
|
+
* error without inspecting it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function isActionRefusal(value: unknown): value is ActionRefusal;
|
|
101
|
+
//# sourceMappingURL=action-refusal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-refusal.d.ts","sourceRoot":"","sources":["../src/action-refusal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,oBAAoB,YAC/B,gBAAgB,EAChB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,WAAW,EACX,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,EACd,UAAU,CACF,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,aAAa,CAAC;AAEhE;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CACrC,MAAM,CACJ,iBAAiB,GAAG,cAAc,GAAG,QAAQ,EAC7C;IACE,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;CAC7B,CACF,CAcF,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,cAAc,GAAG,QAAQ,CAAC;AAEvE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B;;;;;;OAMG;IACH,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC;IAC9C;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAkBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAyBtE"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the registry refused a call, in a form a caller can act on.
|
|
3
|
+
*
|
|
4
|
+
* A caller here is often a model rather than a person, which changes what a
|
|
5
|
+
* refusal has to carry. A person reads a sentence and decides what to do; a
|
|
6
|
+
* model needs to know whether trying again could ever work, and what to change
|
|
7
|
+
* if it could. So every refusal has a code, a status, and a published rule for
|
|
8
|
+
* how a connector should render it.
|
|
9
|
+
*
|
|
10
|
+
* The vocabulary lives here rather than in the API because a plugin dispatching
|
|
11
|
+
* through `host.actions.invoke` has no other way to read it: the catalogue
|
|
12
|
+
* endpoint serves the same table over HTTP, and a plugin in the same process
|
|
13
|
+
* has no route to that. The API imports it back, so there is one list.
|
|
14
|
+
*/
|
|
15
|
+
export const ACTION_ERROR_REASONS = [
|
|
16
|
+
"unknown-action",
|
|
17
|
+
"forbidden-capability",
|
|
18
|
+
"forbidden-surface",
|
|
19
|
+
"insufficient-scope",
|
|
20
|
+
"not-serving",
|
|
21
|
+
"read-only",
|
|
22
|
+
"confirmation-required",
|
|
23
|
+
"output-invalid",
|
|
24
|
+
"rate-limited",
|
|
25
|
+
"internal",
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* How a connector should render each refusal, published as data.
|
|
29
|
+
*
|
|
30
|
+
* Served on the catalogue endpoint as well, so that two connectors - one
|
|
31
|
+
* reading it over HTTP, one in process - cannot disagree about whether a 403
|
|
32
|
+
* is worth retrying. Keyed by the registry's reasons and by the two refusals
|
|
33
|
+
* that are not the registry's own: a body the action's input schema refuses,
|
|
34
|
+
* and a refusal raised by the service behind the action.
|
|
35
|
+
*/
|
|
36
|
+
export const ACTION_ERROR_MCP = {
|
|
37
|
+
"unknown-action": { transport: "protocol", retry: "never" },
|
|
38
|
+
"invalid-body": { transport: "tool-result", retry: "after-edit" },
|
|
39
|
+
"forbidden-capability": { transport: "tool-result", retry: "never" },
|
|
40
|
+
"forbidden-surface": { transport: "tool-result", retry: "never" },
|
|
41
|
+
"insufficient-scope": { transport: "tool-result", retry: "never" },
|
|
42
|
+
"not-serving": { transport: "tool-result", retry: "never" },
|
|
43
|
+
"read-only": { transport: "tool-result", retry: "after-backoff" },
|
|
44
|
+
"confirmation-required": { transport: "tool-result", retry: "never" },
|
|
45
|
+
"output-invalid": { transport: "protocol", retry: "never" },
|
|
46
|
+
"rate-limited": { transport: "tool-result", retry: "after-backoff" },
|
|
47
|
+
internal: { transport: "protocol", retry: "after-backoff" },
|
|
48
|
+
domain: { transport: "tool-result", retry: "never" },
|
|
49
|
+
};
|
|
50
|
+
const KINDS = [
|
|
51
|
+
"registry",
|
|
52
|
+
"invalid-body",
|
|
53
|
+
"domain",
|
|
54
|
+
];
|
|
55
|
+
const TRANSPORTS = [
|
|
56
|
+
"protocol",
|
|
57
|
+
"tool-result",
|
|
58
|
+
];
|
|
59
|
+
const RETRIES = [
|
|
60
|
+
"never",
|
|
61
|
+
"after-edit",
|
|
62
|
+
"after-backoff",
|
|
63
|
+
];
|
|
64
|
+
const REGISTRY_REASONS = ACTION_ERROR_REASONS;
|
|
65
|
+
/**
|
|
66
|
+
* Whether a value `host.actions.invoke` rejected with is a refusal.
|
|
67
|
+
*
|
|
68
|
+
* Structural rather than `instanceof`, for the reason on ActionRefusal. False
|
|
69
|
+
* means the call failed rather than being refused - the host could not be
|
|
70
|
+
* reached, or something broke - and a connector answers that as an internal
|
|
71
|
+
* error without inspecting it.
|
|
72
|
+
*/
|
|
73
|
+
export function isActionRefusal(value) {
|
|
74
|
+
if (!(value instanceof Error) || value.name !== "ActionRefusal") {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const refusal = value;
|
|
78
|
+
if (typeof refusal.reason !== "string" ||
|
|
79
|
+
typeof refusal.status !== "number" ||
|
|
80
|
+
typeof refusal.details !== "function" ||
|
|
81
|
+
!KINDS.includes(refusal.kind) ||
|
|
82
|
+
!TRANSPORTS.includes(refusal.transport) ||
|
|
83
|
+
!RETRIES.includes(refusal.retry) ||
|
|
84
|
+
(refusal.challenge !== undefined && typeof refusal.challenge !== "string")) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
switch (refusal.kind) {
|
|
88
|
+
case "registry":
|
|
89
|
+
return REGISTRY_REASONS.includes(refusal.reason);
|
|
90
|
+
case "invalid-body":
|
|
91
|
+
return refusal.reason === "invalid-body";
|
|
92
|
+
default:
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=action-refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-refusal.js","sourceRoot":"","sources":["../src/action-refusal.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,gBAAgB;IAChB,sBAAsB;IACtB,mBAAmB;IACnB,oBAAoB;IACpB,aAAa;IACb,WAAW;IACX,uBAAuB;IACvB,gBAAgB;IAChB,cAAc;IACd,UAAU;CACF,CAAC;AAaX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAQzB;IACF,gBAAgB,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE;IAC3D,cAAc,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE;IACjE,sBAAsB,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE;IACpE,mBAAmB,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE;IACjE,oBAAoB,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE;IAClE,aAAa,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE;IAC3D,WAAW,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE;IACjE,uBAAuB,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE;IACrE,gBAAgB,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE;IAC3D,cAAc,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE;IACpE,QAAQ,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,EAAE;IAC3D,MAAM,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE;CACrD,CAAC;AAyDF,MAAM,KAAK,GAAuB;IAChC,UAAU;IACV,cAAc;IACd,QAAQ;CACqB,CAAC;AAChC,MAAM,UAAU,GAAuB;IACrC,UAAU;IACV,aAAa;CACqB,CAAC;AACrC,MAAM,OAAO,GAAuB;IAClC,OAAO;IACP,YAAY;IACZ,eAAe;CACQ,CAAC;AAC1B,MAAM,gBAAgB,GAAuB,oBAAoB,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QAChE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAG,KAC+B,CAAC;IAChD,IACE,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ;QAClC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ;QAClC,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU;QACrC,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;QAC7B,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;QACvC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;QAChC,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,EAC1E,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,UAAU;YACb,OAAO,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACnD,KAAK,cAAc;YACjB,OAAO,OAAO,CAAC,MAAM,KAAK,cAAc,CAAC;QAC3C;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { StandardSchemaV1 } from "./standard-schema.ts";
|
|
3
|
+
/**
|
|
4
|
+
* An action's schemas, as the JSON Schema a caller is published.
|
|
5
|
+
*
|
|
6
|
+
* The only call to `z.toJSONSchema` in the product. Every option here is set
|
|
7
|
+
* against a failure that would otherwise be silent, so they are worth reading
|
|
8
|
+
* one at a time.
|
|
9
|
+
*
|
|
10
|
+
* `io: "input"` because output mode makes a key with a default REQUIRED, and a
|
|
11
|
+
* model reading that will dutifully supply a value for every field the platform
|
|
12
|
+
* was perfectly happy to choose itself.
|
|
13
|
+
*
|
|
14
|
+
* `cycles: "throw"` because a self-referential schema has no JSON Schema
|
|
15
|
+
* without `$ref`, and a silently emitted `$ref` to a positional name is worse
|
|
16
|
+
* than a refusal at registration.
|
|
17
|
+
*
|
|
18
|
+
* `reused: "inline"` because `$defs` invents names from the position a schema
|
|
19
|
+
* happens to hold, and those names would reach a committed artefact and a
|
|
20
|
+
* third-party client. Inlining costs bytes and keeps the document stable.
|
|
21
|
+
*
|
|
22
|
+
* The function form of `unrepresentable` because the default error names
|
|
23
|
+
* neither the action nor the path, and a build failing with "unrepresentable
|
|
24
|
+
* type" and nothing else is a half hour of searching.
|
|
25
|
+
*
|
|
26
|
+
* One property is worth stating because it is NOT what one would assume: a bare
|
|
27
|
+
* `.transform()` does not throw under input mode - it is erased and the
|
|
28
|
+
* pre-transform type is emitted. The guarantee is placed where it is real, on
|
|
29
|
+
* the output helper, which does throw. So an action whose pair is converted at
|
|
30
|
+
* registration cannot carry a transform anywhere.
|
|
31
|
+
*
|
|
32
|
+
* `.refine()`, `.check()`, `.superRefine()` and `.brand()` are erased with no
|
|
33
|
+
* marker under both modes. That is why the rule for a published schema is
|
|
34
|
+
* structural constraints first: what the document says has to be at least as
|
|
35
|
+
* strict as what the runtime enforces, or a caller is told a value is
|
|
36
|
+
* acceptable that the service will refuse.
|
|
37
|
+
*/
|
|
38
|
+
/** Refuses a schema built with a second copy of zod, whose internals differ. */
|
|
39
|
+
export declare function isHostZodSchema(schema: unknown): schema is z.ZodType;
|
|
40
|
+
export declare function actionInputJsonSchema(schema: StandardSchemaV1<unknown, unknown>, actionName: string): Record<string, unknown>;
|
|
41
|
+
export declare function actionOutputJsonSchema(schema: StandardSchemaV1<unknown, unknown>, actionName: string): Record<string, unknown>;
|
|
42
|
+
//# sourceMappingURL=action-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-schema.d.ts","sourceRoot":"","sources":["../src/action-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC,OAAO,CAOpE;AA4BD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAC1C,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAC1C,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* An action's schemas, as the JSON Schema a caller is published.
|
|
4
|
+
*
|
|
5
|
+
* The only call to `z.toJSONSchema` in the product. Every option here is set
|
|
6
|
+
* against a failure that would otherwise be silent, so they are worth reading
|
|
7
|
+
* one at a time.
|
|
8
|
+
*
|
|
9
|
+
* `io: "input"` because output mode makes a key with a default REQUIRED, and a
|
|
10
|
+
* model reading that will dutifully supply a value for every field the platform
|
|
11
|
+
* was perfectly happy to choose itself.
|
|
12
|
+
*
|
|
13
|
+
* `cycles: "throw"` because a self-referential schema has no JSON Schema
|
|
14
|
+
* without `$ref`, and a silently emitted `$ref` to a positional name is worse
|
|
15
|
+
* than a refusal at registration.
|
|
16
|
+
*
|
|
17
|
+
* `reused: "inline"` because `$defs` invents names from the position a schema
|
|
18
|
+
* happens to hold, and those names would reach a committed artefact and a
|
|
19
|
+
* third-party client. Inlining costs bytes and keeps the document stable.
|
|
20
|
+
*
|
|
21
|
+
* The function form of `unrepresentable` because the default error names
|
|
22
|
+
* neither the action nor the path, and a build failing with "unrepresentable
|
|
23
|
+
* type" and nothing else is a half hour of searching.
|
|
24
|
+
*
|
|
25
|
+
* One property is worth stating because it is NOT what one would assume: a bare
|
|
26
|
+
* `.transform()` does not throw under input mode - it is erased and the
|
|
27
|
+
* pre-transform type is emitted. The guarantee is placed where it is real, on
|
|
28
|
+
* the output helper, which does throw. So an action whose pair is converted at
|
|
29
|
+
* registration cannot carry a transform anywhere.
|
|
30
|
+
*
|
|
31
|
+
* `.refine()`, `.check()`, `.superRefine()` and `.brand()` are erased with no
|
|
32
|
+
* marker under both modes. That is why the rule for a published schema is
|
|
33
|
+
* structural constraints first: what the document says has to be at least as
|
|
34
|
+
* strict as what the runtime enforces, or a caller is told a value is
|
|
35
|
+
* acceptable that the service will refuse.
|
|
36
|
+
*/
|
|
37
|
+
/** Refuses a schema built with a second copy of zod, whose internals differ. */
|
|
38
|
+
export function isHostZodSchema(schema) {
|
|
39
|
+
return (typeof schema === "object" &&
|
|
40
|
+
schema !== null &&
|
|
41
|
+
"_zod" in schema &&
|
|
42
|
+
schema._zod !== undefined);
|
|
43
|
+
}
|
|
44
|
+
function convert(schema, actionName, io) {
|
|
45
|
+
if (!isHostZodSchema(schema)) {
|
|
46
|
+
throw new Error(`${actionName}: an action schema must be built with the host's zod.`);
|
|
47
|
+
}
|
|
48
|
+
const document = z.toJSONSchema(schema, {
|
|
49
|
+
target: "draft-2020-12",
|
|
50
|
+
io,
|
|
51
|
+
cycles: "throw",
|
|
52
|
+
reused: "inline",
|
|
53
|
+
unrepresentable: ({ path, message }) => {
|
|
54
|
+
throw new Error(`${actionName}: ${message} at ${[io, ...path.map(String)].join(".")}`);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
// zod hangs a non-enumerable `~standard` on the root. A shallow copy leaves
|
|
58
|
+
// it behind, so what a caller is handed is a plain JSON document.
|
|
59
|
+
return { ...document };
|
|
60
|
+
}
|
|
61
|
+
export function actionInputJsonSchema(schema, actionName) {
|
|
62
|
+
return convert(schema, actionName, "input");
|
|
63
|
+
}
|
|
64
|
+
export function actionOutputJsonSchema(schema, actionName) {
|
|
65
|
+
return convert(schema, actionName, "output");
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=action-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-schema.js","sourceRoot":"","sources":["../src/action-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,MAAM,IAAI,MAAM;QACf,MAA6B,CAAC,IAAI,KAAK,SAAS,CAClD,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CACd,MAAe,EACf,UAAkB,EAClB,EAAsB;IAEtB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,uDAAuD,CACrE,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE;QACtC,MAAM,EAAE,eAAe;QACvB,EAAE;QACF,MAAM,EAAE,OAAO;QACf,MAAM,EAAE,QAAQ;QAChB,eAAe,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YACrC,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,KAAK,OAAO,OAAO,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACtE,CAAC;QACJ,CAAC;KACF,CAA4B,CAAC;IAC9B,4EAA4E;IAC5E,kEAAkE;IAClE,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAA0C,EAC1C,UAAkB;IAElB,OAAO,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,MAA0C,EAC1C,UAAkB;IAElB,OAAO,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC"}
|