@capybara-hq/plugin-sdk 0.1.0-rc.2 → 0.1.0-rc.4
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 +2 -1
- package/dist/config-ui.js +25 -7
- package/dist/ctx.d.ts +65 -1
- package/dist/directory.d.ts +11 -2
- package/dist/extension-point.d.ts +4 -0
- package/dist/extension-point.js +16 -14
- package/dist/file-preview.d.ts +8 -0
- package/dist/hooks.d.ts +73 -14
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/marketplace.d.ts +29 -2
- package/dist/marketplace.js +30 -2
- package/dist/model.d.ts +60 -2
- package/dist/model.js +22 -0
- package/dist/org-access.d.ts +27 -0
- package/dist/org-access.js +35 -0
- package/dist/permissions.d.ts +4 -1
- package/dist/plugin-manifest.d.ts +15 -2
- package/dist/plugin-manifest.js +11 -1
- package/dist/sandbox.d.ts +140 -60
- package/dist/sandbox.js +30 -6
- 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 +30 -5
- package/package.json +4 -2
- package/schemas/marketplace.v1.schema.json +36 -0
- package/schemas/plugin.v1.schema.json +29 -4
package/dist/agent-template.d.ts
CHANGED
|
@@ -21,6 +21,62 @@ export interface AgentTemplate {
|
|
|
21
21
|
maxSteps?: number;
|
|
22
22
|
/** Skill names preloaded (pinned) in the sub-agent. */
|
|
23
23
|
skills?: string[];
|
|
24
|
+
/** Further skill names the agent uses (not preloaded; P4-1). */
|
|
25
|
+
selectedSkills?: string[];
|
|
26
|
+
/** Connectors the agent needs: a connector template of the plugin, or a described need. */
|
|
27
|
+
connectors?: AgentTemplateConnector[];
|
|
28
|
+
/** The sub-agents the agent gets in its roster. */
|
|
29
|
+
subagents?: AgentTemplateSubagents;
|
|
30
|
+
/** `provider/model`: the concrete model an org template was saved with. */
|
|
31
|
+
modelRef?: string;
|
|
32
|
+
/** The agent profile's quick actions (same shape and limits). */
|
|
33
|
+
quickActions?: AgentTemplateQuickAction[];
|
|
34
|
+
/** The agent's instruction files. */
|
|
35
|
+
instructionFiles?: AgentTemplateInstructionFile[];
|
|
36
|
+
}
|
|
37
|
+
/** A quick action of the agent profile. */
|
|
38
|
+
export interface AgentTemplateQuickAction {
|
|
39
|
+
title: string;
|
|
40
|
+
body: string;
|
|
41
|
+
}
|
|
42
|
+
/** An instruction file of the agent. */
|
|
43
|
+
export interface AgentTemplateInstructionFile {
|
|
44
|
+
name: string;
|
|
45
|
+
content: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A connector entry of a template: a connector template id (`<id>` or `<plugin>:<id>`) or a need.
|
|
49
|
+
* `policies` tighten the connector's tools by tool name (`ask` / `deny`), applied once it is bound.
|
|
50
|
+
*/
|
|
51
|
+
export type AgentTemplateConnector = {
|
|
52
|
+
template: string;
|
|
53
|
+
tools?: string[];
|
|
54
|
+
policies?: Record<string, "ask" | "deny">;
|
|
55
|
+
} | {
|
|
56
|
+
needs: {
|
|
57
|
+
protocol: "mcp" | "openapi";
|
|
58
|
+
endpoint: string;
|
|
59
|
+
tools: string[];
|
|
60
|
+
label: string;
|
|
61
|
+
};
|
|
62
|
+
policies?: Record<string, "ask" | "deny">;
|
|
63
|
+
};
|
|
64
|
+
/** A sub-agent type described inside a template. */
|
|
65
|
+
export interface AgentTemplateInlineSubagent {
|
|
66
|
+
name: string;
|
|
67
|
+
description: string;
|
|
68
|
+
instructions: string;
|
|
69
|
+
/** Tool keys allowed (an allowlist; `[]` = no tools); absent = all of the agent's tools. */
|
|
70
|
+
tools?: string[];
|
|
71
|
+
/** Skill names. */
|
|
72
|
+
skills: string[];
|
|
73
|
+
model: AgentTemplateTier;
|
|
74
|
+
}
|
|
75
|
+
export interface AgentTemplateSubagents {
|
|
76
|
+
general?: boolean;
|
|
77
|
+
/** Template types as the roster writes them (`<plugin>:<name>` or `<name>`). */
|
|
78
|
+
types?: string[];
|
|
79
|
+
inline?: AgentTemplateInlineSubagent[];
|
|
24
80
|
}
|
|
25
81
|
/** Limits of agent templates. */
|
|
26
82
|
export declare const AGENT_TEMPLATE_LIMITS: {
|
|
@@ -33,9 +89,38 @@ export declare const AGENT_TEMPLATE_LIMITS: {
|
|
|
33
89
|
readonly skills: 3;
|
|
34
90
|
/** Entries of `tools` and of `disallowedTools`. */
|
|
35
91
|
readonly tools: 200;
|
|
92
|
+
/** Entries of `selectedSkills`. */
|
|
93
|
+
readonly selectedSkills: 32;
|
|
94
|
+
/** Entries of `connectors`. */
|
|
95
|
+
readonly connectors: 16;
|
|
96
|
+
/** `subagents.types` plus `subagents.inline`. */
|
|
97
|
+
readonly subagentTypes: 16;
|
|
98
|
+
/** Characters of one inline sub-agent's instructions. */
|
|
99
|
+
readonly inlineInstructionsChars: 100000;
|
|
100
|
+
/** Tools named by a connector entry (its `tools`, and the keys of its `policies`). */
|
|
101
|
+
readonly connectorTools: 200;
|
|
102
|
+
/** Quick actions, as the agent profile limits them. */
|
|
103
|
+
readonly quickActions: 9;
|
|
104
|
+
readonly quickActionTitleChars: 40;
|
|
105
|
+
readonly quickActionBodyChars: 20000;
|
|
106
|
+
/** Instruction files and their joined size, as the agent limits them. */
|
|
107
|
+
readonly instructionFiles: 20;
|
|
108
|
+
readonly instructionFilesChars: 100000;
|
|
36
109
|
};
|
|
110
|
+
/** One frontmatter key of an agent template, for documentation (served to agents by `Platform`). */
|
|
111
|
+
export interface AgentTemplateField {
|
|
112
|
+
name: string;
|
|
113
|
+
type: string;
|
|
114
|
+
required: boolean;
|
|
115
|
+
/** What it does, with its limit. */
|
|
116
|
+
description: string;
|
|
117
|
+
}
|
|
118
|
+
/** The frontmatter keys `parseAgentTemplate` reads (the body is the instructions). */
|
|
119
|
+
export declare const AGENT_TEMPLATE_FIELDS: readonly AgentTemplateField[];
|
|
37
120
|
/** The name rule of a template (the catalog item name rule). */
|
|
38
121
|
export declare const AGENT_TEMPLATE_NAME_RE: RegExp;
|
|
122
|
+
/** The name rule of an instruction file (the agent's rule, mirrored). */
|
|
123
|
+
export declare const AGENT_TEMPLATE_INSTRUCTION_FILE_NAME_RE: RegExp;
|
|
39
124
|
/**
|
|
40
125
|
* Parses an agent template file. Returns the template, or every problem found (English, for the
|
|
41
126
|
* plugin author or an admin). Keys the format does not define are ignored. Never throws.
|
package/dist/agent-template.js
CHANGED
|
@@ -17,14 +17,130 @@ export const AGENT_TEMPLATE_LIMITS = {
|
|
|
17
17
|
skills: 3,
|
|
18
18
|
/** Entries of `tools` and of `disallowedTools`. */
|
|
19
19
|
tools: 200,
|
|
20
|
+
/** Entries of `selectedSkills`. */
|
|
21
|
+
selectedSkills: 32,
|
|
22
|
+
/** Entries of `connectors`. */
|
|
23
|
+
connectors: 16,
|
|
24
|
+
/** `subagents.types` plus `subagents.inline`. */
|
|
25
|
+
subagentTypes: 16,
|
|
26
|
+
/** Characters of one inline sub-agent's instructions. */
|
|
27
|
+
inlineInstructionsChars: 100_000,
|
|
28
|
+
/** Tools named by a connector entry (its `tools`, and the keys of its `policies`). */
|
|
29
|
+
connectorTools: 200,
|
|
30
|
+
/** Quick actions, as the agent profile limits them. */
|
|
31
|
+
quickActions: 9,
|
|
32
|
+
quickActionTitleChars: 40,
|
|
33
|
+
quickActionBodyChars: 20_000,
|
|
34
|
+
/** Instruction files and their joined size, as the agent limits them. */
|
|
35
|
+
instructionFiles: 20,
|
|
36
|
+
instructionFilesChars: 100_000,
|
|
20
37
|
};
|
|
38
|
+
/** The frontmatter keys `parseAgentTemplate` reads (the body is the instructions). */
|
|
39
|
+
export const AGENT_TEMPLATE_FIELDS = [
|
|
40
|
+
{
|
|
41
|
+
name: "name",
|
|
42
|
+
type: "string",
|
|
43
|
+
required: true,
|
|
44
|
+
description: "`^[a-z0-9][a-z0-9-]{0,63}$`; the type name is `<plugin>:<name>`.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "description",
|
|
48
|
+
type: "string",
|
|
49
|
+
required: true,
|
|
50
|
+
description: "When to use it; at most 4096 characters.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "tools",
|
|
54
|
+
type: "array of tool keys",
|
|
55
|
+
required: false,
|
|
56
|
+
description: "Allowlist (at most 200); omitted = the parent's tools, `[]` = none.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "disallowedTools",
|
|
60
|
+
type: "array of tool keys",
|
|
61
|
+
required: false,
|
|
62
|
+
description: "Tool keys removed (at most 200).",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "toolPolicies",
|
|
66
|
+
type: "object",
|
|
67
|
+
required: false,
|
|
68
|
+
description: "Tool key to `ask` or `deny`; can only tighten.",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "model",
|
|
72
|
+
type: "`inherit` \\| `fast` \\| `standard` \\| `capable`",
|
|
73
|
+
required: false,
|
|
74
|
+
description: "A purpose tier, never a model id; default `inherit`.",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "effort",
|
|
78
|
+
type: "`low` \\| `medium` \\| `high` \\| `xhigh` \\| `max`",
|
|
79
|
+
required: false,
|
|
80
|
+
description: "Reasoning effort.",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "maxSteps",
|
|
84
|
+
type: "integer",
|
|
85
|
+
required: false,
|
|
86
|
+
description: "Step limit; the kernel caps it.",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "skills",
|
|
90
|
+
type: "array of skill names",
|
|
91
|
+
required: false,
|
|
92
|
+
description: "At most 3, preloaded (pinned).",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "selectedSkills",
|
|
96
|
+
type: "array of skill names",
|
|
97
|
+
required: false,
|
|
98
|
+
description: "At most 32 further skills the agent may use.",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "connectors",
|
|
102
|
+
type: "array of objects",
|
|
103
|
+
required: false,
|
|
104
|
+
description: "At most 16: `{ template, tools?, policies? }` for a connector template (`<id>` or `<plugin>:<id>`), or `{ needs: { protocol, endpoint, tools, label }, policies? }` for a described need.",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "subagents",
|
|
108
|
+
type: "object",
|
|
109
|
+
required: false,
|
|
110
|
+
description: "`{ general?, types?, inline? }`: the sub-agents in its roster (at most 16 types plus inline).",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "modelRef",
|
|
114
|
+
type: "string",
|
|
115
|
+
required: false,
|
|
116
|
+
description: "`provider/model`; set on org templates saved from an agent.",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "quickActions",
|
|
120
|
+
type: "array of objects",
|
|
121
|
+
required: false,
|
|
122
|
+
description: "At most 9 `{ title, body }` shortcuts.",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "instructionFiles",
|
|
126
|
+
type: "array of objects",
|
|
127
|
+
required: false,
|
|
128
|
+
description: "At most 20 `{ name, content }` files.",
|
|
129
|
+
},
|
|
130
|
+
];
|
|
21
131
|
/** The name rule of a template (the catalog item name rule). */
|
|
22
132
|
export const AGENT_TEMPLATE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
23
133
|
const TIERS = ["inherit", "fast", "standard", "capable"];
|
|
24
134
|
const EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
25
|
-
|
|
135
|
+
/** Room for sub-agents described inline (up to 16 x 100 000 characters of instructions, P4-1). */
|
|
136
|
+
const FRONTMATTER_MAX_CHARS = 2 * 1024 * 1024;
|
|
26
137
|
/** The kernel's tool key rule (model-visible keys): no dots, no whitespace, at most 64 chars. */
|
|
27
138
|
const TOOL_KEY_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
139
|
+
const distinct = (v) => new Set(v).size === v.length;
|
|
140
|
+
const NAME_REF_RE = /^[a-z0-9][a-z0-9-]{0,63}(:[a-z0-9][a-z0-9-]{0,63})?$/;
|
|
141
|
+
const MODEL_REF_RE = /^[^/\s]+\/\S+$/;
|
|
142
|
+
/** The name rule of an instruction file (the agent's rule, mirrored). */
|
|
143
|
+
export const AGENT_TEMPLATE_INSTRUCTION_FILE_NAME_RE = /^[^/\\]{1,64}\.md$/;
|
|
28
144
|
const chars = (s) => [...s].length;
|
|
29
145
|
const isToolKey = (v) => typeof v === "string" && TOOL_KEY_RE.test(v);
|
|
30
146
|
const toolList = (v) => Array.isArray(v) &&
|
|
@@ -112,12 +228,255 @@ export function parseAgentTemplate(text) {
|
|
|
112
228
|
}
|
|
113
229
|
if (fm.skills !== undefined) {
|
|
114
230
|
const s = fm.skills;
|
|
115
|
-
if (Array.isArray(s) && s.length <= L.skills && s.every(isSkillName))
|
|
231
|
+
if (Array.isArray(s) && s.length <= L.skills && s.every(isSkillName) && distinct(s))
|
|
116
232
|
out.skills = s;
|
|
117
233
|
else
|
|
118
234
|
errors.push(`skills must list at most ${L.skills} skill names`);
|
|
119
235
|
}
|
|
236
|
+
if (fm.selectedSkills !== undefined) {
|
|
237
|
+
const s = fm.selectedSkills;
|
|
238
|
+
if (Array.isArray(s) && s.length <= L.selectedSkills && s.every(isSkillName) && distinct(s)) {
|
|
239
|
+
out.selectedSkills = s;
|
|
240
|
+
}
|
|
241
|
+
else
|
|
242
|
+
errors.push(`selectedSkills must list at most ${L.selectedSkills} skill names`);
|
|
243
|
+
}
|
|
244
|
+
if (fm.connectors !== undefined) {
|
|
245
|
+
const c = readConnectors(fm.connectors, errors);
|
|
246
|
+
if (c)
|
|
247
|
+
out.connectors = c;
|
|
248
|
+
}
|
|
249
|
+
if (fm.subagents !== undefined) {
|
|
250
|
+
const s = readSubagents(fm.subagents, errors);
|
|
251
|
+
if (s)
|
|
252
|
+
out.subagents = s;
|
|
253
|
+
}
|
|
254
|
+
if (fm.modelRef !== undefined) {
|
|
255
|
+
if (typeof fm.modelRef === "string" &&
|
|
256
|
+
fm.modelRef.length <= 200 &&
|
|
257
|
+
MODEL_REF_RE.test(fm.modelRef)) {
|
|
258
|
+
out.modelRef = fm.modelRef;
|
|
259
|
+
}
|
|
260
|
+
else
|
|
261
|
+
errors.push("modelRef must be <provider>/<model>");
|
|
262
|
+
}
|
|
263
|
+
if (fm.quickActions !== undefined) {
|
|
264
|
+
const q = readQuickActions(fm.quickActions, errors);
|
|
265
|
+
if (q)
|
|
266
|
+
out.quickActions = q;
|
|
267
|
+
}
|
|
268
|
+
if (fm.instructionFiles !== undefined) {
|
|
269
|
+
const f = readInstructionFiles(fm.instructionFiles, errors);
|
|
270
|
+
if (f)
|
|
271
|
+
out.instructionFiles = f;
|
|
272
|
+
}
|
|
120
273
|
if (errors.length > 0)
|
|
121
274
|
return { errors };
|
|
122
275
|
return { template: out };
|
|
123
276
|
}
|
|
277
|
+
const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
278
|
+
const isStr = (v, max) => typeof v === "string" && v.trim() !== "" && chars(v) <= max;
|
|
279
|
+
const strList = (v, max, itemMax) => Array.isArray(v) && v.length <= max && v.every((x) => isStr(x, itemMax));
|
|
280
|
+
const connectorPolicies = (v) => isObj(v) &&
|
|
281
|
+
Object.keys(v).length <= AGENT_TEMPLATE_LIMITS.connectorTools &&
|
|
282
|
+
Object.entries(v).every(([k, p]) => isStr(k, 128) && (p === "ask" || p === "deny"));
|
|
283
|
+
const connectorPoliciesRule = `must map at most ${AGENT_TEMPLATE_LIMITS.connectorTools} tool names to ask or deny`;
|
|
284
|
+
function readConnectors(v, errors) {
|
|
285
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
286
|
+
if (!Array.isArray(v) || v.length > L.connectors) {
|
|
287
|
+
errors.push(`connectors must be a list of at most ${L.connectors} entries`);
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
const out = [];
|
|
291
|
+
let ok = true;
|
|
292
|
+
v.forEach((e, i) => {
|
|
293
|
+
const at = `connectors[${i}]`;
|
|
294
|
+
if (!isObj(e)) {
|
|
295
|
+
errors.push(`${at} must be { template } or { needs }`);
|
|
296
|
+
ok = false;
|
|
297
|
+
}
|
|
298
|
+
else if ("template" in e) {
|
|
299
|
+
if (typeof e.template !== "string" || !NAME_REF_RE.test(e.template)) {
|
|
300
|
+
errors.push(`${at}.template must be a connector template id (<id> or <plugin>:<id>)`);
|
|
301
|
+
ok = false;
|
|
302
|
+
}
|
|
303
|
+
else if (e.tools !== undefined && !strList(e.tools, L.connectorTools, 128)) {
|
|
304
|
+
errors.push(`${at}.tools must list at most ${L.connectorTools} tool names`);
|
|
305
|
+
ok = false;
|
|
306
|
+
}
|
|
307
|
+
else if (e.policies !== undefined && !connectorPolicies(e.policies)) {
|
|
308
|
+
errors.push(`${at}.policies ${connectorPoliciesRule}`);
|
|
309
|
+
ok = false;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
out.push({
|
|
313
|
+
template: e.template,
|
|
314
|
+
...(e.tools !== undefined && { tools: e.tools }),
|
|
315
|
+
...(e.policies !== undefined && {
|
|
316
|
+
policies: { ...e.policies },
|
|
317
|
+
}),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if ("needs" in e) {
|
|
322
|
+
const n = e.needs;
|
|
323
|
+
if (!isObj(n)) {
|
|
324
|
+
errors.push(`${at}.needs must be an object`);
|
|
325
|
+
ok = false;
|
|
326
|
+
}
|
|
327
|
+
else if (n.protocol !== "mcp" && n.protocol !== "openapi") {
|
|
328
|
+
errors.push(`${at}.needs.protocol must be mcp or openapi`);
|
|
329
|
+
ok = false;
|
|
330
|
+
}
|
|
331
|
+
else if (!isStr(n.endpoint, 2048)) {
|
|
332
|
+
errors.push(`${at}.needs.endpoint must be a non-empty string of at most 2048 characters`);
|
|
333
|
+
ok = false;
|
|
334
|
+
}
|
|
335
|
+
else if (!strList(n.tools, L.connectorTools, 128)) {
|
|
336
|
+
errors.push(`${at}.needs.tools must list at most ${L.connectorTools} tool names`);
|
|
337
|
+
ok = false;
|
|
338
|
+
}
|
|
339
|
+
else if (!isStr(n.label, 80)) {
|
|
340
|
+
errors.push(`${at}.needs.label must be a non-empty string of at most 80 characters`);
|
|
341
|
+
ok = false;
|
|
342
|
+
}
|
|
343
|
+
else if (e.policies !== undefined && !connectorPolicies(e.policies)) {
|
|
344
|
+
errors.push(`${at}.policies ${connectorPoliciesRule}`);
|
|
345
|
+
ok = false;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
out.push({
|
|
349
|
+
needs: {
|
|
350
|
+
protocol: n.protocol,
|
|
351
|
+
endpoint: n.endpoint,
|
|
352
|
+
tools: n.tools,
|
|
353
|
+
label: n.label,
|
|
354
|
+
},
|
|
355
|
+
...(e.policies !== undefined && {
|
|
356
|
+
policies: { ...e.policies },
|
|
357
|
+
}),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
errors.push(`${at} must have template or needs`);
|
|
363
|
+
ok = false;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
return ok ? out : undefined;
|
|
367
|
+
}
|
|
368
|
+
function readSubagents(v, errors) {
|
|
369
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
370
|
+
if (!isObj(v)) {
|
|
371
|
+
errors.push("subagents must be an object");
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
const out = {};
|
|
375
|
+
const n0 = errors.length;
|
|
376
|
+
if (v.general !== undefined) {
|
|
377
|
+
if (typeof v.general === "boolean")
|
|
378
|
+
out.general = v.general;
|
|
379
|
+
else
|
|
380
|
+
errors.push("subagents.general must be true or false");
|
|
381
|
+
}
|
|
382
|
+
if (v.types !== undefined) {
|
|
383
|
+
if (Array.isArray(v.types) &&
|
|
384
|
+
v.types.every((t) => typeof t === "string" && NAME_REF_RE.test(t)) &&
|
|
385
|
+
distinct(v.types)) {
|
|
386
|
+
out.types = v.types;
|
|
387
|
+
}
|
|
388
|
+
else
|
|
389
|
+
errors.push("subagents.types must list sub-agent type names");
|
|
390
|
+
}
|
|
391
|
+
if (v.inline !== undefined) {
|
|
392
|
+
if (!Array.isArray(v.inline))
|
|
393
|
+
errors.push("subagents.inline must be a list");
|
|
394
|
+
else {
|
|
395
|
+
const inline = [];
|
|
396
|
+
v.inline.forEach((e, i) => {
|
|
397
|
+
const at = `subagents.inline[${i}]`;
|
|
398
|
+
if (!isObj(e)) {
|
|
399
|
+
errors.push(`${at} must be an object`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const before = errors.length;
|
|
403
|
+
if (typeof e.name !== "string" || !AGENT_TEMPLATE_NAME_RE.test(e.name)) {
|
|
404
|
+
errors.push(`${at}.name must match ^[a-z0-9][a-z0-9-]{0,63}$`);
|
|
405
|
+
}
|
|
406
|
+
if (!isStr(e.description, L.descriptionChars)) {
|
|
407
|
+
errors.push(`${at}.description must be a non-empty string of at most ${L.descriptionChars} characters`);
|
|
408
|
+
}
|
|
409
|
+
if (typeof e.instructions !== "string" ||
|
|
410
|
+
e.instructions.trim() === "" ||
|
|
411
|
+
chars(e.instructions) > L.inlineInstructionsChars) {
|
|
412
|
+
errors.push(`${at}.instructions must be a non-empty string of at most ${L.inlineInstructionsChars} characters`);
|
|
413
|
+
}
|
|
414
|
+
if (e.tools !== undefined && !toolList(e.tools)) {
|
|
415
|
+
errors.push(`${at}.tools must be ${toolListRule}`);
|
|
416
|
+
}
|
|
417
|
+
if (!(Array.isArray(e.skills) &&
|
|
418
|
+
e.skills.length <= L.skills &&
|
|
419
|
+
e.skills.every(isSkillName) &&
|
|
420
|
+
distinct(e.skills))) {
|
|
421
|
+
errors.push(`${at}.skills must list at most ${L.skills} skill names`);
|
|
422
|
+
}
|
|
423
|
+
if (!TIERS.includes(e.model)) {
|
|
424
|
+
errors.push(`${at}.model must be one of ${TIERS.join(", ")}`);
|
|
425
|
+
}
|
|
426
|
+
if (errors.length === before)
|
|
427
|
+
inline.push(e);
|
|
428
|
+
});
|
|
429
|
+
out.inline = inline;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if ((out.types?.length ?? 0) + (out.inline?.length ?? 0) > L.subagentTypes) {
|
|
433
|
+
errors.push(`subagents.types and subagents.inline together must be at most ${L.subagentTypes}`);
|
|
434
|
+
}
|
|
435
|
+
return errors.length === n0 ? out : undefined;
|
|
436
|
+
}
|
|
437
|
+
function readQuickActions(v, errors) {
|
|
438
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
439
|
+
if (!Array.isArray(v) || v.length > L.quickActions) {
|
|
440
|
+
errors.push(`quickActions must be a list of at most ${L.quickActions} entries`);
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
const n0 = errors.length;
|
|
444
|
+
const out = [];
|
|
445
|
+
v.forEach((e, i) => {
|
|
446
|
+
if (isObj(e) &&
|
|
447
|
+
isStr(e.title, L.quickActionTitleChars) &&
|
|
448
|
+
isStr(e.body, L.quickActionBodyChars)) {
|
|
449
|
+
out.push({ title: e.title, body: e.body });
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
errors.push(`quickActions[${i}] must have a title (at most ${L.quickActionTitleChars} characters) and a body (at most ${L.quickActionBodyChars})`);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
return errors.length === n0 ? out : undefined;
|
|
456
|
+
}
|
|
457
|
+
function readInstructionFiles(v, errors) {
|
|
458
|
+
const L = AGENT_TEMPLATE_LIMITS;
|
|
459
|
+
if (!Array.isArray(v) || v.length > L.instructionFiles) {
|
|
460
|
+
errors.push(`instructionFiles must be a list of at most ${L.instructionFiles} entries`);
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
const n0 = errors.length;
|
|
464
|
+
const out = [];
|
|
465
|
+
let total = 0;
|
|
466
|
+
v.forEach((e, i) => {
|
|
467
|
+
if (isObj(e) &&
|
|
468
|
+
typeof e.name === "string" &&
|
|
469
|
+
AGENT_TEMPLATE_INSTRUCTION_FILE_NAME_RE.test(e.name) &&
|
|
470
|
+
typeof e.content === "string") {
|
|
471
|
+
total += chars(e.content);
|
|
472
|
+
out.push({ name: e.name, content: e.content });
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
errors.push(`instructionFiles[${i}] must have a name (<name>.md) and a content string`);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
if (errors.length === n0 && total > L.instructionFilesChars) {
|
|
479
|
+
errors.push(`instructionFiles must total at most ${L.instructionFilesChars} characters`);
|
|
480
|
+
}
|
|
481
|
+
return errors.length === n0 ? out : undefined;
|
|
482
|
+
}
|
package/dist/config-ui.d.ts
CHANGED
|
@@ -105,7 +105,8 @@ export declare function ui<T extends z.ZodTypeAny>(schema: T, hints: ConfigUiHin
|
|
|
105
105
|
/**
|
|
106
106
|
* An extension config schema as a JSON Schema for forms (the input side, defaults included, no
|
|
107
107
|
* `$schema`):
|
|
108
|
-
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `
|
|
108
|
+
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `org`
|
|
109
|
+
* (`orgAccess`) becomes `"x-org"`, `ui`
|
|
109
110
|
* becomes `"x-ui"` (a JSON copy of the hints); `title` / `description` are filled from
|
|
110
111
|
* `x-ui.label.en` / `x-ui.help.en` when the node has none;
|
|
111
112
|
* - unrepresentable types become `{ "x-unsupported": true }` instead of throwing;
|
package/dist/config-ui.js
CHANGED
|
@@ -48,7 +48,8 @@ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
|
48
48
|
/**
|
|
49
49
|
* An extension config schema as a JSON Schema for forms (the input side, defaults included, no
|
|
50
50
|
* `$schema`):
|
|
51
|
-
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `
|
|
51
|
+
* - meta `secret: true` becomes `"x-secret": true`, `hot: true` becomes `"x-hot": true`, `org`
|
|
52
|
+
* (`orgAccess`) becomes `"x-org"`, `ui`
|
|
52
53
|
* becomes `"x-ui"` (a JSON copy of the hints); `title` / `description` are filled from
|
|
53
54
|
* `x-ui.label.en` / `x-ui.help.en` when the node has none;
|
|
54
55
|
* - unrepresentable types become `{ "x-unsupported": true }` instead of throwing;
|
|
@@ -68,6 +69,9 @@ export function configJsonSchema(schema) {
|
|
|
68
69
|
if (js.hot === true)
|
|
69
70
|
js["x-hot"] = true;
|
|
70
71
|
delete js.hot;
|
|
72
|
+
if (js.org !== undefined)
|
|
73
|
+
js["x-org"] = js.org;
|
|
74
|
+
delete js.org;
|
|
71
75
|
if ("ui" in js) {
|
|
72
76
|
// A JSON copy: hints are data, and the scrub must never mutate the schema's meta.
|
|
73
77
|
const hints = JSON.parse(JSON.stringify(js.ui ?? null));
|
|
@@ -275,8 +279,21 @@ export function validateConfigUi(schema, level) {
|
|
|
275
279
|
}
|
|
276
280
|
return node;
|
|
277
281
|
};
|
|
278
|
-
const visit = (node, path, isRoot) => {
|
|
282
|
+
const visit = (node, path, isRoot, depth) => {
|
|
279
283
|
const report = (message) => problems.push({ path, message });
|
|
284
|
+
if (node["x-org"] !== undefined) {
|
|
285
|
+
const org = node["x-org"];
|
|
286
|
+
if (level !== "deployment")
|
|
287
|
+
report(`x-org is only allowed at the deployment level, not ${level}`);
|
|
288
|
+
else if (depth !== 1)
|
|
289
|
+
report("x-org is only allowed on a top-level field of the config");
|
|
290
|
+
else if (org !== "readonly" && org !== "override") {
|
|
291
|
+
report(`x-org must be "readonly" or "override", not ${JSON.stringify(org)}`);
|
|
292
|
+
}
|
|
293
|
+
else if (org === "readonly" && node["x-secret"] === true) {
|
|
294
|
+
report('x-org "readonly" on a secret field: an org never sees a secret, so there is nothing to show');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
280
297
|
const text = (value, what, max, plainOk = false) => {
|
|
281
298
|
if (value === undefined)
|
|
282
299
|
return;
|
|
@@ -316,13 +333,14 @@ export function validateConfigUi(schema, level) {
|
|
|
316
333
|
if (isObj(props)) {
|
|
317
334
|
for (const [k, child] of Object.entries(props)) {
|
|
318
335
|
if (isObj(child))
|
|
319
|
-
visit(child, path === "" ? k : `${path}.${k}`, false);
|
|
336
|
+
visit(child, path === "" ? k : `${path}.${k}`, false, depth + 1);
|
|
320
337
|
}
|
|
321
338
|
}
|
|
322
|
-
if (isObj(shape.items))
|
|
323
|
-
visit(shape.items, path === "" ? "items" : `${path}.items`, false);
|
|
339
|
+
if (isObj(shape.items)) {
|
|
340
|
+
visit(shape.items, path === "" ? "items" : `${path}.items`, false, depth + 1);
|
|
341
|
+
}
|
|
324
342
|
if (isObj(shape.additionalProperties)) {
|
|
325
|
-
visit(shape.additionalProperties, path === "" ? "additionalProperties" : `${path}.additionalProperties`, false);
|
|
343
|
+
visit(shape.additionalProperties, path === "" ? "additionalProperties" : `${path}.additionalProperties`, false, depth + 1);
|
|
326
344
|
}
|
|
327
345
|
};
|
|
328
346
|
const checkHints = (h, node, isRoot, report, text) => {
|
|
@@ -573,7 +591,7 @@ export function validateConfigUi(schema, level) {
|
|
|
573
591
|
});
|
|
574
592
|
}
|
|
575
593
|
};
|
|
576
|
-
visit(schema, "", true);
|
|
594
|
+
visit(schema, "", true, 0);
|
|
577
595
|
if (hintsBytes > L.hintsBytes) {
|
|
578
596
|
problems.push({
|
|
579
597
|
path: "",
|
package/dist/ctx.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Kysely } from "kysely";
|
|
2
2
|
import type { ExtensionPointRef } from "./extension-point.js";
|
|
3
3
|
import type { AvailableHook, HookHandler, HookOptions } from "./hooks.js";
|
|
4
|
+
import type { ModelEmbeddingInfo, ModelKind } from "./model.js";
|
|
4
5
|
import type { ObjectHead, PresignedUpload, PresignGetOptions } from "./storage.js";
|
|
5
6
|
import type { ToolHandler, ToolSpec } from "./tool.js";
|
|
6
7
|
import type { EmbedClaims, UiActionArgs, UiActionResult, UiRenderArgs, UiView } from "./ui.js";
|
|
@@ -58,6 +59,65 @@ export interface ExtensionStorage {
|
|
|
58
59
|
ttlSeconds: number;
|
|
59
60
|
}): Promise<PresignedUpload>;
|
|
60
61
|
}
|
|
62
|
+
/** A `ctx.models.embed` request: texts embedded for one org with the model mapped to a purpose. */
|
|
63
|
+
export interface ExtensionEmbedRequest {
|
|
64
|
+
orgId: string;
|
|
65
|
+
/** A purpose this extension declared in `permissions.models` with `kind: "embedding"`. */
|
|
66
|
+
purpose: string;
|
|
67
|
+
/** The texts, one vector each; split into batches by the model's `maxBatch` by the kernel. */
|
|
68
|
+
input: string[];
|
|
69
|
+
/** One of the model's `embedding.dimensions`; the model's default when absent. */
|
|
70
|
+
dimensions?: number;
|
|
71
|
+
inputType?: "query" | "document";
|
|
72
|
+
/**
|
|
73
|
+
* Aborts this call (e.g. the tool call's `signal`); combined with `ctx.signal`. Batches not yet
|
|
74
|
+
* sent are not sent; batches already answered are still recorded as usage.
|
|
75
|
+
*/
|
|
76
|
+
signal?: AbortSignal;
|
|
77
|
+
}
|
|
78
|
+
/** A `ctx.models.embed` result. */
|
|
79
|
+
export interface ExtensionEmbedResult {
|
|
80
|
+
/** One vector per input, in input order. */
|
|
81
|
+
vectors: number[][];
|
|
82
|
+
/** The model that made them (a changed model invalidates stored vectors). */
|
|
83
|
+
model: {
|
|
84
|
+
provider: string;
|
|
85
|
+
id: string;
|
|
86
|
+
};
|
|
87
|
+
dimensions: number;
|
|
88
|
+
}
|
|
89
|
+
/** The model a purpose maps to in an org (`ctx.models.describe`). */
|
|
90
|
+
export interface ExtensionModelTarget {
|
|
91
|
+
provider: string;
|
|
92
|
+
id: string;
|
|
93
|
+
kind: ModelKind;
|
|
94
|
+
/** Present for an embedding model. */
|
|
95
|
+
embedding?: ModelEmbeddingInfo;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Models by purpose (`permissions.models`): the purpose → model mapping is chosen by org admins
|
|
99
|
+
* (and the deployment default by the super admin), never by the extension. Only purposes this
|
|
100
|
+
* extension declared with `kind: "embedding"` are accepted (`model.purpose_unknown` otherwise), and
|
|
101
|
+
* only for an org that enabled the extension (`extension.not_enabled`). Usage is recorded by the
|
|
102
|
+
* kernel and reported through `usage.report` (purpose `embedding`).
|
|
103
|
+
*/
|
|
104
|
+
export interface ExtensionModels {
|
|
105
|
+
/**
|
|
106
|
+
* Embeds `input` with the org's effective model for `purpose`. Rejects with `model.unconfigured`
|
|
107
|
+
* when the purpose maps to no model the org can use, `validation_failed` for dimensions the model
|
|
108
|
+
* lacks, and a `ModelError` when the provider fails (a result of the wrong count or dimension is a
|
|
109
|
+
* `ModelError` of kind `server`). Not retried by the kernel. Each call fires the `model.request`
|
|
110
|
+
* hook first (`purpose: "embedding"`, every text of the call): when a policy hook denies it, the
|
|
111
|
+
* call rejects with an error whose `code` is `model.hook_denied` (HTTP 403 if it escapes an
|
|
112
|
+
* extension route), and nothing is sent or billed.
|
|
113
|
+
*/
|
|
114
|
+
embed(req: ExtensionEmbedRequest): Promise<ExtensionEmbedResult>;
|
|
115
|
+
/** The org's effective model for `purpose`, or null when it maps to no model the org can use. */
|
|
116
|
+
describe(req: {
|
|
117
|
+
orgId: string;
|
|
118
|
+
purpose: string;
|
|
119
|
+
}): Promise<ExtensionModelTarget | null>;
|
|
120
|
+
}
|
|
61
121
|
/** 传给插件 `setup` 的上下文。不提供定时器与进程内共享状态;kv 写入必须带 TTL。 */
|
|
62
122
|
export interface ExtensionContext {
|
|
63
123
|
kv: {
|
|
@@ -94,6 +154,8 @@ export interface ExtensionContext {
|
|
|
94
154
|
};
|
|
95
155
|
/** 对象存储(需 `permissions.storage`,见 {@link ExtensionStorage})。 */
|
|
96
156
|
storage: ExtensionStorage;
|
|
157
|
+
/** Models by purpose (requires `permissions.models`, see {@link ExtensionModels}). */
|
|
158
|
+
models: ExtensionModels;
|
|
97
159
|
/** 已切到本插件 schema(withSchema)的 Kysely 实例。 */
|
|
98
160
|
db: Kysely<any>;
|
|
99
161
|
http: {
|
|
@@ -109,7 +171,9 @@ export interface ExtensionContext {
|
|
|
109
171
|
* manifest's `requires` / `optional`. Without `orgId` it is the deployment-level view and returns
|
|
110
172
|
* synchronously; with `orgId` it returns a Promise, and orgScoped extension points (such as
|
|
111
173
|
* `channel`, `directory`) are filtered by that org's enablement (queried every time, not cached);
|
|
112
|
-
* `
|
|
174
|
+
* with `orgId`, a provider whose effective config is not valid in that org is excluded too;
|
|
175
|
+
* `get` rejects with `ExtensionPointUnavailableError` when the provider is not enabled in that
|
|
176
|
+
* org or its config is not valid there.
|
|
113
177
|
* When there is no provider, `get` throws `ExtensionPointUnavailableError` and `list` returns an
|
|
114
178
|
* empty list.
|
|
115
179
|
*/
|