@capybara-hq/plugin-sdk 0.0.0-stage → 0.1.0-rc.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.
Files changed (70) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +27 -2
  4. package/dist/agent-template.d.ts +47 -0
  5. package/dist/agent-template.js +123 -0
  6. package/dist/bootstrap.d.ts +67 -0
  7. package/dist/bootstrap.js +109 -0
  8. package/dist/cards.d.ts +64 -0
  9. package/dist/cards.js +217 -0
  10. package/dist/channel.d.ts +230 -0
  11. package/dist/channel.js +26 -0
  12. package/dist/connector-auth.d.ts +64 -0
  13. package/dist/connector-auth.js +96 -0
  14. package/dist/connector.d.ts +154 -0
  15. package/dist/connector.js +1 -0
  16. package/dist/ctx.d.ts +176 -0
  17. package/dist/ctx.js +1 -0
  18. package/dist/define-extension.d.ts +41 -0
  19. package/dist/define-extension.js +85 -0
  20. package/dist/directory.d.ts +29 -0
  21. package/dist/directory.js +1 -0
  22. package/dist/extension-point.d.ts +29 -0
  23. package/dist/extension-point.js +36 -0
  24. package/dist/file-preview.d.ts +64 -0
  25. package/dist/file-preview.js +72 -0
  26. package/dist/frontmatter.d.ts +12 -0
  27. package/dist/frontmatter.js +36 -0
  28. package/dist/hooks.d.ts +194 -0
  29. package/dist/hooks.js +14 -0
  30. package/dist/identity.d.ts +44 -0
  31. package/dist/identity.js +1 -0
  32. package/dist/index.d.ts +27 -0
  33. package/dist/index.js +27 -0
  34. package/dist/marketplace.d.ts +99 -0
  35. package/dist/marketplace.js +256 -0
  36. package/dist/model-params.d.ts +82 -0
  37. package/dist/model-params.js +222 -0
  38. package/dist/model.d.ts +227 -0
  39. package/dist/model.js +152 -0
  40. package/dist/permissions.d.ts +67 -0
  41. package/dist/permissions.js +271 -0
  42. package/dist/plugin-manifest.d.ts +141 -0
  43. package/dist/plugin-manifest.js +322 -0
  44. package/dist/plugin-package.d.ts +139 -0
  45. package/dist/plugin-package.js +448 -0
  46. package/dist/sandbox.d.ts +152 -0
  47. package/dist/sandbox.js +96 -0
  48. package/dist/secret.d.ts +5 -0
  49. package/dist/secret.js +8 -0
  50. package/dist/semver-range.d.ts +6 -0
  51. package/dist/semver-range.js +12 -0
  52. package/dist/skill-invocation.d.ts +83 -0
  53. package/dist/skill-invocation.js +201 -0
  54. package/dist/skill-keys.d.ts +9 -0
  55. package/dist/skill-keys.js +16 -0
  56. package/dist/skill.d.ts +77 -0
  57. package/dist/skill.js +244 -0
  58. package/dist/storage.d.ts +96 -0
  59. package/dist/storage.js +9 -0
  60. package/dist/tool.d.ts +154 -0
  61. package/dist/tool.js +218 -0
  62. package/dist/ui.d.ts +265 -0
  63. package/dist/ui.js +358 -0
  64. package/dist/url.d.ts +2 -0
  65. package/dist/url.js +12 -0
  66. package/package.json +40 -3
  67. package/schemas/bootstrap.v1.schema.json +125 -0
  68. package/schemas/connector-template.v1.schema.json +116 -0
  69. package/schemas/marketplace.v1.schema.json +270 -0
  70. package/schemas/plugin.v1.schema.json +330 -0
@@ -0,0 +1,72 @@
1
+ /** File previewer extension point ref (UI refresh spec §5.8). */
2
+ export const FILE_PREVIEW_EXTENSION_POINT = "file-preview@1";
3
+ /** Largest file a previewer may declare (`FilePreviewer.maxBytes`): 20 MiB. */
4
+ export const FILE_PREVIEW_MAX_BYTES = 20 * 1024 * 1024;
5
+ const ID_RE = /^[a-z0-9-]{1,40}$/;
6
+ const TITLE_MAX = 100;
7
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
8
+ const strings = (v) => Array.isArray(v) && v.every((s) => typeof s === "string" && s !== "" && s.length <= 100);
9
+ /** Checks a `file-preview@1` provider; returns the problems (empty = valid). */
10
+ export function validateFilePreviewProvider(p) {
11
+ if (!isObj(p))
12
+ return ["provider must be an object"];
13
+ if (!Array.isArray(p.previewers) || p.previewers.length === 0)
14
+ return ["previewers must be a non-empty array"];
15
+ const out = [];
16
+ const seen = new Set();
17
+ p.previewers.forEach((v, i) => {
18
+ const at = `previewers[${i}]`;
19
+ if (!isObj(v)) {
20
+ out.push(`${at} must be an object`);
21
+ return;
22
+ }
23
+ if (typeof v.id !== "string" || !ID_RE.test(v.id))
24
+ out.push(`${at}.id must match ${ID_RE.source}`);
25
+ else if (seen.has(v.id))
26
+ out.push(`${at}.id "${v.id}" is a duplicate`);
27
+ else
28
+ seen.add(v.id);
29
+ const t = v.title;
30
+ if (!isObj(t) ||
31
+ [t.zh, t.en].some((s) => typeof s !== "string" || s === "" || s.length > TITLE_MAX))
32
+ out.push(`${at}.title needs non-empty zh and en of at most ${TITLE_MAX} characters`);
33
+ const a = v.accepts;
34
+ if (!isObj(a) ||
35
+ (a.mimeTypes !== undefined && !strings(a.mimeTypes)) ||
36
+ (a.extensions !== undefined && !strings(a.extensions)) ||
37
+ (a.mimeTypes?.length ?? 0) +
38
+ (a.extensions?.length ?? 0) ===
39
+ 0)
40
+ out.push(`${at}.accepts needs at least one MIME type or extension`);
41
+ if (typeof v.maxBytes !== "number" ||
42
+ !Number.isInteger(v.maxBytes) ||
43
+ v.maxBytes < 1 ||
44
+ v.maxBytes > FILE_PREVIEW_MAX_BYTES)
45
+ out.push(`${at}.maxBytes must be an integer from 1 to ${FILE_PREVIEW_MAX_BYTES}`);
46
+ const hasRender = typeof v.render === "function";
47
+ const hasEmbed = v.embed !== undefined;
48
+ if (hasRender === hasEmbed)
49
+ out.push(`${at} must have exactly one of render or embed`);
50
+ else if (hasEmbed && (typeof v.embed !== "string" || v.embed === ""))
51
+ out.push(`${at}.embed must be the id of a ui contribution`);
52
+ });
53
+ return out;
54
+ }
55
+ const baseType = (t) => (t.split(";")[0] ?? "").trim().toLowerCase();
56
+ /** Whether a previewer takes this file: its type or extension is accepted and it is within `maxBytes`. */
57
+ export function previewerAccepts(p, file) {
58
+ if (file.size > p.maxBytes)
59
+ return false;
60
+ const type = baseType(file.contentType);
61
+ const typeOk = (p.accepts.mimeTypes ?? []).some((m) => {
62
+ const want = m.toLowerCase();
63
+ return want.endsWith("/*") ? type.startsWith(want.slice(0, -1)) : type === want;
64
+ });
65
+ if (typeOk)
66
+ return true;
67
+ const name = file.name.toLowerCase();
68
+ return (p.accepts.extensions ?? []).some((e) => {
69
+ const ext = e.toLowerCase().replace(/^\.?/, ".");
70
+ return name.endsWith(ext) && name.length > ext.length;
71
+ });
72
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Reads a Markdown file that starts with YAML frontmatter between `---` lines (SKILL.md, agent
3
+ * templates): the frontmatter mapping and the body after it. Tolerates a BOM and CRLF (CRLF in the
4
+ * body becomes LF). `file` names the file in the errors (English); frontmatter longer than
5
+ * `maxChars` is not parsed. Not exported from the package.
6
+ */
7
+ export declare function readFrontmatter(text: string, file: string, maxChars: number): {
8
+ frontmatter: Record<string, unknown>;
9
+ body: string;
10
+ } | {
11
+ errors: string[];
12
+ };
@@ -0,0 +1,36 @@
1
+ import { parse as parseYaml } from "yaml";
2
+ /**
3
+ * Reads a Markdown file that starts with YAML frontmatter between `---` lines (SKILL.md, agent
4
+ * templates): the frontmatter mapping and the body after it. Tolerates a BOM and CRLF (CRLF in the
5
+ * body becomes LF). `file` names the file in the errors (English); frontmatter longer than
6
+ * `maxChars` is not parsed. Not exported from the package.
7
+ */
8
+ export function readFrontmatter(text, file, maxChars) {
9
+ const src = (text.startsWith("\uFEFF") ? text.slice(1) : text).replace(/\r\n?/g, "\n");
10
+ const lines = src.split("\n");
11
+ if (lines[0]?.trimEnd() !== "---") {
12
+ return { errors: [`${file} must start with YAML frontmatter delimited by '---' lines`] };
13
+ }
14
+ const end = lines.findIndex((l, i) => i > 0 && l.trimEnd() === "---");
15
+ if (end < 0)
16
+ return { errors: [`${file} frontmatter is not closed by a '---' line`] };
17
+ const front = lines.slice(1, end).join("\n");
18
+ if (front.length > maxChars) {
19
+ return {
20
+ errors: [`frontmatter must be at most ${maxChars} characters`],
21
+ };
22
+ }
23
+ const body = lines.slice(end + 1).join("\n");
24
+ let data;
25
+ try {
26
+ data = parseYaml(front, { uniqueKeys: true, maxAliasCount: 50 });
27
+ }
28
+ catch (e) {
29
+ const first = (e instanceof Error ? e.message : String(e)).split("\n")[0];
30
+ return { errors: [`frontmatter is not valid YAML: ${first}`] };
31
+ }
32
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
33
+ return { errors: ["frontmatter must be a YAML mapping"] };
34
+ }
35
+ return { frontmatter: data, body };
36
+ }
@@ -0,0 +1,194 @@
1
+ import type { ContentBlock, ModelRequest, ModelUsage } from "./model.js";
2
+ /**
3
+ * Hook mount points a plugin can register on through `ctx.hooks.on` (from M7). The other names in
4
+ * `HOOK_MOUNT_POINTS` (`session.*`, `agent.preStep`) stay reserved: they validate in a manifest but
5
+ * cannot be registered yet.
6
+ */
7
+ export declare const HOOKS_AVAILABLE: readonly ["model.request", "model.response", "tool.preExecute", "tool.postExecute", "usage.report"];
8
+ /** A mount point that `ctx.hooks.on` accepts. */
9
+ export type AvailableHook = (typeof HOOKS_AVAILABLE)[number];
10
+ /** Where a sub-agent was dispatched from: the parent conversation, its run and the `agent` call. */
11
+ export interface HookParentScope {
12
+ conversationId: string;
13
+ runId: string;
14
+ /**
15
+ * The `agent` call's id; `skill-<seq>` for a person's forked skill invocation, which has no
16
+ * tool call in the parent's log.
17
+ */
18
+ callId: string;
19
+ }
20
+ /**
21
+ * Who and where a model or tool hook fires for. Hooks fire in chat conversations and in
22
+ * sub-agents' conversations (never in eval replays).
23
+ */
24
+ export interface HookScope {
25
+ orgId: string;
26
+ accountId: string;
27
+ agentId: string;
28
+ agentVersion: number;
29
+ conversationId: string;
30
+ runId: string;
31
+ /** Set when the call belongs to a sub-agent: where it was dispatched from. */
32
+ parent?: HookParentScope;
33
+ }
34
+ /**
35
+ * What a model call is for: `step` is a run step's call (the agent's turn), `compaction` is the call
36
+ * that summarizes earlier history before a step when the context grows too large, `title` is the call
37
+ * that writes a conversation's title after its first completed run (no run: `runId` is "", `step` 0,
38
+ * `attempt` 1).
39
+ */
40
+ export type ModelCallPurpose = "step" | "compaction" | "title";
41
+ /** What a reported model call was for (the same values as {@link ModelCallPurpose}). */
42
+ export type UsagePurpose = ModelCallPurpose;
43
+ /**
44
+ * An image or document of a `model.request` payload. Hook payloads never carry media bytes or
45
+ * attachment ids: each image or document block is replaced by this placeholder.
46
+ * `size` is the size of the bytes sent to the provider, in bytes (0 when unknown).
47
+ */
48
+ export interface HookMediaPlaceholder {
49
+ type: "media_omitted";
50
+ kind: "image" | "document";
51
+ mediaType: string;
52
+ size: number;
53
+ }
54
+ /** A content block of a `model.request` payload: image and document blocks become placeholders. */
55
+ export type HookContentBlock = Exclude<ContentBlock, {
56
+ type: "image";
57
+ } | {
58
+ type: "document";
59
+ }> | HookMediaPlaceholder;
60
+ /** A message of a `model.request` payload (see {@link HookContentBlock}). */
61
+ export type HookModelMessage = {
62
+ role: "user";
63
+ content: HookContentBlock[];
64
+ } | {
65
+ role: "assistant";
66
+ content: HookContentBlock[];
67
+ } | {
68
+ role: "system";
69
+ content: string;
70
+ };
71
+ /** The request of a `model.request` payload: the request sent to the provider, without media bytes. */
72
+ export type HookModelRequest = Omit<ModelRequest, "messages"> & {
73
+ messages: HookModelMessage[];
74
+ };
75
+ /**
76
+ * `model.request`: before a model call is sent. A policy hook may deny it. Fires once per attempt of a
77
+ * step (`purpose: "step"`), once for each compaction call (`purpose: "compaction"`, `attempt` 1) and
78
+ * once for each conversation title call (`purpose: "title"`, `runId` "", `step` 0, `attempt` 1; a
79
+ * denial skips the title and is recorded only in the audit log).
80
+ * `request` is the request as sent, except that images and documents are placeholders
81
+ * ({@link HookMediaPlaceholder}): no media bytes, no attachment ids.
82
+ */
83
+ export interface ModelRequestHookPayload extends HookScope {
84
+ purpose: ModelCallPurpose;
85
+ step: number;
86
+ attempt: number;
87
+ provider: string;
88
+ model: string;
89
+ request: Readonly<HookModelRequest>;
90
+ }
91
+ /**
92
+ * `model.response`: after a sent model call finished or failed, with its usage when known (step
93
+ * attempts, compaction and title calls, see `purpose`). Not fired for a call the user aborted (the run was
94
+ * stopped while the call was in flight), nor for a call that was never sent (denied, or failed
95
+ * before sending), so a `model.request` can have no matching `model.response`.
96
+ */
97
+ export interface ModelResponseHookPayload extends HookScope {
98
+ purpose: ModelCallPurpose;
99
+ step: number;
100
+ attempt: number;
101
+ provider: string;
102
+ model: string;
103
+ outcome: "ok" | "failed";
104
+ content: readonly ContentBlock[];
105
+ stopReason: string | null;
106
+ usage: ModelUsage | null;
107
+ latencyMs: number;
108
+ errorCode?: string;
109
+ }
110
+ /** `tool.preExecute`: before a tool call runs. A policy hook may deny it. */
111
+ export interface ToolPreHookPayload extends HookScope {
112
+ step: number;
113
+ callId: string;
114
+ toolKey: string;
115
+ specHash: string;
116
+ input: unknown;
117
+ }
118
+ /** `tool.postExecute`: after a tool call finished, with the result the model sees. */
119
+ export interface ToolPostHookPayload extends ToolPreHookPayload {
120
+ isError: boolean;
121
+ content: string;
122
+ durationMs: number;
123
+ }
124
+ /**
125
+ * `usage.report`: the usage of one model call that the event log recorded (chat runs and full-run eval
126
+ * replays, see `conversationKind`), delivered through a queue **at least once**: a failed or
127
+ * interrupted delivery is retried (up to 5 times, 2 s doubling), possibly on another replica, so
128
+ * handlers must be idempotent by `eventId` (`<conversationId>/<seq>`). Delivered only while the
129
+ * organization has the plugin enabled; the verdict is ignored.
130
+ */
131
+ export interface UsageReportPayload {
132
+ eventId: string;
133
+ /**
134
+ * A run step's call, a compaction call, or a conversation title call (`title`: no run, so
135
+ * `runId` is ""). All are billed.
136
+ */
137
+ purpose: UsagePurpose;
138
+ orgId: string;
139
+ accountId: string;
140
+ agentId: string;
141
+ conversationId: string;
142
+ /** A sub-agent's calls report `chat`; `parent` tells them apart. */
143
+ conversationKind: "chat" | "eval";
144
+ runId: string;
145
+ /** Set when the call belongs to a sub-agent: where it was dispatched from. */
146
+ parent?: HookParentScope;
147
+ provider: string;
148
+ model: string;
149
+ usage: ModelUsage;
150
+ occurredAt: string;
151
+ }
152
+ /** Payload type of each mount point. */
153
+ export interface HookPayloads {
154
+ "model.request": ModelRequestHookPayload;
155
+ "model.response": ModelResponseHookPayload;
156
+ "tool.preExecute": ToolPreHookPayload;
157
+ "tool.postExecute": ToolPostHookPayload;
158
+ "usage.report": UsageReportPayload;
159
+ }
160
+ /**
161
+ * What a hook returns: nothing, or `{ deny: reason }`. A deny only takes effect for a `policy` hook
162
+ * on a mount point in {@link POLICY_HOOKS}; it is ignored everywhere else. Hooks never rewrite the
163
+ * request or the result.
164
+ */
165
+ export type HookVerdict = void | {
166
+ deny: string;
167
+ };
168
+ /**
169
+ * A hook handler. The payload is a deep-frozen copy (mutating it throws). `signal` aborts when the
170
+ * hook runs past `hooks.timeoutMs` or the call it observes is aborted.
171
+ */
172
+ export type HookHandler<K extends AvailableHook> = (payload: HookPayloads[K], signal: AbortSignal) => Promise<HookVerdict> | HookVerdict;
173
+ /**
174
+ * - `policy` (only on {@link POLICY_HOOKS}): policy hooks run serially by plugin name (registration
175
+ * order within a plugin) before the call; the first `{ deny }`, error or timeout blocks the call and
176
+ * the rest are skipped. The model sees your deny reason (sanitized, at most 500 characters), but
177
+ * only a generic sentence for an error or a timeout.
178
+ * - `observe`: dispatched after the policy hooks, concurrently and fire-and-forget, whether or not
179
+ * the call was denied; the call never waits for them. Verdicts, errors and timeouts are ignored
180
+ * (logged as throttled warnings). Each call keeps the `hooks.timeoutMs` limit and its abort signal;
181
+ * when too many observe calls of one plugin are in flight on a replica, new ones are dropped.
182
+ *
183
+ * A `policy` hook must also be declared in `permissions.policyHooks`. Policy hooks fail closed: when
184
+ * a plugin that declares a policy hook on a mount point is enabled and approved but not loaded on the
185
+ * replica that handles a call (setup failed, pending configuration, import failed), the call is
186
+ * denied with a generic reason. Observe hooks of a plugin that is not loaded are simply skipped.
187
+ */
188
+ export interface HookOptions {
189
+ mode: "observe" | "policy";
190
+ }
191
+ /** Mount points where a `policy` hook may deny the call. */
192
+ export declare const POLICY_HOOKS: readonly ["model.request", "tool.preExecute"];
193
+ /** A mount point that accepts `policy` hooks (`permissions.policyHooks`). */
194
+ export type PolicyHook = (typeof POLICY_HOOKS)[number];
package/dist/hooks.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Hook mount points a plugin can register on through `ctx.hooks.on` (from M7). The other names in
3
+ * `HOOK_MOUNT_POINTS` (`session.*`, `agent.preStep`) stay reserved: they validate in a manifest but
4
+ * cannot be registered yet.
5
+ */
6
+ export const HOOKS_AVAILABLE = [
7
+ "model.request",
8
+ "model.response",
9
+ "tool.preExecute",
10
+ "tool.postExecute",
11
+ "usage.report",
12
+ ];
13
+ /** Mount points where a `policy` hook may deny the call. */
14
+ export const POLICY_HOOKS = ["model.request", "tool.preExecute"];
@@ -0,0 +1,44 @@
1
+ /** 外部身份;登录、渠道入站、通讯录三处用同一个 (source, externalId) 键。 */
2
+ export interface ExternalIdentity {
3
+ /**
4
+ * `"<插件名>:<租户 id>"`,必须与 `tenantId` 一致。第三方登录时内核核对
5
+ * `source === \`${插件名}:${tenantId}\``,不一致按身份源出错拒绝。
6
+ */
7
+ source: string;
8
+ externalId: string;
9
+ tenantId: string;
10
+ email?: string;
11
+ emailVerified: boolean;
12
+ displayName: string;
13
+ avatarUrl?: string;
14
+ }
15
+ /**
16
+ * 第三方登录(`identity-source@1`)。state、nonce 与 PKCE 都由内核生成并保存在服务端:
17
+ * 插件把 `codeChallenge` / `codeChallengeMethod` 放进授权地址(`code_challenge` / `code_challenge_method`),
18
+ * 回调换 token 时带上 `codeVerifier`(`code_verifier`)。平台不支持 PKCE 时忽略这几个字段即可。
19
+ */
20
+ export interface IdentitySource {
21
+ label: {
22
+ zh: string;
23
+ en: string;
24
+ };
25
+ authorizeUrl(args: {
26
+ config: unknown;
27
+ redirectUri: string;
28
+ state: string;
29
+ nonce: string;
30
+ /** PKCE challenge(base64url(SHA-256(codeVerifier)));内核总会提供,旧内核不提供。 */
31
+ codeChallenge?: string;
32
+ /** PKCE challenge 的算法;内核只用 `"S256"`。 */
33
+ codeChallengeMethod?: "S256";
34
+ }): string;
35
+ callback(args: {
36
+ config: unknown;
37
+ redirectUri: string;
38
+ query: Record<string, string>;
39
+ nonce: string;
40
+ signal: AbortSignal;
41
+ /** 与本次发起的 `codeChallenge` 对应的 PKCE verifier;内核总会提供,旧内核不提供。 */
42
+ codeVerifier?: string;
43
+ }): Promise<ExternalIdentity>;
44
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ export declare const SDK_VERSION = "0.1.0-rc.0";
2
+ export * from "./agent-template.js";
3
+ export * from "./bootstrap.js";
4
+ export * from "./cards.js";
5
+ export * from "./channel.js";
6
+ export * from "./connector.js";
7
+ export * from "./connector-auth.js";
8
+ export * from "./ctx.js";
9
+ export * from "./define-extension.js";
10
+ export * from "./directory.js";
11
+ export * from "./extension-point.js";
12
+ export * from "./file-preview.js";
13
+ export * from "./hooks.js";
14
+ export * from "./identity.js";
15
+ export * from "./marketplace.js";
16
+ export * from "./model.js";
17
+ export * from "./model-params.js";
18
+ export * from "./permissions.js";
19
+ export * from "./plugin-manifest.js";
20
+ export * from "./plugin-package.js";
21
+ export * from "./sandbox.js";
22
+ export * from "./secret.js";
23
+ export * from "./skill.js";
24
+ export * from "./skill-invocation.js";
25
+ export * from "./storage.js";
26
+ export * from "./tool.js";
27
+ export * from "./ui.js";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ export const SDK_VERSION = "0.1.0-rc.0";
2
+ export * from "./agent-template.js";
3
+ export * from "./bootstrap.js";
4
+ export * from "./cards.js";
5
+ export * from "./channel.js";
6
+ export * from "./connector.js";
7
+ export * from "./connector-auth.js";
8
+ export * from "./ctx.js";
9
+ export * from "./define-extension.js";
10
+ export * from "./directory.js";
11
+ export * from "./extension-point.js";
12
+ export * from "./file-preview.js";
13
+ export * from "./hooks.js";
14
+ export * from "./identity.js";
15
+ export * from "./marketplace.js";
16
+ export * from "./model.js";
17
+ export * from "./model-params.js";
18
+ export * from "./permissions.js";
19
+ export * from "./plugin-manifest.js";
20
+ export * from "./plugin-package.js";
21
+ export * from "./sandbox.js";
22
+ export * from "./secret.js";
23
+ export * from "./skill.js";
24
+ export * from "./skill-invocation.js";
25
+ export * from "./storage.js";
26
+ export * from "./tool.js";
27
+ export * from "./ui.js";
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ /**
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: a marketplace that offers several versions lists
5
+ * the plugin once per version, so `(name, marketplaceEntryVersion)` is unique in a manifest.
6
+ *
7
+ * Sources: a same-repo `path`, a `git` repo, an `npm` package (exact version + sha512, downloads
8
+ * are verified against it, never npm metadata) or an `archive` URL (sha512 or sha256 SRI).
9
+ *
10
+ * The committed JSON Schema (`schemas/marketplace.v1.schema.json`) is generated from
11
+ * {@link MarketplaceManifestSchema} by `pnpm gen:package-schemas` and drift-checked by
12
+ * `check:package-schemas`. Cross-field rules (unique `(name, version)`, `version` equal to the npm
13
+ * source's, `sensitive` ⊆ `keys`) are decided only by {@link parseMarketplaceManifest}. Unknown
14
+ * fields are dropped (forward compatible: a newer manifest does not make an older kernel reject it).
15
+ */
16
+ /** Manifest format version. */
17
+ export declare const MARKETPLACE_MANIFEST_SCHEMA_VERSION = 1;
18
+ /** Limits of the marketplace manifest. */
19
+ export declare const MARKETPLACE_LIMITS: {
20
+ readonly nameChars: 100;
21
+ readonly summaryChars: 280;
22
+ readonly plugins: 5000;
23
+ readonly permissionKeys: 500;
24
+ readonly urlChars: 2000;
25
+ };
26
+ /** Where a marketplace entry's plugin comes from. */
27
+ export type MarketplaceSource = {
28
+ path: string;
29
+ } | {
30
+ git: {
31
+ url: string;
32
+ ref?: string;
33
+ commit?: string;
34
+ path?: string;
35
+ };
36
+ } | {
37
+ npm: {
38
+ package: string;
39
+ version: string;
40
+ integrity: string;
41
+ };
42
+ } | {
43
+ archive: {
44
+ url: string;
45
+ integrity: string;
46
+ };
47
+ };
48
+ /** One plugin version listed by a marketplace. */
49
+ export interface MarketplaceEntry {
50
+ /** Catalog item name (`^[a-z0-9][a-z0-9-]{0,63}$`). */
51
+ name: string;
52
+ summary: {
53
+ en: string;
54
+ zh?: string;
55
+ };
56
+ /** Exact semver; defaults to the npm source's version, otherwise the plugin's own. */
57
+ version?: string;
58
+ /** Exact semver. */
59
+ minKernel?: string;
60
+ /** Display only (a mismatch with the computed permissions warns, never rejects). */
61
+ permissions?: {
62
+ keys: string[];
63
+ sensitive: string[];
64
+ };
65
+ /** https URL. */
66
+ homepage?: string;
67
+ source: MarketplaceSource;
68
+ }
69
+ /** `marketplace.json`. */
70
+ export interface MarketplaceManifest {
71
+ $schema?: string;
72
+ schemaVersion: 1;
73
+ /** Display name, unrelated to the repo name. */
74
+ name: string;
75
+ owner?: {
76
+ name: string;
77
+ url?: string;
78
+ };
79
+ description?: string;
80
+ plugins: MarketplaceEntry[];
81
+ }
82
+ export interface MarketplaceParseOptions {
83
+ /** Tests only: allow http on loopback addresses for `git` / `archive` URLs. */
84
+ allowInsecureLoopback?: boolean;
85
+ }
86
+ /**
87
+ * The zod schema of `marketplace.json` (https only; non-strict: unknown fields dropped). The JSON
88
+ * Schema is generated with `z.toJSONSchema(MarketplaceManifestSchema, { io: "input" })`.
89
+ */
90
+ export declare const MarketplaceManifestSchema: z.ZodType<MarketplaceManifest>;
91
+ /**
92
+ * Validates and parses `marketplace.json` (unknown fields dropped). Invalid input returns
93
+ * `{ errors }` (`<path>: <reason>`, at most 50); never throws.
94
+ */
95
+ export declare function parseMarketplaceManifest(json: unknown, opts?: MarketplaceParseOptions): MarketplaceManifest | {
96
+ errors: string[];
97
+ };
98
+ /** The entry's version: `version`, else the npm source's version, else `null` (the plugin's own). */
99
+ export declare function marketplaceEntryVersion(e: MarketplaceEntry): string | null;