@noimosai/platform-cli 0.1.3 → 0.1.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 +12 -56
- package/bin/noimos-platform.mjs +3 -4
- package/dist/cli/cli.js +477 -87
- package/dist/cli/command-help.js +349 -23
- package/dist/cli/download.js +25 -25
- package/dist/cli/input-error.js +48 -0
- package/dist/cli/local-agents.js +199 -0
- package/dist/cli/mcp-supervisor.js +136 -0
- package/dist/cli/noimos-cli.js +72 -0
- package/dist/cli/project-archive.js +27 -0
- package/dist/cli/project-files.js +25 -0
- package/dist/cli/project-sync.js +259 -0
- package/dist/cli/setup-lock.js +1 -0
- package/dist/cli/setup.js +347 -0
- package/dist/cli/skills.js +142 -0
- package/dist/cli/team-harness-targets.js +337 -0
- package/dist/cli/team-harness.js +1562 -0
- package/dist/cli/team-resources.js +222 -0
- package/dist/cli/team-skills.js +457 -0
- package/dist/cli/update-bootstrap.js +437 -0
- package/dist/cli/update-release.js +212 -0
- package/dist/cli/version.js +1 -0
- package/dist/cli/wallet.js +85 -0
- package/dist/client/agent-run.js +1425 -0
- package/dist/client/browser.js +905 -0
- package/dist/client/distribution.js +2 -0
- package/dist/client/evm-wallet.js +214 -0
- package/dist/client/execution-context.js +14 -0
- package/dist/client/failure-recovery.js +128 -0
- package/dist/client/index.js +4 -225
- package/dist/client/local-input.js +33 -12
- package/dist/client/local-profile-lock.js +52 -0
- package/dist/client/local-profile.js +181 -0
- package/dist/client/local-projects.js +131 -0
- package/dist/client/local-setup.js +93 -0
- package/dist/client/realtime-voice.js +184 -0
- package/dist/client/tool-name-hints.js +48 -0
- package/dist/client/wallet-access.js +214 -0
- package/dist/mcp/agent-server.js +345 -0
- package/dist/mcp/context.js +86 -0
- package/dist/mcp/host-schema.js +76 -0
- package/dist/mcp/icon.js +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/instructions.js +26 -0
- package/dist/mcp/local-projects.js +42 -0
- package/dist/mcp/local-upload.js +39 -0
- package/dist/mcp/noimos-runtime.js +18 -0
- package/dist/mcp/noimos-server.js +38 -0
- package/dist/mcp/operations.js +40 -0
- package/dist/mcp/parallel-result.js +17 -0
- package/dist/mcp/project-sync.js +105 -0
- package/dist/mcp/results-reply.js +15 -0
- package/dist/mcp/runtime.js +81 -0
- package/dist/mcp/server.js +410 -0
- package/dist/mcp/team-skill.js +63 -0
- package/dist/mcp/update-notice.js +75 -0
- package/dist/mcp/version.js +1 -0
- package/dist/schema/accountRequestMethods.js +1 -0
- package/dist/schema/boundedBody.js +30 -0
- package/dist/schema/cronValidator.js +150 -0
- package/dist/schema/platformApiKey.js +14 -0
- package/dist/schema/platformAppAuth.js +11 -0
- package/dist/schema/platformBillingConstants.js +4 -0
- package/dist/schema/platformCapabilityFamilies.js +293 -0
- package/dist/schema/platformConnectionCatalog.js +39 -0
- package/dist/schema/platformConnectionPolicy.js +33 -0
- package/dist/schema/platformCustomTools.js +148 -0
- package/dist/schema/platformEndUserApi.js +62 -0
- package/dist/schema/platformFunding.js +10 -0
- package/dist/schema/platformHttpConstraints.js +12 -0
- package/dist/schema/platformLocalSetup.js +25 -0
- package/dist/schema/platformMcpTools.js +7 -0
- package/dist/schema/platformMetaTools.js +63 -0
- package/dist/schema/platformMonitorSchedule.js +123 -0
- package/dist/schema/platformMonitors.js +159 -0
- package/dist/schema/platformOAuth.js +13 -0
- package/dist/schema/platformProjectPermissions.js +33 -0
- package/dist/schema/platformPublicExecution.js +202 -21
- package/dist/schema/platformRoutes.js +122 -0
- package/dist/schema/platformSkills.js +11 -0
- package/dist/schema/platformTeamSecretName.js +3 -0
- package/dist/schema/platformTeamSkillSlug.js +1 -0
- package/dist/schema/platformTeamSkills.js +302 -0
- package/dist/schema/platformToolAccess.js +62 -0
- package/dist/schema/platformTriggers.js +153 -0
- package/dist/schema/platformWorkflows.js +100 -0
- package/dist/schema/scheduleTimeZone.js +15 -0
- package/dist/schema/toolDiscovery.js +71 -0
- package/dist/schema/toolInputRepair.js +122 -0
- package/dist/schema/toolResultContext.js +51 -0
- package/dist/schema/untrustedContent.js +35 -0
- package/npm-shrinkwrap.json +73 -0
- package/package.json +6 -3
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Document, isScalar, parseDocument, visit } from "yaml";
|
|
3
|
+
import { projectScopeSchema } from "./platformPublicExecution.js";
|
|
4
|
+
import { PLATFORM_CUSTOM_TOOL_LIMITS, platformVersionAuthorSchema } from "./platformCustomTools.js";
|
|
5
|
+
import { PLATFORM_TEAM_SKILL_SLUG } from "./platformTeamSkillSlug.js";
|
|
6
|
+
export { PLATFORM_TEAM_SKILL_SLUG } from "./platformTeamSkillSlug.js";
|
|
7
|
+
export const PLATFORM_TEAM_SKILL_LIMITS = {
|
|
8
|
+
perProject: 200,
|
|
9
|
+
versions: 50,
|
|
10
|
+
titleChars: 80,
|
|
11
|
+
nameChars: 40,
|
|
12
|
+
descriptionChars: 1024,
|
|
13
|
+
contentBytes: 32_768,
|
|
14
|
+
frontmatterChars: 8_192,
|
|
15
|
+
markdownBytes: 65_536,
|
|
16
|
+
files: 200,
|
|
17
|
+
fileBytes: 5 * 1024 * 1024,
|
|
18
|
+
packageBytes: 10 * 1024 * 1024,
|
|
19
|
+
requestBytes: 20 * 1024 * 1024,
|
|
20
|
+
};
|
|
21
|
+
export const PLATFORM_TEAM_STORAGE_BYTES = 100_000_000;
|
|
22
|
+
export const platformSkillStorageSchema = z.object({ usedBytes: z.number().int().nonnegative().safe(), limitBytes: z.number().int().positive().safe() });
|
|
23
|
+
export const platformSkillPathSchema = z.string().min(1).max(260).refine(path => !/[\\\u0000-\u001f\u007f:]/u.test(path.normalize("NFKC")) && !path.startsWith("/") && path.normalize("NFKC").split("/").every(part => part && !/[. ]$/.test(part)
|
|
24
|
+
&& ![".git", "node_modules", "__proto__", "constructor", "prototype"].includes(part.toLowerCase()) && !/^\.env(?:\.|$)/i.test(part)
|
|
25
|
+
&& !/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part)), "Invalid relative skill file path");
|
|
26
|
+
export const platformSkillFilePathSchema = platformSkillPathSchema.refine(path => path.normalize("NFKC").toLowerCase() !== "skill.md", "SKILL.md is the main skill content");
|
|
27
|
+
export const platformSkillFileSchema = z.object({
|
|
28
|
+
path: platformSkillFilePathSchema,
|
|
29
|
+
data: z.string().max(Math.ceil(PLATFORM_TEAM_SKILL_LIMITS.fileBytes / 3) * 4).regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/),
|
|
30
|
+
executable: z.boolean().optional(),
|
|
31
|
+
}).strict();
|
|
32
|
+
export const skillFileBytes = (file) => file.data.length / 4 * 3 - (file.data.endsWith("==") ? 2 : file.data.endsWith("=") ? 1 : 0);
|
|
33
|
+
export const platformSkillFilesSchema = z.array(platformSkillFileSchema).max(PLATFORM_TEAM_SKILL_LIMITS.files).superRefine((files, context) => {
|
|
34
|
+
const names = new Set();
|
|
35
|
+
let bytes = 0;
|
|
36
|
+
for (const [index, file] of files.entries()) {
|
|
37
|
+
const name = file.path.normalize("NFKC").toLowerCase(), size = skillFileBytes(file);
|
|
38
|
+
if (names.has(name) || files.some(other => other !== file && other.path.normalize("NFKC").toLowerCase().startsWith(`${name}/`)))
|
|
39
|
+
context.addIssue({ code: "custom", path: [index, "path"], message: "Skill file paths collide" });
|
|
40
|
+
names.add(name);
|
|
41
|
+
bytes += size;
|
|
42
|
+
if (size > PLATFORM_TEAM_SKILL_LIMITS.fileBytes)
|
|
43
|
+
context.addIssue({ code: "custom", path: [index, "data"], message: "Skill file is too large" });
|
|
44
|
+
}
|
|
45
|
+
if (bytes > PLATFORM_TEAM_SKILL_LIMITS.packageBytes)
|
|
46
|
+
context.addIssue({ code: "custom", message: "Skill package is too large" });
|
|
47
|
+
});
|
|
48
|
+
export function readSkillMarkdown(source) {
|
|
49
|
+
const text = source.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
|
|
50
|
+
let content = text, frontmatter = "", name, description, hasMetadata = false;
|
|
51
|
+
if (text.startsWith("---\n")) {
|
|
52
|
+
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(text);
|
|
53
|
+
if (!match)
|
|
54
|
+
throw new Error("The SKILL.md frontmatter has no closing ---.");
|
|
55
|
+
frontmatter = match[1];
|
|
56
|
+
content = text.slice(match[0].length);
|
|
57
|
+
if (frontmatter.length > 8192)
|
|
58
|
+
throw new Error("The SKILL.md frontmatter is too large.");
|
|
59
|
+
const document = parseDocument(frontmatter, { uniqueKeys: true });
|
|
60
|
+
if (document.errors.length)
|
|
61
|
+
throw new Error("The SKILL.md frontmatter is not valid YAML.");
|
|
62
|
+
const fields = z.object({ name: z.string().optional(), description: z.string().optional() }).passthrough().parse(document.toJS({ maxAliasCount: 50 }));
|
|
63
|
+
name = fields.name;
|
|
64
|
+
description = fields.description?.trim();
|
|
65
|
+
hasMetadata = Object.keys(fields).some(key => key !== "name" && key !== "description");
|
|
66
|
+
}
|
|
67
|
+
content = content.trim();
|
|
68
|
+
const heading = /^#\s+(.+?)\s*#*\s*(?:\n|$)/.exec(content);
|
|
69
|
+
if (heading)
|
|
70
|
+
content = content.slice(heading[0].length).trim();
|
|
71
|
+
return { name, description, title: heading?.[1], content, frontmatter, hasMetadata };
|
|
72
|
+
}
|
|
73
|
+
function validSkillFrontmatter(value) {
|
|
74
|
+
if (!value.trim())
|
|
75
|
+
return true;
|
|
76
|
+
try {
|
|
77
|
+
readSkillMarkdown(`---\n${value}\n---\n`);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
let indirect = false;
|
|
83
|
+
visit(parseDocument(value), { Alias() { indirect = true; return visit.BREAK; },
|
|
84
|
+
Pair(_, pair) { if (isScalar(pair.key) && pair.key.value === "<<") {
|
|
85
|
+
indirect = true;
|
|
86
|
+
return visit.BREAK;
|
|
87
|
+
} } });
|
|
88
|
+
return !indirect;
|
|
89
|
+
}
|
|
90
|
+
function frontmatterObject(frontmatter) {
|
|
91
|
+
const document = parseDocument(frontmatter || "", { merge: true, uniqueKeys: true });
|
|
92
|
+
if (document.errors.length)
|
|
93
|
+
throw new Error("The SKILL.md frontmatter is not valid YAML.");
|
|
94
|
+
const value = document.toJS({ maxAliasCount: 50 });
|
|
95
|
+
if (value === null || value === undefined)
|
|
96
|
+
return {};
|
|
97
|
+
return z.record(z.string(), z.unknown()).parse(value);
|
|
98
|
+
}
|
|
99
|
+
export const SKILL_EXECUTING_FIELDS = ["hooks", "allowed-tools", "allowedTools", "mcpServers"];
|
|
100
|
+
export const SKILL_EXECUTING_FILES = ["mcp.json"];
|
|
101
|
+
export const skillExecutingFiles = (files) => (files ?? []).filter(file => SKILL_EXECUTING_FILES.includes(file.path.toLowerCase()));
|
|
102
|
+
export function omitFrontmatterFields(frontmatter, fields) {
|
|
103
|
+
const value = frontmatterObject(frontmatter);
|
|
104
|
+
const removed = fields.filter(field => Object.hasOwn(value, field));
|
|
105
|
+
if (!removed.length)
|
|
106
|
+
return { frontmatter, removed };
|
|
107
|
+
for (const field of removed)
|
|
108
|
+
delete value[field];
|
|
109
|
+
return { frontmatter: Object.keys(value).length ? new Document(value).toString() : "", removed };
|
|
110
|
+
}
|
|
111
|
+
export function restoreFrontmatterFields(local, stored, fields) {
|
|
112
|
+
const target = parseDocument(local);
|
|
113
|
+
if (target.errors.length)
|
|
114
|
+
throw new Error("The SKILL.md frontmatter is not valid YAML.");
|
|
115
|
+
const source = frontmatterObject(stored), present = frontmatterObject(local);
|
|
116
|
+
const restored = fields.filter(field => Object.hasOwn(source, field) && !Object.hasOwn(present, field));
|
|
117
|
+
for (const field of restored)
|
|
118
|
+
target.set(field, source[field]);
|
|
119
|
+
return restored.length ? target.toString() : local;
|
|
120
|
+
}
|
|
121
|
+
export function frontmatterValue(frontmatter, field) {
|
|
122
|
+
const value = frontmatterObject(frontmatter);
|
|
123
|
+
return Object.hasOwn(value, field) ? value[field] : undefined;
|
|
124
|
+
}
|
|
125
|
+
export function renderSkillMarkdown(skill, name) {
|
|
126
|
+
const document = parseDocument(skill.frontmatter || "");
|
|
127
|
+
if (document.errors.length)
|
|
128
|
+
throw new Error("The SKILL.md frontmatter is not valid YAML.");
|
|
129
|
+
if (name)
|
|
130
|
+
document.set("name", name);
|
|
131
|
+
document.set("description", skill.description);
|
|
132
|
+
return `---\n${document.toString()}---\n# ${skill.title}\n\n${skill.content}\n`;
|
|
133
|
+
}
|
|
134
|
+
export const PLATFORM_TEAM_SKILL_KINDS = ["instructions", "skill", "agent", "rule", "hook", "folder", "mcp_server", "permission"];
|
|
135
|
+
export const platformTeamSkillKindSchema = z.enum(PLATFORM_TEAM_SKILL_KINDS);
|
|
136
|
+
export const platformTeamSkillListKindSchema = z.enum([...PLATFORM_TEAM_SKILL_KINDS, "all"]);
|
|
137
|
+
export const teamSkillKindHoldsFiles = (kind) => kind === "skill" || kind === "hook" || kind === "folder";
|
|
138
|
+
export const teamSkillKindNeedsAdmin = (kind) => kind === "hook" || kind === "mcp_server" || kind === "permission";
|
|
139
|
+
export const AGENT_EXECUTING_FIELDS = ["hooks", "mcpServers", "permissionMode"];
|
|
140
|
+
export const teamItemExecutingFields = (kind) => kind === "skill" ? SKILL_EXECUTING_FIELDS : kind === "agent" ? AGENT_EXECUTING_FIELDS : [];
|
|
141
|
+
export function teamItemNeedsAdmin(kind, definition) {
|
|
142
|
+
if (teamSkillKindNeedsAdmin(kind))
|
|
143
|
+
return true;
|
|
144
|
+
if (kind === "skill" && skillExecutingFiles(definition.files).length)
|
|
145
|
+
return true;
|
|
146
|
+
const fields = teamItemExecutingFields(kind), frontmatter = definition.frontmatter;
|
|
147
|
+
if (!fields.length || !frontmatter?.trim())
|
|
148
|
+
return false;
|
|
149
|
+
try {
|
|
150
|
+
return omitFrontmatterFields(frontmatter, fields).removed.length > 0;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export const TEAM_HOOK_DIR_PLACEHOLDER = "${RELVO_HOOK_DIR}";
|
|
157
|
+
export const TEAM_AGENT_PLACEHOLDER = "${RELVO_AGENT}";
|
|
158
|
+
function readJsonContent(content, key, what) {
|
|
159
|
+
let value;
|
|
160
|
+
try {
|
|
161
|
+
value = JSON.parse(content);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
throw new Error(`${what} is JSON.`);
|
|
165
|
+
}
|
|
166
|
+
const wrapped = z.object({ [key]: z.unknown() }).strict().safeParse(value);
|
|
167
|
+
return wrapped.success ? wrapped.data[key] : value;
|
|
168
|
+
}
|
|
169
|
+
const hookHandlerSchema = z.object({ type: z.string().min(1) }).passthrough();
|
|
170
|
+
const hookGroupSchema = z.object({ matcher: z.string().optional(), hooks: z.array(hookHandlerSchema).min(1) }).passthrough();
|
|
171
|
+
export const platformTeamHookConfigSchema = z.record(z.string().regex(/^[A-Z][A-Za-z]{1,40}$/, "Hook events are names such as PreToolUse"), z.array(hookGroupSchema).min(1));
|
|
172
|
+
export function readTeamHookConfig(content) {
|
|
173
|
+
const what = "A hook's content is a JSON object of events, as under \"hooks\" in settings.json";
|
|
174
|
+
const parsed = platformTeamHookConfigSchema.safeParse(readJsonContent(content, "hooks", what));
|
|
175
|
+
if (!parsed.success)
|
|
176
|
+
throw new Error(`${what}: ${parsed.error.issues[0]?.message ?? "invalid"}.`);
|
|
177
|
+
return parsed.data;
|
|
178
|
+
}
|
|
179
|
+
const environmentName = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,99}$/);
|
|
180
|
+
export const platformTeamMcpServerSchema = z.union([
|
|
181
|
+
z.object({ type: z.literal("stdio").optional(), command: z.string().trim().min(1).max(500), args: z.array(z.string().max(2000)).max(100).optional(),
|
|
182
|
+
env: z.record(environmentName, z.string().max(2000)).optional() }).strict(),
|
|
183
|
+
z.object({ type: z.literal("http"), url: z.url({ protocol: /^https?$/ }).max(2000), headers: z.record(z.string().regex(/^[A-Za-z0-9-]{1,100}$/), z.string().max(2000)).optional(),
|
|
184
|
+
bearerTokenEnvVar: environmentName.optional() }).strict(),
|
|
185
|
+
]);
|
|
186
|
+
export function readTeamMcpServer(content) {
|
|
187
|
+
const what = "An MCP server's content is one server as in .mcp.json: {\"command\", \"args\", \"env\"} or {\"type\": \"http\", \"url\"}";
|
|
188
|
+
const parsed = platformTeamMcpServerSchema.safeParse(readJsonContent(content, "mcpServer", what));
|
|
189
|
+
if (!parsed.success)
|
|
190
|
+
throw new Error(`${what}: ${parsed.error.issues[0]?.message ?? "invalid"}.`);
|
|
191
|
+
return parsed.data;
|
|
192
|
+
}
|
|
193
|
+
const permissionRules = z.array(z.string().trim().min(1).max(500)).max(200).optional();
|
|
194
|
+
export const platformTeamPermissionsSchema = z.object({ allow: permissionRules, ask: permissionRules, deny: permissionRules }).strict()
|
|
195
|
+
.refine(rules => Boolean(rules.allow?.length || rules.ask?.length || rules.deny?.length), "Name at least one rule");
|
|
196
|
+
export function readTeamPermissions(content) {
|
|
197
|
+
const what = "Permission rules are JSON as under \"permissions\" in settings.json: {\"allow\", \"ask\", \"deny\"}";
|
|
198
|
+
const parsed = platformTeamPermissionsSchema.safeParse(readJsonContent(content, "permissions", what));
|
|
199
|
+
if (!parsed.success)
|
|
200
|
+
throw new Error(`${what}: ${parsed.error.issues[0]?.message ?? "invalid"}.`);
|
|
201
|
+
return parsed.data;
|
|
202
|
+
}
|
|
203
|
+
export const TEAM_HOOK_PLUGIN_HARNESSES = ["pi", "opencode", "amp"];
|
|
204
|
+
export const TEAM_HOOK_NATIVE_FOLDER = "native";
|
|
205
|
+
export function teamHookPluginHarnesses(files) {
|
|
206
|
+
const named = new Set();
|
|
207
|
+
for (const file of files ?? []) {
|
|
208
|
+
const [folder, harness, ...rest] = file.path.split("/");
|
|
209
|
+
if (folder !== TEAM_HOOK_NATIVE_FOLDER)
|
|
210
|
+
continue;
|
|
211
|
+
if (!rest.length || !TEAM_HOOK_PLUGIN_HARNESSES.some(name => name === harness))
|
|
212
|
+
return null;
|
|
213
|
+
named.add(harness);
|
|
214
|
+
}
|
|
215
|
+
return TEAM_HOOK_PLUGIN_HARNESSES.filter(name => named.has(name));
|
|
216
|
+
}
|
|
217
|
+
export function teamSkillKindIssues(kind, definition) {
|
|
218
|
+
const fields = [];
|
|
219
|
+
if (definition.files?.length && !teamSkillKindHoldsFiles(kind))
|
|
220
|
+
fields.push("files");
|
|
221
|
+
const reader = kind === "hook" ? readTeamHookConfig : kind === "mcp_server" ? readTeamMcpServer : kind === "permission" ? readTeamPermissions : undefined;
|
|
222
|
+
if (reader) {
|
|
223
|
+
try {
|
|
224
|
+
reader(definition.content);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
fields.push("content");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (kind === "hook") {
|
|
231
|
+
const plugins = teamHookPluginHarnesses(definition.files);
|
|
232
|
+
if (plugins === null)
|
|
233
|
+
fields.push("files");
|
|
234
|
+
else if (!plugins.length && !fields.includes("content") && !Object.keys(readTeamHookConfig(definition.content)).length)
|
|
235
|
+
fields.push("content");
|
|
236
|
+
}
|
|
237
|
+
return fields;
|
|
238
|
+
}
|
|
239
|
+
export const TEAM_HARNESSES = ["claude", "codex", "pi", "dsh", "opencode", "gemini", "kimi", "qwen", "cursor", "copilot", "amp", "droid"];
|
|
240
|
+
export const teamHarnessSchema = z.enum(TEAM_HARNESSES);
|
|
241
|
+
export const TEAM_HARNESS_NAMES = { claude: "Claude Code", codex: "Codex", pi: "Pi", dsh: "deepseek-harness", opencode: "OpenCode",
|
|
242
|
+
gemini: "Gemini CLI", kimi: "Kimi Code", qwen: "Qwen Code", cursor: "Cursor", copilot: "GitHub Copilot CLI", amp: "Amp", droid: "Factory Droid" };
|
|
243
|
+
export const platformTeamHarnessesSchema = z.array(z.enum(["all", ...TEAM_HARNESSES])).max(TEAM_HARNESSES.length + 1)
|
|
244
|
+
.describe("The coding agents to write this item for when members sync: all (the default), or names such as claude, codex, gemini. On update, [] keeps the current choice.");
|
|
245
|
+
export const storedTeamHarnesses = (requested) => requested.includes("all") ? []
|
|
246
|
+
: TEAM_HARNESSES.filter(harness => requested.includes(harness));
|
|
247
|
+
export const teamItemTargets = (harnesses, harness) => !harnesses?.length || harnesses.includes(harness);
|
|
248
|
+
export const platformTeamSkillSlugSchema = z.string().regex(PLATFORM_TEAM_SKILL_SLUG).describe("Lowercase letters, digits and underscores, starting with a letter.");
|
|
249
|
+
export const platformTeamItemNameSchema = z.string().trim().min(1).max(PLATFORM_TEAM_SKILL_LIMITS.nameChars).regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/)
|
|
250
|
+
.describe("The user-chosen name in coding agents and ZIP downloads. Lowercase letters, digits and single hyphens, starting with a letter.");
|
|
251
|
+
const optionalItemName = z.union([platformTeamItemNameSchema, z.literal("")]).optional().transform(value => value || undefined);
|
|
252
|
+
export const teamItemName = (item) => item.name ?? item.slug.replaceAll("_", "-");
|
|
253
|
+
export const teamItemInstallName = (item) => `relvo-team-${teamItemName(item)}`;
|
|
254
|
+
export const platformTeamSkillDefinitionSchema = z.object({
|
|
255
|
+
title: z.string().trim().min(1).max(PLATFORM_TEAM_SKILL_LIMITS.titleChars),
|
|
256
|
+
description: z.string().trim().min(1).max(PLATFORM_TEAM_SKILL_LIMITS.descriptionChars).describe("When an agent should follow this skill; agents choose skills by it."),
|
|
257
|
+
content: z.string().trim().min(1).max(PLATFORM_TEAM_SKILL_LIMITS.contentBytes).refine(value => new TextEncoder().encode(value).byteLength <= PLATFORM_TEAM_SKILL_LIMITS.contentBytes, "The content is too long")
|
|
258
|
+
.describe("Markdown: the steps, the tools to use (by name) and what to check. No secrets."),
|
|
259
|
+
files: platformSkillFilesSchema.optional(),
|
|
260
|
+
frontmatter: z.string().max(PLATFORM_TEAM_SKILL_LIMITS.frontmatterChars).refine(validSkillFrontmatter, "Invalid skill YAML metadata").optional(),
|
|
261
|
+
}).strict();
|
|
262
|
+
export const platformTeamSkillCreateSchema = platformTeamSkillDefinitionSchema.extend({ slug: platformTeamSkillSlugSchema, name: optionalItemName,
|
|
263
|
+
kind: platformTeamSkillKindSchema.optional().describe("skill (the default): steps an agent follows when a task fits; agent: a subagent's instructions, " +
|
|
264
|
+
"with description saying when to delegate to it; instructions: shared base instructions in AGENTS.md or CLAUDE.md; rule: additional, optionally path-scoped rules; folder: shared files, content saying what they are; " +
|
|
265
|
+
"hook, mcp_server, permission: JSON content, written only by team admins in the console. Only a skill, hook or folder holds files."),
|
|
266
|
+
harnesses: platformTeamHarnessesSchema.optional() }).strict();
|
|
267
|
+
export const platformTeamSkillUpdateSchema = platformTeamSkillDefinitionSchema.extend({ expectedVersion: z.number().int().min(1), name: optionalItemName, harnesses: platformTeamHarnessesSchema.optional() }).strict();
|
|
268
|
+
export const platformTeamSkillRestoreSchema = z.object({ version: z.number().int().min(1), expectedVersion: z.number().int().min(1) }).strict();
|
|
269
|
+
export const platformTeamSkillSchema = z.object({ slug: platformTeamSkillSlugSchema, name: platformTeamItemNameSchema.optional(), kind: platformTeamSkillKindSchema.default("skill"),
|
|
270
|
+
adminOnly: z.boolean().default(false),
|
|
271
|
+
harnesses: z.array(teamHarnessSchema).default([]), title: z.string(), description: z.string(), content: z.string(),
|
|
272
|
+
files: platformSkillFilesSchema.optional(), frontmatter: z.string().optional(),
|
|
273
|
+
version: z.number().int().min(1), createdAt: z.string(), updatedAt: z.string() });
|
|
274
|
+
export const platformTeamSkillSummarySchema = platformTeamSkillSchema.omit({ content: true, files: true, frontmatter: true });
|
|
275
|
+
export const platformTeamSkillVersionSchema = z.object({ version: z.number().int().min(1), title: z.string(), createdAt: z.string() });
|
|
276
|
+
export const platformSuggestionSchema = z.discriminatedUnion("kind", [
|
|
277
|
+
z.object({ kind: z.literal("custom_tool"), occurrences: z.number().int().min(2), lastSeenAt: z.string(),
|
|
278
|
+
executions: z.array(z.string()).min(1).max(3),
|
|
279
|
+
code: z.string().min(1).max(PLATFORM_CUSTOM_TOOL_LIMITS.codeChars) }),
|
|
280
|
+
z.object({ kind: z.literal("skill"), occurrences: z.number().int().min(2), lastSeenAt: z.string(), tools: z.array(z.string()).min(2).max(3) }),
|
|
281
|
+
]);
|
|
282
|
+
export const platformSuggestionsSchema = z.object({ suggestions: z.array(platformSuggestionSchema).max(20), since: z.string() });
|
|
283
|
+
export const platformTeamSkillHistorySchema = platformTeamSkillVersionSchema.extend({ author: platformVersionAuthorSchema });
|
|
284
|
+
export const platformTeamSkillVersionDetailSchema = platformTeamSkillSchema.pick({ title: true, description: true, content: true, files: true, frontmatter: true })
|
|
285
|
+
.extend({ version: z.number().int().min(1), createdAt: z.string(), author: platformVersionAuthorSchema });
|
|
286
|
+
const skillInput = projectScopeSchema.extend({ slug: platformTeamSkillSlugSchema });
|
|
287
|
+
const skillResult = z.object({ skill: platformTeamSkillSchema }).strict();
|
|
288
|
+
export const platformTeamSkillContracts = {
|
|
289
|
+
listDeveloperTeamSkills: { input: projectScopeSchema.strict(), output: z.object({ skills: z.array(platformTeamSkillSummarySchema.extend({ canEdit: z.boolean(), canDelete: z.boolean() })),
|
|
290
|
+
canCreate: z.boolean(), canCreateAdminKinds: z.boolean().optional(),
|
|
291
|
+
storage: platformSkillStorageSchema.optional() }).strict() },
|
|
292
|
+
getDeveloperTeamSkill: { input: skillInput.strict(), output: skillResult },
|
|
293
|
+
getDeveloperTeamSkillAccess: { input: skillInput.strict(), output: z.object({ members: z.array(z.object({ memberId: z.string(), email: z.string(), name: z.string().nullable(),
|
|
294
|
+
image: z.string().nullish(), canRead: z.boolean(), canEdit: z.boolean(), canDelete: z.boolean() }).strict()) }).strict() },
|
|
295
|
+
createDeveloperTeamSkill: { input: projectScopeSchema.extend({ skill: platformTeamSkillCreateSchema }).strict(), output: skillResult },
|
|
296
|
+
updateDeveloperTeamSkill: { input: skillInput.extend({ skill: platformTeamSkillUpdateSchema }).strict(), output: skillResult },
|
|
297
|
+
restoreDeveloperTeamSkill: { input: skillInput.merge(platformTeamSkillRestoreSchema).strict(), output: skillResult },
|
|
298
|
+
deleteDeveloperTeamSkill: { input: skillInput.strict(), output: z.object({ success: z.literal(true) }).strict() },
|
|
299
|
+
listDeveloperTeamSkillVersions: { input: skillInput.strict(), output: z.object({ versions: z.array(platformTeamSkillHistorySchema) }).strict() },
|
|
300
|
+
getDeveloperTeamSkillVersion: { input: skillInput.extend({ version: z.number().int().min(1) }).strict(), output: z.object({ version: platformTeamSkillVersionDetailSchema }).strict() },
|
|
301
|
+
listDeveloperSuggestions: { input: projectScopeSchema.strict(), output: platformSuggestionsSchema },
|
|
302
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { platformCustomToolNameSchema } from "./platformCustomTools.js";
|
|
3
|
+
import { PLATFORM_TEAM_SECRET_NAME } from "./platformTeamSecretName.js";
|
|
4
|
+
import { PLATFORM_TEAM_SKILL_SLUG } from "./platformTeamSkillSlug.js";
|
|
5
|
+
import { platformToolNameSchema } from "./platformPublicExecution.js";
|
|
6
|
+
const connectionId = z.string().regex(/^[A-Za-z0-9-]{1,64}$/), projectId = connectionId;
|
|
7
|
+
export const platformConnectionGrantSchema = z.templateLiteral(["connection:", connectionId]);
|
|
8
|
+
export const platformConnectionOperationGrantSchema = z.templateLiteral(["connection:", connectionId, "/", z.string().regex(/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/)]);
|
|
9
|
+
export const platformSkillGrantSchema = z.templateLiteral(["skill:", projectId, "/", z.string().regex(PLATFORM_TEAM_SKILL_SLUG)]);
|
|
10
|
+
export const platformSecretGrantSchema = z.templateLiteral(["secret:", projectId, "/", z.string().regex(PLATFORM_TEAM_SECRET_NAME)]);
|
|
11
|
+
export const memberToolAccessSchema = z.array(z.union([platformCustomToolNameSchema, platformConnectionGrantSchema, platformConnectionOperationGrantSchema,
|
|
12
|
+
platformSkillGrantSchema, platformSecretGrantSchema])).max(20_000).nullable();
|
|
13
|
+
export const keyToolAccessSchema = memberToolAccessSchema;
|
|
14
|
+
export const platformTeamAccessSchema = z.object({
|
|
15
|
+
members: z.array(z.object({ memberId: z.string(), email: z.string(), name: z.string().nullable(), role: z.string(), toolAccess: memberToolAccessSchema }).loose()),
|
|
16
|
+
invitations: z.array(z.object({ id: z.string(), email: z.string(), role: z.string(), expiresAt: z.string(), toolAccess: memberToolAccessSchema }).loose()),
|
|
17
|
+
});
|
|
18
|
+
export const platformMemberAccessUpdateSchema = z.object({ toolAccess: memberToolAccessSchema }).strict();
|
|
19
|
+
export const platformTeamQuerySchema = z.object({
|
|
20
|
+
memberOffset: z.coerce.number().int().min(0).max(100_000).default(0),
|
|
21
|
+
invitationOffset: z.coerce.number().int().min(0).max(100_000).default(0),
|
|
22
|
+
limit: z.coerce.number().int().min(1).max(100).default(10),
|
|
23
|
+
}).strict();
|
|
24
|
+
const teamRole = z.enum(["OWNER", "ADMIN", "EDITOR", "BILLING", "MEMBER"]);
|
|
25
|
+
const rosterMember = z.object({ memberId: z.string(), email: z.string(), name: z.string().nullable(), role: teamRole });
|
|
26
|
+
const rosterInvitation = z.object({ id: z.string(), email: z.string(), role: teamRole, expiresAt: z.string() });
|
|
27
|
+
export const platformTeamContextSchema = z.object({
|
|
28
|
+
team: z.object({ id: z.string(), name: z.string() }),
|
|
29
|
+
project: z.object({ id: z.string(), name: z.string(), role: z.enum(["ADMIN", "DEVELOPER", "VIEWER"]) }),
|
|
30
|
+
member: rosterMember,
|
|
31
|
+
scopes: z.array(z.string()),
|
|
32
|
+
canListMembers: z.boolean(),
|
|
33
|
+
members: z.object({ items: z.array(rosterMember), nextOffset: z.number().int().nonnegative().nullable() }).nullable(),
|
|
34
|
+
invitations: z.object({ items: z.array(rosterInvitation), nextOffset: z.number().int().nonnegative().nullable() }).nullable(),
|
|
35
|
+
});
|
|
36
|
+
export const memberTeamSkillAccessSchema = z.enum(["EDIT", "OWN", "READ"]);
|
|
37
|
+
export const platformProjectRoleSchema = z.enum(["ADMIN", "DEVELOPER", "VIEWER"]);
|
|
38
|
+
export const PLATFORM_REALTIME_VOICE_TOOL = "realtime_voice";
|
|
39
|
+
export const projectPermissionPolicySchema = z.object({
|
|
40
|
+
tools: z.array(platformToolNameSchema).max(20_000).nullable(),
|
|
41
|
+
teamSkillAccess: memberTeamSkillAccessSchema,
|
|
42
|
+
}).strict();
|
|
43
|
+
export function projectPermissionPolicy(set) {
|
|
44
|
+
return set ? { tools: set.toolExecutionRestricted ? set.toolExecution : null, teamSkillAccess: set.teamSkillAccess } : null;
|
|
45
|
+
}
|
|
46
|
+
export function projectSkillAccess(member) {
|
|
47
|
+
const existing = member.role === "OWNER" || member.role === "ADMIN" ? "EDIT" : member.teamSkillAccess;
|
|
48
|
+
const cap = member.projectPolicy?.teamSkillAccess;
|
|
49
|
+
return !cap ? existing : existing === "READ" || cap === "READ" ? "READ" : existing === "OWN" || cap === "OWN" ? "OWN" : "EDIT";
|
|
50
|
+
}
|
|
51
|
+
export const projectPermissionSetDefinitionSchema = projectPermissionPolicySchema.extend({
|
|
52
|
+
name: z.string().trim().min(1).max(100), role: platformProjectRoleSchema,
|
|
53
|
+
});
|
|
54
|
+
export const projectPermissionSetSchema = projectPermissionSetDefinitionSchema.extend({
|
|
55
|
+
id: z.string(), version: z.number().int().positive(), archivedAt: z.string().nullable(),
|
|
56
|
+
});
|
|
57
|
+
export const projectPermissionSetRefSchema = projectPermissionSetSchema.pick({ id: true, name: true, version: true });
|
|
58
|
+
export const projectAssignmentSchema = z.object({
|
|
59
|
+
projectId: z.string().min(1).max(128), role: platformProjectRoleSchema, permissionSetId: z.string().min(1).max(128).nullable().optional(),
|
|
60
|
+
}).strict();
|
|
61
|
+
export const projectAssignmentsSchema = z.array(projectAssignmentSchema).min(1).max(100)
|
|
62
|
+
.refine(assignments => new Set(assignments.map(assignment => assignment.projectId)).size === assignments.length);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { projectScopeSchema, platformJsonObjectSchema, platformMicrosSchema, PLATFORM_SEARCH_MAX_LENGTH } from "./platformPublicExecution.js";
|
|
3
|
+
import { platformMonitorDestinationInputSchema, platformMonitorDestinationSchema, platformMonitorRecipientSchema, PLATFORM_MONITOR_DESTINATION_LIMITS } from "./platformMonitors.js";
|
|
4
|
+
import { platformWorkflowSlugSchema } from "./platformWorkflows.js";
|
|
5
|
+
import { PLATFORM_FREE_TOOL_OVERAGE_MICROS } from "./platformBillingConstants.js";
|
|
6
|
+
export const PLATFORM_TRIGGER_LIMITS = {
|
|
7
|
+
perProject: 50,
|
|
8
|
+
perTeam: 100,
|
|
9
|
+
nameChars: 80,
|
|
10
|
+
budgetChars: 16,
|
|
11
|
+
eventsPage: 100,
|
|
12
|
+
eventDataBytes: 65_536,
|
|
13
|
+
ingressBodyBytes: 1_048_576,
|
|
14
|
+
retentionDays: 30,
|
|
15
|
+
keptEvents: 10_000,
|
|
16
|
+
eventsPerBatch: 100,
|
|
17
|
+
typesPage: 100,
|
|
18
|
+
};
|
|
19
|
+
export const PLATFORM_TRIGGER_INTERVALS = { "1m": 60, "5m": 300, "15m": 900, "1h": 3_600 };
|
|
20
|
+
export const platformTriggerIntervalSchema = z.enum(["1m", "5m", "15m", "1h"]);
|
|
21
|
+
export const PLATFORM_TRIGGER_DEFAULT_MAXIMUM_CHARGE_MICROS_PER_POLL = PLATFORM_FREE_TOOL_OVERAGE_MICROS;
|
|
22
|
+
export const PLATFORM_TRIGGER_ID = /^trg_[A-Za-z0-9_-]{1,100}$/;
|
|
23
|
+
export const platformTriggerIdSchema = z.string().regex(PLATFORM_TRIGGER_ID);
|
|
24
|
+
export const PLATFORM_TRIGGER_TYPE = /^[a-z0-9][a-z0-9_]{0,63}\.[A-Za-z0-9][A-Za-z0-9_.:]{0,79}$/;
|
|
25
|
+
export const platformTriggerTypeIdSchema = z.string().regex(PLATFORM_TRIGGER_TYPE);
|
|
26
|
+
const toolName = z.string().regex(/^[a-z][a-z0-9_]{1,127}$/);
|
|
27
|
+
const connectionId = z.string().min(1).max(128);
|
|
28
|
+
const pointer = z.string().max(200).regex(/^(\/[^/]*)*$/);
|
|
29
|
+
export const platformTriggerDestinationKindSchema = z.enum(["webhook", "slack", "discord", "email", "workflow"]);
|
|
30
|
+
const workflowDestination = z.object({
|
|
31
|
+
kind: z.literal("workflow"),
|
|
32
|
+
label: z.string().trim().max(PLATFORM_MONITOR_DESTINATION_LIMITS.labelChars).default(""),
|
|
33
|
+
workflow: platformWorkflowSlugSchema,
|
|
34
|
+
input: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}$/).default("event").describe("The workflow input that receives the event object."),
|
|
35
|
+
maximumChargeMicros: platformMicrosSchema,
|
|
36
|
+
}).strict();
|
|
37
|
+
export const platformTriggerDestinationInputSchema = z.union([platformMonitorDestinationInputSchema, workflowDestination]);
|
|
38
|
+
export const platformTriggerDestinationSchema = platformMonitorDestinationSchema.extend({
|
|
39
|
+
kind: platformTriggerDestinationKindSchema,
|
|
40
|
+
workflow: z.object({ slug: platformWorkflowSlugSchema, input: z.string(), maximumChargeMicros: platformMicrosSchema }).nullable().default(null),
|
|
41
|
+
});
|
|
42
|
+
const common = {
|
|
43
|
+
name: z.string().trim().max(PLATFORM_TRIGGER_LIMITS.nameChars).default("").describe("Empty names the trigger after its type or operation."),
|
|
44
|
+
connectionId: connectionId.describe("The connection of the app, from /tools/connections."),
|
|
45
|
+
destinations: z.array(platformTriggerDestinationInputSchema).max(PLATFORM_MONITOR_DESTINATION_LIMITS.perMonitor).default([]),
|
|
46
|
+
};
|
|
47
|
+
export const platformTriggerCreateSchema = z.discriminatedUnion("kind", [
|
|
48
|
+
z.object({
|
|
49
|
+
kind: z.literal("poll"),
|
|
50
|
+
tool: toolName.describe("A read operation of the connected app, for example github__issues_list_for_repo."),
|
|
51
|
+
arguments: platformJsonObjectSchema.default({}).describe("The operation's arguments without connectionId."),
|
|
52
|
+
interval: platformTriggerIntervalSchema.default("5m"),
|
|
53
|
+
itemsPath: pointer.optional().describe("Where the list of items is in the response body. Omit to use the largest list."),
|
|
54
|
+
maximumChargeMicrosPerPoll: platformMicrosSchema.default(PLATFORM_TRIGGER_DEFAULT_MAXIMUM_CHARGE_MICROS_PER_POLL),
|
|
55
|
+
...common,
|
|
56
|
+
}).strict(),
|
|
57
|
+
z.object({
|
|
58
|
+
kind: z.literal("webhook"),
|
|
59
|
+
type: platformTriggerTypeIdSchema.describe("A trigger type from /trigger-types, for example github.push."),
|
|
60
|
+
config: platformJsonObjectSchema.default({}).describe("The type's settings, valid against its configSchema."),
|
|
61
|
+
...common,
|
|
62
|
+
}).strict(),
|
|
63
|
+
]);
|
|
64
|
+
export const platformTriggerUpdateSchema = z.object({
|
|
65
|
+
enabled: z.boolean().optional(),
|
|
66
|
+
name: z.string().trim().max(PLATFORM_TRIGGER_LIMITS.nameChars).optional().describe("Empty or omitted keeps the name."),
|
|
67
|
+
}).strict();
|
|
68
|
+
export const platformTriggerStatusSchema = z.enum(["active", "disabled", "failed"]);
|
|
69
|
+
export const platformTriggerSchema = z.object({
|
|
70
|
+
id: platformTriggerIdSchema,
|
|
71
|
+
name: z.string(),
|
|
72
|
+
kind: z.enum(["poll", "webhook"]),
|
|
73
|
+
app: z.string(),
|
|
74
|
+
connectionId: z.string(),
|
|
75
|
+
status: platformTriggerStatusSchema,
|
|
76
|
+
statusReason: z.string().nullable(),
|
|
77
|
+
tool: z.string().nullable(),
|
|
78
|
+
arguments: platformJsonObjectSchema.nullable(),
|
|
79
|
+
interval: platformTriggerIntervalSchema.nullable(),
|
|
80
|
+
itemsPath: z.string().nullable(),
|
|
81
|
+
maximumChargeMicrosPerPoll: platformMicrosSchema.nullable(),
|
|
82
|
+
type: z.string().nullable(),
|
|
83
|
+
config: platformJsonObjectSchema.nullable(),
|
|
84
|
+
apiKeyId: z.string(),
|
|
85
|
+
lastCheckedAt: z.string().nullable(),
|
|
86
|
+
lastEventAt: z.string().nullable(),
|
|
87
|
+
nextCheckAt: z.string().nullable(),
|
|
88
|
+
lastError: z.string().nullable(),
|
|
89
|
+
createdAt: z.string(),
|
|
90
|
+
destinations: z.array(platformTriggerDestinationSchema),
|
|
91
|
+
});
|
|
92
|
+
export const platformTriggerEventSchema = z.object({
|
|
93
|
+
id: z.string().regex(/^\d{1,20}$/),
|
|
94
|
+
triggerId: platformTriggerIdSchema,
|
|
95
|
+
type: z.string(),
|
|
96
|
+
key: z.string(),
|
|
97
|
+
title: z.string(),
|
|
98
|
+
url: z.string(),
|
|
99
|
+
data: platformJsonObjectSchema,
|
|
100
|
+
receivedAt: z.string(),
|
|
101
|
+
});
|
|
102
|
+
export const platformTriggerEventsQuerySchema = z.object({
|
|
103
|
+
after: z.string().regex(/^\d{1,20}$/).optional(),
|
|
104
|
+
limit: z.coerce.number().int().min(1).max(PLATFORM_TRIGGER_LIMITS.eventsPage).default(50),
|
|
105
|
+
}).strict();
|
|
106
|
+
export const platformTriggerEventsSchema = z.object({ events: z.array(platformTriggerEventSchema), nextAfter: z.string().nullable() });
|
|
107
|
+
export const platformTriggerTypeSchema = z.object({
|
|
108
|
+
type: platformTriggerTypeIdSchema,
|
|
109
|
+
app: z.string(),
|
|
110
|
+
name: z.string(),
|
|
111
|
+
description: z.string(),
|
|
112
|
+
configSchema: platformJsonObjectSchema,
|
|
113
|
+
setup: z.enum(["automatic", "app"]),
|
|
114
|
+
available: z.boolean(),
|
|
115
|
+
unavailableReason: z.string().nullable(),
|
|
116
|
+
});
|
|
117
|
+
export const platformTriggerOperationSchema = z.object({ tool: toolName, app: z.string(), description: z.string(), inputSchema: platformJsonObjectSchema });
|
|
118
|
+
export const platformTriggerTypesQuerySchema = z.object({
|
|
119
|
+
app: z.string().regex(/^[a-z0-9][a-z0-9_]{0,63}$/),
|
|
120
|
+
connectionId: z.string().max(128).optional(),
|
|
121
|
+
query: z.string().trim().max(PLATFORM_SEARCH_MAX_LENGTH).default(""),
|
|
122
|
+
offset: z.coerce.number().int().min(0).max(100_000).default(0),
|
|
123
|
+
limit: z.coerce.number().int().min(1).max(PLATFORM_TRIGGER_LIMITS.typesPage).default(50),
|
|
124
|
+
}).strict();
|
|
125
|
+
export const platformTriggerTypesSchema = z.object({
|
|
126
|
+
types: z.array(platformTriggerTypeSchema),
|
|
127
|
+
operations: z.array(platformTriggerOperationSchema),
|
|
128
|
+
total: z.number().int().min(0),
|
|
129
|
+
});
|
|
130
|
+
export const platformTriggerDeliverySchema = z.object({
|
|
131
|
+
version: z.literal(1),
|
|
132
|
+
type: z.literal("trigger.event"),
|
|
133
|
+
deliveryId: z.string(),
|
|
134
|
+
trigger: z.object({ id: platformTriggerIdSchema, name: z.string(), kind: z.enum(["poll", "webhook"]), app: z.string(), source: z.string(), connectionId: z.string() }).strict(),
|
|
135
|
+
event: platformTriggerEventSchema.omit({ triggerId: true }),
|
|
136
|
+
}).strict();
|
|
137
|
+
const projectTrigger = projectScopeSchema.extend({ triggerId: platformTriggerIdSchema });
|
|
138
|
+
export const platformTriggerContracts = {
|
|
139
|
+
listDeveloperTriggers: { input: projectScopeSchema.strict(), output: z.object({ triggers: z.array(platformTriggerSchema) }).strict() },
|
|
140
|
+
createDeveloperTrigger: { input: projectScopeSchema.extend({ apiKeyId: z.string().min(1).max(128), trigger: platformTriggerCreateSchema }).strict(),
|
|
141
|
+
output: z.object({ trigger: platformTriggerSchema }).strict() },
|
|
142
|
+
updateDeveloperTrigger: { input: projectTrigger.merge(platformTriggerUpdateSchema).strict(), output: z.object({ trigger: platformTriggerSchema }).strict() },
|
|
143
|
+
deleteDeveloperTrigger: { input: projectTrigger.strict(), output: z.object({ success: z.literal(true) }).strict() },
|
|
144
|
+
listDeveloperTriggerEvents: {
|
|
145
|
+
input: projectScopeSchema.extend({ triggerId: platformTriggerIdSchema.optional(), after: platformTriggerEventsQuerySchema.shape.after,
|
|
146
|
+
limit: z.number().int().min(1).max(PLATFORM_TRIGGER_LIMITS.eventsPage).default(50) }).strict(),
|
|
147
|
+
output: platformTriggerEventsSchema,
|
|
148
|
+
},
|
|
149
|
+
listDeveloperTriggerTypes: { input: projectScopeSchema.merge(platformTriggerTypesQuerySchema).strict(), output: platformTriggerTypesSchema },
|
|
150
|
+
addDeveloperTriggerDestination: { input: projectTrigger.extend({ destination: platformTriggerDestinationInputSchema }).strict(), output: z.object({ trigger: platformTriggerSchema }).strict() },
|
|
151
|
+
removeDeveloperTriggerDestination: { input: projectTrigger.extend({ destinationId: platformTriggerDestinationSchema.shape.id }).strict(), output: z.object({ trigger: platformTriggerSchema }).strict() },
|
|
152
|
+
listDeveloperTriggerRecipients: { input: projectScopeSchema.strict(), output: z.object({ recipients: z.array(platformMonitorRecipientSchema) }).strict() },
|
|
153
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { platformExecutionIdSchema, platformJsonObjectSchema, platformMicrosSchema, platformProviderChoiceSchema, platformToolNameSchema } from "./platformPublicExecution.js";
|
|
3
|
+
import { platformCustomToolInputSchemaSchema } from "./platformCustomTools.js";
|
|
4
|
+
export const PLATFORM_WORKFLOW_LIMITS = {
|
|
5
|
+
perProject: 50,
|
|
6
|
+
steps: 20,
|
|
7
|
+
versions: 50,
|
|
8
|
+
activeRunsPerProject: 10,
|
|
9
|
+
titleChars: 80,
|
|
10
|
+
descriptionChars: 1_000,
|
|
11
|
+
stepsBytes: 32_768,
|
|
12
|
+
inputBytes: 16_384,
|
|
13
|
+
};
|
|
14
|
+
export const PLATFORM_WORKFLOW_SLUG = /^[a-z][a-z0-9_]{1,39}$/;
|
|
15
|
+
export const platformWorkflowSlugSchema = z.string().regex(PLATFORM_WORKFLOW_SLUG).describe("Lowercase letters, digits and underscores, starting with a letter.");
|
|
16
|
+
export const platformWorkflowStepIdSchema = z.string().regex(/^[a-z][a-z0-9_]{0,39}$/);
|
|
17
|
+
export const platformWorkflowRunIdSchema = z.string().regex(/^wfr_[A-Za-z0-9_-]{1,100}$/);
|
|
18
|
+
const pointerSchema = z.string().max(500).regex(/^(?:\/(?:[^~/]|~[01])*)*$/, "A JSON Pointer such as /data/companies/0/name");
|
|
19
|
+
const referenceSchema = z.union([z.object({ $input: z.string(), path: z.string().optional() }).strict(), z.object({ $step: z.string(), path: z.string().optional() }).strict()]);
|
|
20
|
+
export function platformWorkflowReference(value) {
|
|
21
|
+
const parsed = referenceSchema.safeParse(value);
|
|
22
|
+
if (!parsed.success)
|
|
23
|
+
return undefined;
|
|
24
|
+
const path = parsed.data.path ?? "";
|
|
25
|
+
return "$input" in parsed.data ? { kind: "input", name: parsed.data.$input, path } : { kind: "step", step: parsed.data.$step, path };
|
|
26
|
+
}
|
|
27
|
+
export function platformWorkflowReferences(value) {
|
|
28
|
+
const reference = platformWorkflowReference(value);
|
|
29
|
+
if (reference)
|
|
30
|
+
return [reference];
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
return value.flatMap(platformWorkflowReferences);
|
|
33
|
+
if (value !== null && typeof value === "object")
|
|
34
|
+
return Object.values(value).flatMap(platformWorkflowReferences);
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
export const platformWorkflowStepSchema = z.object({
|
|
38
|
+
id: platformWorkflowStepIdSchema.describe("Names the step for later steps' references: lowercase letters, digits and underscores."),
|
|
39
|
+
tool: platformToolNameSchema.describe("A tool of the project's catalog."),
|
|
40
|
+
arguments: platformJsonObjectSchema.describe("The tool's arguments. Write {\"$input\":\"name\"} for a run input or {\"$step\":\"id\",\"path\":\"/data/0/url\"} for a value of an earlier step's output."),
|
|
41
|
+
provider: platformProviderChoiceSchema,
|
|
42
|
+
}).strict();
|
|
43
|
+
const byteLength = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
44
|
+
const inputNames = (inputSchema) => {
|
|
45
|
+
const properties = inputSchema.properties;
|
|
46
|
+
return new Set(properties !== null && typeof properties === "object" && !Array.isArray(properties) ? Object.keys(properties) : []);
|
|
47
|
+
};
|
|
48
|
+
const definitionShape = {
|
|
49
|
+
title: z.string().trim().min(1).max(PLATFORM_WORKFLOW_LIMITS.titleChars),
|
|
50
|
+
description: z.string().trim().min(1).max(PLATFORM_WORKFLOW_LIMITS.descriptionChars).describe("What the workflow does and returns; agents choose workflows by it."),
|
|
51
|
+
inputSchema: platformCustomToolInputSchemaSchema.describe("JSON Schema of the run inputs, e.g. {\"type\":\"object\",\"properties\":{\"domain\":{\"type\":\"string\"}},\"required\":[\"domain\"]}."),
|
|
52
|
+
steps: z.array(platformWorkflowStepSchema).min(1).max(PLATFORM_WORKFLOW_LIMITS.steps).describe("Tool calls in the order they run; each starts after the previous one succeeded."),
|
|
53
|
+
};
|
|
54
|
+
function checkDefinition(definition, context) {
|
|
55
|
+
if (byteLength(definition.steps) > PLATFORM_WORKFLOW_LIMITS.stepsBytes)
|
|
56
|
+
context.addIssue({ code: "custom", path: ["steps"], message: "The steps are too large" });
|
|
57
|
+
const inputs = inputNames(definition.inputSchema), earlier = new Set();
|
|
58
|
+
definition.steps.forEach((step, index) => {
|
|
59
|
+
if (earlier.has(step.id))
|
|
60
|
+
context.addIssue({ code: "custom", path: ["steps", index, "id"], message: "Each step id is used once" });
|
|
61
|
+
for (const reference of platformWorkflowReferences(step.arguments)) {
|
|
62
|
+
if (!pointerSchema.safeParse(reference.path).success)
|
|
63
|
+
context.addIssue({ code: "custom", path: ["steps", index, "arguments"], message: `Invalid path ${reference.path}` });
|
|
64
|
+
else if (reference.kind === "input" && !inputs.has(reference.name))
|
|
65
|
+
context.addIssue({ code: "custom", path: ["steps", index, "arguments"], message: `No input named ${reference.name}` });
|
|
66
|
+
else if (reference.kind === "step" && !earlier.has(reference.step))
|
|
67
|
+
context.addIssue({ code: "custom", path: ["steps", index, "arguments"], message: `No earlier step named ${reference.step}` });
|
|
68
|
+
}
|
|
69
|
+
earlier.add(step.id);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export const platformWorkflowDefinitionSchema = z.object(definitionShape).strict().superRefine(checkDefinition);
|
|
73
|
+
export const platformWorkflowCreateSchema = z.object({ ...definitionShape, slug: platformWorkflowSlugSchema }).strict().superRefine(checkDefinition);
|
|
74
|
+
export const platformWorkflowUpdateSchema = z.object({ ...definitionShape, expectedVersion: z.number().int().min(1) }).strict().superRefine(checkDefinition);
|
|
75
|
+
export const platformWorkflowSchema = z.object({ slug: platformWorkflowSlugSchema, title: z.string(), description: z.string(), inputSchema: platformJsonObjectSchema,
|
|
76
|
+
steps: z.array(platformWorkflowStepSchema), version: z.number().int().min(1), createdAt: z.string(), updatedAt: z.string() });
|
|
77
|
+
export const platformWorkflowSummarySchema = platformWorkflowSchema.omit({ inputSchema: true, steps: true });
|
|
78
|
+
export const platformWorkflowVersionSchema = z.object({ version: z.number().int().min(1), title: z.string(), createdAt: z.string() });
|
|
79
|
+
export const platformWorkflowRunCreateSchema = z.object({
|
|
80
|
+
inputs: platformJsonObjectSchema.default({}).refine(value => byteLength(value) <= PLATFORM_WORKFLOW_LIMITS.inputBytes, "The inputs are too large")
|
|
81
|
+
.describe("Values for the workflow's input schema."),
|
|
82
|
+
maximumChargeMicros: platformMicrosSchema.describe("The most the whole run may charge, in micros of USD. A step whose price no longer fits stops the run before it is bought."),
|
|
83
|
+
version: z.number().int().min(1).optional().describe("Run an earlier kept version; omit for the current one."),
|
|
84
|
+
}).strict();
|
|
85
|
+
export const platformWorkflowRunStatusSchema = z.enum(["running", "succeeded", "failed", "needs_review", "cancelled"]);
|
|
86
|
+
export const platformWorkflowRunStepSchema = z.object({
|
|
87
|
+
id: platformWorkflowStepIdSchema, tool: platformToolNameSchema,
|
|
88
|
+
status: z.enum(["pending", "queued", "running", "waiting", "succeeded", "failed", "cancelled", "needs_review"]),
|
|
89
|
+
executionId: platformExecutionIdSchema.nullable(), chargedMicros: platformMicrosSchema, errorCode: z.string().nullable(),
|
|
90
|
+
}).strict();
|
|
91
|
+
export const platformWorkflowRunSchema = z.object({
|
|
92
|
+
id: platformWorkflowRunIdSchema, workflow: platformWorkflowSlugSchema, version: z.number().int().min(1), status: platformWorkflowRunStatusSchema,
|
|
93
|
+
cancelRequested: z.boolean(), errorCode: z.string().nullable(), inputs: platformJsonObjectSchema,
|
|
94
|
+
maximumChargeMicros: platformMicrosSchema, chargedMicros: platformMicrosSchema, steps: z.array(platformWorkflowRunStepSchema),
|
|
95
|
+
resultExecutionId: platformExecutionIdSchema.nullable(),
|
|
96
|
+
createdAt: z.string(), updatedAt: z.string(), completedAt: z.string().nullable(),
|
|
97
|
+
}).strict();
|
|
98
|
+
export const platformWorkflowRunSummarySchema = platformWorkflowRunSchema.omit({ inputs: true, steps: true });
|
|
99
|
+
export const platformWorkflowRunListQuerySchema = z.object({ before: platformWorkflowRunIdSchema.optional(), limit: z.coerce.number().int().min(1).max(50).default(20) }).strict();
|
|
100
|
+
export const platformWorkflowRunListSchema = z.object({ runs: z.array(platformWorkflowRunSummarySchema), nextBefore: platformWorkflowRunIdSchema.nullable() }).strict();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function isScheduleTimeZone(value) {
|
|
3
|
+
if (!/^[A-Za-z][A-Za-z0-9_+-]*(\/[A-Za-z0-9_+-]+)*$/.test(value))
|
|
4
|
+
return false;
|
|
5
|
+
try {
|
|
6
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value });
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export const ScheduleTimeZone = z.string().max(64)
|
|
14
|
+
.refine(isScheduleTimeZone, "Invalid time zone (must be an IANA tz-database name).")
|
|
15
|
+
.transform(value => new Intl.DateTimeFormat("en-US", { timeZone: value }).resolvedOptions().timeZone);
|