@capybara-hq/plugin-sdk 0.1.0-rc.1 → 0.1.0-rc.3
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 +1 -1
- package/agent-docs/agent-templates.md +47 -0
- package/agent-docs/concepts.md +58 -0
- package/agent-docs/connector-templates.md +47 -0
- package/agent-docs/extension-points/channel.md +15 -0
- package/agent-docs/extension-points/config-source.md +15 -0
- package/agent-docs/extension-points/connector.md +15 -0
- package/agent-docs/extension-points/counter.md +15 -0
- package/agent-docs/extension-points/directory.md +15 -0
- package/agent-docs/extension-points/file-preview.md +15 -0
- package/agent-docs/extension-points/identity-source.md +15 -0
- package/agent-docs/extension-points/model-provider.md +15 -0
- package/agent-docs/extension-points/object-store.md +15 -0
- package/agent-docs/extension-points/sandbox.md +15 -0
- package/agent-docs/extension-points/secret-store.md +15 -0
- package/agent-docs/extension-points/web-fetch.md +15 -0
- package/agent-docs/extension-points/web-search.md +15 -0
- package/agent-docs/extensions.md +63 -0
- package/agent-docs/plugin-format.md +60 -0
- package/agent-docs/skills.md +44 -0
- package/agent-docs/tool-naming.md +21 -0
- package/dist/agent-template.d.ts +85 -0
- package/dist/agent-template.js +361 -2
- package/dist/config-ui.d.ts +131 -0
- package/dist/config-ui.js +602 -0
- package/dist/ctx.d.ts +3 -1
- package/dist/directory.d.ts +11 -2
- package/dist/extension-point.d.ts +9 -0
- package/dist/extension-point.js +17 -12
- package/dist/file-preview.d.ts +8 -0
- package/dist/hooks.d.ts +14 -4
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/marketplace.d.ts +29 -2
- package/dist/marketplace.js +30 -2
- package/dist/org-access.d.ts +27 -0
- package/dist/org-access.js +35 -0
- package/dist/plugin-manifest.d.ts +25 -2
- package/dist/plugin-manifest.js +20 -1
- package/dist/sandbox.d.ts +140 -60
- package/dist/sandbox.js +30 -6
- package/dist/secret.d.ts +4 -1
- package/dist/secret.js +5 -2
- package/dist/skill-invocation.d.ts +8 -5
- package/dist/skill-invocation.js +17 -8
- package/dist/skill.d.ts +20 -3
- package/dist/skill.js +28 -6
- package/dist/tool.d.ts +8 -0
- package/dist/ui.d.ts +8 -0
- package/dist/web.d.ts +105 -0
- package/dist/web.js +135 -0
- package/package.json +4 -2
- package/schemas/marketplace.v1.schema.json +36 -0
- package/schemas/plugin.v1.schema.json +49 -4
package/dist/extension-point.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
const info = (cardinality, brokered = false, orgScoped = false) => ({
|
|
1
|
+
const info = (majors, description, cardinality, brokered = false, orgScoped = false, selection) => ({
|
|
2
|
+
majors,
|
|
3
|
+
description,
|
|
2
4
|
cardinality,
|
|
3
5
|
brokered,
|
|
4
6
|
orgScoped,
|
|
7
|
+
...(selection ? { selection } : {}),
|
|
5
8
|
});
|
|
6
9
|
/** The registered extension points. */
|
|
7
10
|
export const EXTENSION_POINTS = {
|
|
8
|
-
sandbox: info("single", true),
|
|
9
|
-
"config-source": info("single"),
|
|
10
|
-
"secret-store": info("single", true),
|
|
11
|
-
"object-store": info("single", true),
|
|
12
|
-
channel: info("multi", false, true),
|
|
13
|
-
"model-provider": info("multi", true),
|
|
14
|
-
"identity-source": info("multi"),
|
|
15
|
-
directory: info("multi", false, true),
|
|
16
|
-
counter: info("multi"),
|
|
17
|
-
connector: info("multi", true),
|
|
18
|
-
"file-preview": info("multi", false, true),
|
|
11
|
+
sandbox: info([2], "Runs an environment's instance and keeps its files for agents' commands (Docker, E2B).", "single", true),
|
|
12
|
+
"config-source": info([1], "Supplies deployment configuration from an external store such as Nacos.", "single"),
|
|
13
|
+
"secret-store": info([1], "Stores and resolves secrets (connector credentials, secret config) outside the database.", "single", true),
|
|
14
|
+
"object-store": info([1], "Stores files and artifacts in S3-compatible object storage, with presigned uploads.", "single", true),
|
|
15
|
+
channel: info([1], "Connects an instant-messaging platform (Slack, Lark): inbound events, replies and cards.", "multi", false, true),
|
|
16
|
+
"model-provider": info([1, 2], "Calls a model vendor's API and streams the result back to the runtime.", "multi", true),
|
|
17
|
+
"identity-source": info([1], "Signs people in through an external identity provider (SSO).", "multi"),
|
|
18
|
+
directory: info([1], "Syncs members and departments from an external company directory.", "multi", false, true),
|
|
19
|
+
counter: info([1], "Counts usage so quotas and reports can be built on it.", "multi"),
|
|
20
|
+
connector: info([1], "Speaks one protocol (MCP, OpenAPI) to external systems and exposes their tools to agents.", "multi", true),
|
|
21
|
+
"file-preview": info([1], "Renders previews of files of given types in the conversation UI.", "multi", false, true),
|
|
22
|
+
"web-search": info([1], "Searches the web for the WebSearch tool; each org picks a default provider.", "multi", true, true, "org-default"),
|
|
23
|
+
"web-fetch": info([1], "Fetches and extracts web pages for the WebFetch tool; each org picks a default provider.", "multi", true, true, "org-default"),
|
|
19
24
|
};
|
|
20
25
|
/** Cardinality of the registered extension points (derived from EXTENSION_POINTS). */
|
|
21
26
|
export const EXTENSION_POINT_CARDINALITY = Object.fromEntries(Object.entries(EXTENSION_POINTS).map(([k, v]) => [k, v.cardinality]));
|
package/dist/file-preview.d.ts
CHANGED
|
@@ -53,6 +53,14 @@ export interface FilePreviewArgs {
|
|
|
53
53
|
* rejects a file over the previewer's `maxBytes`. The extension never gets a storage key or URL.
|
|
54
54
|
*/
|
|
55
55
|
read(): Promise<Uint8Array>;
|
|
56
|
+
/**
|
|
57
|
+
* The extension's effective deployment config for this org: the platform's deployment config
|
|
58
|
+
* with the org's copy applied to the overridable fields (`orgAccess(…, "override")`), or the
|
|
59
|
+
* platform's config (equal to `ctx.config`) while the org follows it. Use it, not `ctx.config`,
|
|
60
|
+
* for work done for this org. Optional in the type so older extensions compile; the kernel
|
|
61
|
+
* always passes it.
|
|
62
|
+
*/
|
|
63
|
+
config?: unknown;
|
|
56
64
|
}
|
|
57
65
|
/** Checks a `file-preview@1` provider; returns the problems (empty = valid). */
|
|
58
66
|
export declare function validateFilePreviewProvider(p: unknown): string[];
|
package/dist/hooks.d.ts
CHANGED
|
@@ -35,9 +35,10 @@ export interface HookScope {
|
|
|
35
35
|
* What a model call is for: `step` is a run step's call (the agent's turn), `compaction` is the call
|
|
36
36
|
* that summarizes earlier history before a step when the context grows too large, `title` is the call
|
|
37
37
|
* that writes a conversation's title after its first completed run (no run: `runId` is "", `step` 0,
|
|
38
|
-
* `attempt` 1)
|
|
38
|
+
* `attempt` 1), `web_fetch` is the call that extracts an answer from a fetched web page for the
|
|
39
|
+
* `WebFetch` tool.
|
|
39
40
|
*/
|
|
40
|
-
export type ModelCallPurpose = "step" | "compaction" | "title";
|
|
41
|
+
export type ModelCallPurpose = "step" | "compaction" | "title" | "web_fetch";
|
|
41
42
|
/** What a reported model call was for (the same values as {@link ModelCallPurpose}). */
|
|
42
43
|
export type UsagePurpose = ModelCallPurpose;
|
|
43
44
|
/**
|
|
@@ -168,9 +169,18 @@ export type HookVerdict = void | {
|
|
|
168
169
|
};
|
|
169
170
|
/**
|
|
170
171
|
* A hook handler. The payload is a deep-frozen copy (mutating it throws). `signal` aborts when the
|
|
171
|
-
* hook runs past `hooks.timeoutMs` or the call it observes is aborted.
|
|
172
|
+
* hook runs past `hooks.timeoutMs` or the call it observes is aborted. `call.config` is the
|
|
173
|
+
* extension's effective deployment config for the payload's org (the platform's config with the
|
|
174
|
+
* org's copy applied to the overridable fields, or `ctx.config` while the org follows it); the
|
|
175
|
+
* kernel always passes it, and never calls a hook for an org where that config is not valid
|
|
176
|
+
* (observe hooks are skipped, policy hooks deny).
|
|
172
177
|
*/
|
|
173
|
-
export type HookHandler<K extends AvailableHook> = (payload: HookPayloads[K], signal: AbortSignal) => Promise<HookVerdict> | HookVerdict;
|
|
178
|
+
export type HookHandler<K extends AvailableHook> = (payload: HookPayloads[K], signal: AbortSignal, call?: HookCall) => Promise<HookVerdict> | HookVerdict;
|
|
179
|
+
/** The per-call context of a hook handler (see {@link HookHandler}). */
|
|
180
|
+
export interface HookCall {
|
|
181
|
+
/** The extension's effective deployment config for the payload's org. */
|
|
182
|
+
config: unknown;
|
|
183
|
+
}
|
|
174
184
|
/**
|
|
175
185
|
* - `policy` (only on {@link POLICY_HOOKS}): policy hooks run serially by plugin name (registration
|
|
176
186
|
* order within a plugin) before the call; the first `{ deny }`, error or timeout blocks the call and
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.1.0-rc.
|
|
1
|
+
export declare const SDK_VERSION = "0.1.0-rc.3";
|
|
2
2
|
export * from "./agent-template.js";
|
|
3
3
|
export * from "./bootstrap.js";
|
|
4
4
|
export * from "./cards.js";
|
|
5
5
|
export * from "./channel.js";
|
|
6
|
+
export * from "./config-ui.js";
|
|
6
7
|
export * from "./connector.js";
|
|
7
8
|
export * from "./connector-auth.js";
|
|
8
9
|
export * from "./ctx.js";
|
|
@@ -15,6 +16,7 @@ export * from "./identity.js";
|
|
|
15
16
|
export * from "./marketplace.js";
|
|
16
17
|
export * from "./model.js";
|
|
17
18
|
export * from "./model-params.js";
|
|
19
|
+
export * from "./org-access.js";
|
|
18
20
|
export * from "./permissions.js";
|
|
19
21
|
export * from "./plugin-manifest.js";
|
|
20
22
|
export * from "./plugin-package.js";
|
|
@@ -26,3 +28,4 @@ export * from "./storage.js";
|
|
|
26
28
|
export * from "./tool.js";
|
|
27
29
|
export * from "./tool-key.js";
|
|
28
30
|
export * from "./ui.js";
|
|
31
|
+
export * from "./web.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.1.0-rc.
|
|
1
|
+
export const SDK_VERSION = "0.1.0-rc.3";
|
|
2
2
|
export * from "./agent-template.js";
|
|
3
3
|
export * from "./bootstrap.js";
|
|
4
4
|
export * from "./cards.js";
|
|
5
5
|
export * from "./channel.js";
|
|
6
|
+
export * from "./config-ui.js";
|
|
6
7
|
export * from "./connector.js";
|
|
7
8
|
export * from "./connector-auth.js";
|
|
8
9
|
export * from "./ctx.js";
|
|
@@ -15,6 +16,7 @@ export * from "./identity.js";
|
|
|
15
16
|
export * from "./marketplace.js";
|
|
16
17
|
export * from "./model.js";
|
|
17
18
|
export * from "./model-params.js";
|
|
19
|
+
export * from "./org-access.js";
|
|
18
20
|
export * from "./permissions.js";
|
|
19
21
|
export * from "./plugin-manifest.js";
|
|
20
22
|
export * from "./plugin-package.js";
|
|
@@ -26,3 +28,4 @@ export * from "./storage.js";
|
|
|
26
28
|
export * from "./tool.js";
|
|
27
29
|
export * from "./tool-key.js";
|
|
28
30
|
export * from "./ui.js";
|
|
31
|
+
export * from "./web.js";
|
package/dist/marketplace.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/**
|
|
3
3
|
* The marketplace manifest `marketplace.json` (package ecosystem spec §5.2): a list of plugins,
|
|
4
|
-
* each with one source. One entry is one version
|
|
5
|
-
*
|
|
4
|
+
* each with one source. One entry is one version. `capybara-plugin marketplace --write` lists one
|
|
5
|
+
* entry per plugin, its current version (plugin component management spec §13.2); readers still
|
|
6
|
+
* accept several versions of a plugin (marketplaces written in the older form), so
|
|
7
|
+
* `(name, marketplaceEntryVersion)` is unique in a manifest.
|
|
6
8
|
*
|
|
7
9
|
* Sources: a same-repo `path`, a `git` repo, an `npm` package (exact version + sha512, downloads
|
|
8
10
|
* are verified against it, never npm metadata) or an `archive` URL (sha512 or sha256 SRI).
|
|
@@ -19,6 +21,10 @@ export declare const MARKETPLACE_MANIFEST_SCHEMA_VERSION = 1;
|
|
|
19
21
|
export declare const MARKETPLACE_LIMITS: {
|
|
20
22
|
readonly nameChars: 100;
|
|
21
23
|
readonly summaryChars: 280;
|
|
24
|
+
/** Characters of each `displayName` language (as `plugin.json`'s). */
|
|
25
|
+
readonly displayNameChars: 100;
|
|
26
|
+
/** Characters of a `deprecated.reason`. */
|
|
27
|
+
readonly deprecationReasonChars: 280;
|
|
22
28
|
readonly plugins: 5000;
|
|
23
29
|
readonly permissionKeys: 500;
|
|
24
30
|
readonly urlChars: 2000;
|
|
@@ -53,6 +59,11 @@ export interface MarketplaceEntry {
|
|
|
53
59
|
en: string;
|
|
54
60
|
zh?: string;
|
|
55
61
|
};
|
|
62
|
+
/** Display name (the plugin's `plugin.json` `displayName`); lists show it, else the name. */
|
|
63
|
+
displayName?: {
|
|
64
|
+
zh: string;
|
|
65
|
+
en: string;
|
|
66
|
+
};
|
|
56
67
|
/** Exact semver; defaults to the npm source's version, otherwise the plugin's own. */
|
|
57
68
|
version?: string;
|
|
58
69
|
/** Exact semver. */
|
|
@@ -64,6 +75,15 @@ export interface MarketplaceEntry {
|
|
|
64
75
|
};
|
|
65
76
|
/** https URL. */
|
|
66
77
|
homepage?: string;
|
|
78
|
+
/**
|
|
79
|
+
* This version is deprecated (`true`, or with a reason shown to admins): browsing lists it under
|
|
80
|
+
* the plugin's other versions, never as the one to import, and the kernel refuses importing it
|
|
81
|
+
* (`marketplace.entry_deprecated`). Set by hand on the entry by the marketplace maintainer;
|
|
82
|
+
* `capybara-plugin marketplace --write` keeps it. `false` is the same as absent.
|
|
83
|
+
*/
|
|
84
|
+
deprecated?: boolean | {
|
|
85
|
+
reason?: string;
|
|
86
|
+
};
|
|
67
87
|
source: MarketplaceSource;
|
|
68
88
|
}
|
|
69
89
|
/** `marketplace.json`. */
|
|
@@ -97,3 +117,10 @@ export declare function parseMarketplaceManifest(json: unknown, opts?: Marketpla
|
|
|
97
117
|
};
|
|
98
118
|
/** The entry's version: `version`, else the npm source's version, else `null` (the plugin's own). */
|
|
99
119
|
export declare function marketplaceEntryVersion(e: MarketplaceEntry): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* The entry's deprecation (see {@link MarketplaceEntry.deprecated}): `null` when it is not
|
|
122
|
+
* deprecated (absent or `false`), else its reason (`null` without one).
|
|
123
|
+
*/
|
|
124
|
+
export declare function marketplaceEntryDeprecation(e: MarketplaceEntry): {
|
|
125
|
+
reason: string | null;
|
|
126
|
+
} | null;
|
package/dist/marketplace.js
CHANGED
|
@@ -2,8 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
import { CATALOG_ITEM_NAME_RE, isSafePackagePath, NPM_PACKAGE_NAME_RE, SEMVER_VERSION_RE, SHA512_INTEGRITY_RE, } from "./plugin-package.js";
|
|
3
3
|
/**
|
|
4
4
|
* The marketplace manifest `marketplace.json` (package ecosystem spec §5.2): a list of plugins,
|
|
5
|
-
* each with one source. One entry is one version
|
|
6
|
-
*
|
|
5
|
+
* each with one source. One entry is one version. `capybara-plugin marketplace --write` lists one
|
|
6
|
+
* entry per plugin, its current version (plugin component management spec §13.2); readers still
|
|
7
|
+
* accept several versions of a plugin (marketplaces written in the older form), so
|
|
8
|
+
* `(name, marketplaceEntryVersion)` is unique in a manifest.
|
|
7
9
|
*
|
|
8
10
|
* Sources: a same-repo `path`, a `git` repo, an `npm` package (exact version + sha512, downloads
|
|
9
11
|
* are verified against it, never npm metadata) or an `archive` URL (sha512 or sha256 SRI).
|
|
@@ -20,6 +22,10 @@ export const MARKETPLACE_MANIFEST_SCHEMA_VERSION = 1;
|
|
|
20
22
|
export const MARKETPLACE_LIMITS = {
|
|
21
23
|
nameChars: 100,
|
|
22
24
|
summaryChars: 280,
|
|
25
|
+
/** Characters of each `displayName` language (as `plugin.json`'s). */
|
|
26
|
+
displayNameChars: 100,
|
|
27
|
+
/** Characters of a `deprecated.reason`. */
|
|
28
|
+
deprecationReasonChars: 280,
|
|
23
29
|
plugins: 5000,
|
|
24
30
|
permissionKeys: 500,
|
|
25
31
|
urlChars: 2000,
|
|
@@ -123,6 +129,12 @@ function buildSchema(opts) {
|
|
|
123
129
|
en: z.string().min(1).max(M.summaryChars),
|
|
124
130
|
zh: z.string().min(1).max(M.summaryChars).optional(),
|
|
125
131
|
}),
|
|
132
|
+
displayName: z
|
|
133
|
+
.object({
|
|
134
|
+
zh: z.string().min(1).max(M.displayNameChars),
|
|
135
|
+
en: z.string().min(1).max(M.displayNameChars),
|
|
136
|
+
})
|
|
137
|
+
.optional(),
|
|
126
138
|
version: exactVersion("version").optional(),
|
|
127
139
|
minKernel: exactVersion("minKernel").optional(),
|
|
128
140
|
permissions: z
|
|
@@ -132,6 +144,12 @@ function buildSchema(opts) {
|
|
|
132
144
|
})
|
|
133
145
|
.optional(),
|
|
134
146
|
homepage: urlString(HTTPS_URL_RE).optional(),
|
|
147
|
+
deprecated: z
|
|
148
|
+
.union([
|
|
149
|
+
z.boolean(),
|
|
150
|
+
z.object({ reason: z.string().min(1).max(M.deprecationReasonChars).optional() }),
|
|
151
|
+
])
|
|
152
|
+
.optional(),
|
|
135
153
|
source,
|
|
136
154
|
});
|
|
137
155
|
return z
|
|
@@ -254,3 +272,13 @@ export function marketplaceEntryVersion(e) {
|
|
|
254
272
|
return e.source.npm.version;
|
|
255
273
|
return null;
|
|
256
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* The entry's deprecation (see {@link MarketplaceEntry.deprecated}): `null` when it is not
|
|
277
|
+
* deprecated (absent or `false`), else its reason (`null` without one).
|
|
278
|
+
*/
|
|
279
|
+
export function marketplaceEntryDeprecation(e) {
|
|
280
|
+
const d = e.deprecated;
|
|
281
|
+
if (d === undefined || d === false)
|
|
282
|
+
return null;
|
|
283
|
+
return { reason: d === true ? null : (d.reason ?? null) };
|
|
284
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* How an org may treat a deployment config field: `readonly` (the org sees it, never sets it) or
|
|
4
|
+
* `override` (an org may set its own value). A field without a level is deployment-only.
|
|
5
|
+
*/
|
|
6
|
+
export type OrgAccess = "readonly" | "override";
|
|
7
|
+
/**
|
|
8
|
+
* Marks a deployment config field with an org access level (Zod 4 meta `{ org: access }`), merging
|
|
9
|
+
* with existing meta (`secret`, `hot`, `ui`), so `orgAccess(secret(x), …)` and
|
|
10
|
+
* `secret(orgAccess(x, …))` are the same. `configJsonSchema` publishes it as `"x-org"`.
|
|
11
|
+
*
|
|
12
|
+
* The author's promise for `override`: the extension reads the field from the per-call `config`
|
|
13
|
+
* the kernel passes with each request, not from `ctx.config`, and the field may be absent from
|
|
14
|
+
* `ctx.config` (the deployment value is the default, which an org may replace per call).
|
|
15
|
+
*
|
|
16
|
+
* The per-call config of an org that has its own copy still contains the platform's hidden
|
|
17
|
+
* (platform-only) secrets, which an org cannot set; it never contains an overridable platform
|
|
18
|
+
* secret. Never send a hidden secret to an endpoint taken from an overridable field: an org could
|
|
19
|
+
* point that endpoint at a server it controls.
|
|
20
|
+
*/
|
|
21
|
+
export declare function orgAccess<T extends z.ZodTypeAny>(schema: T, access: OrgAccess): T;
|
|
22
|
+
/**
|
|
23
|
+
* The org access level of a schema's meta, or undefined when it has none. It sees through
|
|
24
|
+
* `.optional()`, `.default()`, `.nullable()`, `.prefault()`, `.catch()` and `.readonly()` (the
|
|
25
|
+
* outermost level wins), so it agrees with the `"x-org"` that `configJsonSchema` publishes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function orgAccessOf(schema: z.ZodTypeAny): OrgAccess | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a deployment config field with an org access level (Zod 4 meta `{ org: access }`), merging
|
|
3
|
+
* with existing meta (`secret`, `hot`, `ui`), so `orgAccess(secret(x), …)` and
|
|
4
|
+
* `secret(orgAccess(x, …))` are the same. `configJsonSchema` publishes it as `"x-org"`.
|
|
5
|
+
*
|
|
6
|
+
* The author's promise for `override`: the extension reads the field from the per-call `config`
|
|
7
|
+
* the kernel passes with each request, not from `ctx.config`, and the field may be absent from
|
|
8
|
+
* `ctx.config` (the deployment value is the default, which an org may replace per call).
|
|
9
|
+
*
|
|
10
|
+
* The per-call config of an org that has its own copy still contains the platform's hidden
|
|
11
|
+
* (platform-only) secrets, which an org cannot set; it never contains an overridable platform
|
|
12
|
+
* secret. Never send a hidden secret to an endpoint taken from an overridable field: an org could
|
|
13
|
+
* point that endpoint at a server it controls.
|
|
14
|
+
*/
|
|
15
|
+
export function orgAccess(schema, access) {
|
|
16
|
+
return schema.meta({ ...(schema.meta() ?? {}), org: access });
|
|
17
|
+
}
|
|
18
|
+
/** The wrappers a field's org level is read through, as `configJsonSchema` inherits meta. */
|
|
19
|
+
const WRAPPERS = new Set(["default", "prefault", "optional", "nullable", "readonly", "catch"]);
|
|
20
|
+
/**
|
|
21
|
+
* The org access level of a schema's meta, or undefined when it has none. It sees through
|
|
22
|
+
* `.optional()`, `.default()`, `.nullable()`, `.prefault()`, `.catch()` and `.readonly()` (the
|
|
23
|
+
* outermost level wins), so it agrees with the `"x-org"` that `configJsonSchema` publishes.
|
|
24
|
+
*/
|
|
25
|
+
export function orgAccessOf(schema) {
|
|
26
|
+
let node = schema;
|
|
27
|
+
while (node) {
|
|
28
|
+
const v = node.meta()?.org;
|
|
29
|
+
if (v === "readonly" || v === "override")
|
|
30
|
+
return v;
|
|
31
|
+
const def = node._zod.def;
|
|
32
|
+
node = WRAPPERS.has(def.type) ? def.innerType : undefined;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type JsonSchema } from "./config-ui.js";
|
|
2
3
|
import { type ConnectorTemplateSpec, type LocalizedName, type StaticExtensionManifest } from "./plugin-package.js";
|
|
3
4
|
/**
|
|
4
5
|
* The plugin manifest `.capybara-plugin/plugin.json` (package ecosystem spec §4.1). A plugin is a
|
|
@@ -74,7 +75,29 @@ export interface PluginExtensionSection {
|
|
|
74
75
|
migrations?: string;
|
|
75
76
|
/** `./ui`. */
|
|
76
77
|
ui?: string;
|
|
78
|
+
/**
|
|
79
|
+
* The config JSON Schemas (`configJsonSchema`, with `x-ui` hints), written by the packer so an
|
|
80
|
+
* extension not loaded on a replica still gets its form; each ≤
|
|
81
|
+
* {@link CONFIG_UI_LIMITS}`.configSchemaBytes`. Not part of the `manifest_mismatch` comparison.
|
|
82
|
+
*/
|
|
83
|
+
configSchemas?: {
|
|
84
|
+
deployment?: JsonSchema;
|
|
85
|
+
org?: JsonSchema;
|
|
86
|
+
};
|
|
77
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* A plugin's description: a string (English) or `{ en, zh? }`, each ≤ 1024 characters. Read it
|
|
90
|
+
* through {@link pluginDescription}.
|
|
91
|
+
*/
|
|
92
|
+
export type PluginDescription = string | {
|
|
93
|
+
en: string;
|
|
94
|
+
zh?: string;
|
|
95
|
+
};
|
|
96
|
+
/** A plugin description as `{ en, zh? }` (a plain string is the English text). */
|
|
97
|
+
export declare function pluginDescription(d: PluginDescription): {
|
|
98
|
+
en: string;
|
|
99
|
+
zh?: string;
|
|
100
|
+
};
|
|
78
101
|
/** `.capybara-plugin/plugin.json`. */
|
|
79
102
|
export interface PluginManifest {
|
|
80
103
|
$schema?: string;
|
|
@@ -84,8 +107,8 @@ export interface PluginManifest {
|
|
|
84
107
|
/** Exact semver version. */
|
|
85
108
|
version: string;
|
|
86
109
|
publisher: string;
|
|
87
|
-
/** ≤ 1024 characters. */
|
|
88
|
-
description:
|
|
110
|
+
/** A string (English) or `{ en, zh? }`, each ≤ 1024 characters; see {@link pluginDescription}. */
|
|
111
|
+
description: PluginDescription;
|
|
89
112
|
displayName?: LocalizedName;
|
|
90
113
|
/** `./…` .svg / .png inside the plugin. */
|
|
91
114
|
icon?: string;
|
package/dist/plugin-manifest.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import semver from "semver";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { AGENT_TEMPLATE_LIMITS } from "./agent-template.js";
|
|
4
|
+
import { CONFIG_UI_LIMITS } from "./config-ui.js";
|
|
4
5
|
import { defineExtension, EXTENSION_NAME_RE } from "./define-extension.js";
|
|
5
6
|
import { CATALOG_ITEM_NAME_RE, CONNECTOR_TEMPLATE_LIMITS, isSafePackagePath, SEMVER_VERSION_RE, validateConnectorTemplate, } from "./plugin-package.js";
|
|
6
7
|
import { hasUpperBound } from "./semver-range.js";
|
|
@@ -47,6 +48,10 @@ export const PLUGIN_MANIFEST_LIMITS = {
|
|
|
47
48
|
displayNameChars: 100,
|
|
48
49
|
pathChars: 512,
|
|
49
50
|
};
|
|
51
|
+
/** A plugin description as `{ en, zh? }` (a plain string is the English text). */
|
|
52
|
+
export function pluginDescription(d) {
|
|
53
|
+
return typeof d === "string" ? { en: d } : { en: d.en, ...(d.zh !== undefined && { zh: d.zh }) };
|
|
54
|
+
}
|
|
50
55
|
const L = PLUGIN_MANIFEST_LIMITS;
|
|
51
56
|
const MAX_ERRORS = 50;
|
|
52
57
|
const ICON_RE = /\.(?:svg|png)$/i;
|
|
@@ -87,6 +92,11 @@ const staticExtensionManifest = z.strictObject({
|
|
|
87
92
|
ui: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
88
93
|
config: z.strictObject({ deployment: configFields, org: configFields }),
|
|
89
94
|
});
|
|
95
|
+
const configSchemaJson = z
|
|
96
|
+
.record(z.string(), z.unknown())
|
|
97
|
+
.refine((v) => new TextEncoder().encode(JSON.stringify(v)).length <= CONFIG_UI_LIMITS.configSchemaBytes, {
|
|
98
|
+
message: `must be at most ${CONFIG_UI_LIMITS.configSchemaBytes} bytes of JSON (${CONFIG_UI_LIMITS.configSchemaBytes / 1024} KiB)`,
|
|
99
|
+
});
|
|
90
100
|
const extensionSection = z.strictObject({
|
|
91
101
|
entry: dotPath("entry").refine((p) => p.endsWith(".mjs"), {
|
|
92
102
|
message: "entry must be a .mjs file",
|
|
@@ -94,6 +104,9 @@ const extensionSection = z.strictObject({
|
|
|
94
104
|
manifest: staticExtensionManifest,
|
|
95
105
|
migrations: dotPath("migrations").optional(),
|
|
96
106
|
ui: dotPath("ui").optional(),
|
|
107
|
+
configSchemas: z
|
|
108
|
+
.strictObject({ deployment: configSchemaJson.optional(), org: configSchemaJson.optional() })
|
|
109
|
+
.optional(),
|
|
97
110
|
});
|
|
98
111
|
const schema = z
|
|
99
112
|
.strictObject({
|
|
@@ -107,7 +120,13 @@ const schema = z
|
|
|
107
120
|
.max(256)
|
|
108
121
|
.regex(SEMVER_VERSION_RE, { message: "version must be an exact semver version" }),
|
|
109
122
|
publisher: text(L.publisherChars, "publisher"),
|
|
110
|
-
description:
|
|
123
|
+
description: z.union([
|
|
124
|
+
text(L.descriptionChars, "description"),
|
|
125
|
+
z.strictObject({
|
|
126
|
+
en: text(L.descriptionChars, "description.en"),
|
|
127
|
+
zh: text(L.descriptionChars, "description.zh").optional(),
|
|
128
|
+
}),
|
|
129
|
+
]),
|
|
111
130
|
displayName: localized.optional(),
|
|
112
131
|
icon: dotPath("icon")
|
|
113
132
|
.regex(ICON_RE, { message: "icon must be a .svg or .png file" })
|