@m6d/cortex-cli 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -28
- package/package.json +3 -13
- package/src/commands/apply.ts +61 -38
- package/src/commands/management.ts +64 -19
- package/src/commands/new.ts +8 -10
- package/src/commands/setup.ts +83 -38
- package/src/contracts/datasets/dataset-sql.ts +36 -2
- package/src/contracts/datasets/datasets.ts +84 -182
- package/src/contracts/egress-policy/scrub.ts +11 -2
- package/src/contracts/errors/index.ts +7 -0
- package/src/contracts/json/index.ts +40 -17
- package/src/contracts/manifest/index.ts +601 -168
- package/src/contracts/server/index.ts +44 -53
- package/src/contracts/triggers/index.ts +13 -9
- package/src/contracts/wire/index.ts +149 -27
- package/src/management/client.ts +79 -117
- package/src/management/profiles.ts +11 -22
- package/src/scaffold/files.ts +13 -11
- package/src/ui/prompts.ts +30 -9
- package/src/contracts/charts/chart-spec.ts +0 -217
- package/src/contracts/charts/index.ts +0 -8
- package/src/contracts/datasets/datasets.fixtures.json +0 -2849
- package/src/contracts/markdown/index.ts +0 -47
- package/src/contracts/pattern/index.ts +0 -276
- package/src/contracts/server/reserved-names.fixtures.json +0 -16
|
@@ -4,7 +4,7 @@ import { canCarrySecret } from "../egress-policy/index";
|
|
|
4
4
|
import {
|
|
5
5
|
agentSlugSchema,
|
|
6
6
|
EVENT_HEADER,
|
|
7
|
-
|
|
7
|
+
identifierSchema,
|
|
8
8
|
localeSchema,
|
|
9
9
|
SERVER_EVENTS,
|
|
10
10
|
refuseBlockedApprovalTitle,
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
} from "../server/index";
|
|
13
13
|
import { MESSAGE_TRIGGER_NAME } from "../triggers/index";
|
|
14
14
|
import {
|
|
15
|
+
BASE_LOCALE,
|
|
16
|
+
byLocale,
|
|
15
17
|
DEFAULT_MEDIA_FORMS,
|
|
16
18
|
MEDIA_FORMS,
|
|
17
19
|
TOOL_SURFACES,
|
|
@@ -60,20 +62,14 @@ type Bounds = { min: number; max: number; default: number };
|
|
|
60
62
|
|
|
61
63
|
/**
|
|
62
64
|
* The range of every number an author sets, in the console and in a
|
|
63
|
-
* manifest alike, and the value a manifest that leaves one out gets
|
|
64
|
-
* console builds its form schemas, with
|
|
65
|
+
* manifest alike, and the value a manifest that leaves one out gets, which
|
|
66
|
+
* a new row's column holds too. The console builds its form schemas, with
|
|
67
|
+
* its own messages, from these.
|
|
65
68
|
*/
|
|
66
69
|
export const SETTING_BOUNDS = {
|
|
67
70
|
tool: {
|
|
68
71
|
timeoutMs: { min: 100, max: 120_000, default: 30_000 },
|
|
69
72
|
maxResponseBytes: { min: 1_024, max: 1_048_576, default: 262_144 },
|
|
70
|
-
rateLimitRequests: { min: 1, max: 10_000, default: 60 },
|
|
71
|
-
rateLimitWindowSeconds: { min: 1, max: 86_400, default: 60 },
|
|
72
|
-
},
|
|
73
|
-
agent: {
|
|
74
|
-
rateLimitRequests: { min: 1, max: 10_000, default: 60 },
|
|
75
|
-
rateLimitWindowSeconds: { min: 1, max: 86_400, default: 60 },
|
|
76
|
-
dailyExecutionQuota: { min: 1, max: 1_000_000, default: 10_000 },
|
|
77
73
|
},
|
|
78
74
|
/** How much of the conversation the agent's model sees. */
|
|
79
75
|
agentContext: {
|
|
@@ -92,6 +88,18 @@ export const SETTING_BOUNDS = {
|
|
|
92
88
|
trigger: { disableAfterFailures: { min: 1, max: 1_000, default: 5 } },
|
|
93
89
|
datasetHost: { timeoutMs: { min: 100, max: 120_000, default: 30_000 } },
|
|
94
90
|
webhook: { timeoutMs: { min: 1_000, max: 30_000, default: 10_000 } },
|
|
91
|
+
/** The score, a fraction, a new visitor of an anonymous agent needs to get an id. */
|
|
92
|
+
recaptcha: { minScore: { min: 0.1, max: 0.9, default: 0.5 } },
|
|
93
|
+
/**
|
|
94
|
+
* How often Cortex checks a caller for the server, per minute: a sign-in
|
|
95
|
+
* (a new end-user token it asks the server to verify, or a visitor one
|
|
96
|
+
* of its anonymous agents gives an id), or a trigger's secret. Anyone
|
|
97
|
+
* can try either, so each bounds what strangers cost it.
|
|
98
|
+
*/
|
|
99
|
+
server: {
|
|
100
|
+
signInChecksPerMinute: { min: 1, max: 100_000, default: 600 },
|
|
101
|
+
triggerChecksPerMinute: { min: 1, max: 10_000, default: 60 },
|
|
102
|
+
},
|
|
95
103
|
} as const satisfies Record<string, Record<string, Bounds>>;
|
|
96
104
|
|
|
97
105
|
/** An integer within `bounds`, their default when left out. */
|
|
@@ -99,28 +107,90 @@ function setting({ min, max, default: fallback }: Bounds) {
|
|
|
99
107
|
return z.number().int().min(min).max(max).default(fallback);
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
/** One number per setting a group of `SETTING_BOUNDS` names. */
|
|
111
|
+
export type SettingValues<TBounds> = { [Name in keyof TBounds]: number };
|
|
112
|
+
|
|
113
|
+
/** Every setting of a group at its default, as a row that sets none stores them. */
|
|
114
|
+
export function settingDefaults<TBounds extends Record<string, Bounds>>(bounds: TBounds) {
|
|
115
|
+
return Object.fromEntries(
|
|
116
|
+
Object.entries(bounds).map(([name, bound]) => [name, bound.default]),
|
|
117
|
+
) as SettingValues<TBounds>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type RateLimitBounds = { max: number; allUsers: number | null; perUser: number | null };
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Every rate limit an author sets, in the console and in a manifest alike,
|
|
124
|
+
* per minute except an agent's tokens, per UTC day. Each holds for all of an
|
|
125
|
+
* entry's users together and for each user alone, from 1 to `max`, or not
|
|
126
|
+
* at all (null); a manifest that leaves one out gets the default.
|
|
127
|
+
*/
|
|
128
|
+
export const RATE_LIMITS = {
|
|
129
|
+
agent: {
|
|
130
|
+
messagesPerMinute: { max: 10_000, allUsers: 600, perUser: 20 },
|
|
131
|
+
tokensPerDay: { max: 1_000_000_000_000, allUsers: null, perUser: 1_000_000 },
|
|
132
|
+
},
|
|
133
|
+
tool: { callsPerMinute: { max: 10_000, allUsers: 60, perUser: 20 } },
|
|
134
|
+
datasetHost: { queriesPerMinute: { max: 10_000, allUsers: 60, perUser: 20 } },
|
|
135
|
+
} as const satisfies Record<string, Record<string, RateLimitBounds>>;
|
|
136
|
+
|
|
137
|
+
/** Whom a rate limit holds for: all of an entry's users together, or each user alone. */
|
|
138
|
+
export const RATE_LIMIT_USERS = ["allUsers", "perUser"] as const;
|
|
139
|
+
export type RateLimitUsers = (typeof RATE_LIMIT_USERS)[number];
|
|
140
|
+
|
|
141
|
+
/** A group of `RATE_LIMITS`, one value per limit for all users and for each user. */
|
|
142
|
+
export function mapRateLimits<TBounds extends Record<string, RateLimitBounds>, TValue>(
|
|
143
|
+
bounds: TBounds,
|
|
144
|
+
value: (bound: RateLimitBounds, users: RateLimitUsers) => TValue,
|
|
145
|
+
) {
|
|
146
|
+
return Object.fromEntries(
|
|
147
|
+
RATE_LIMIT_USERS.map((users) => [
|
|
148
|
+
users,
|
|
149
|
+
Object.fromEntries(
|
|
150
|
+
Object.entries(bounds).map(([name, bound]) => [name, value(bound, users)]),
|
|
151
|
+
),
|
|
152
|
+
]),
|
|
153
|
+
) as Record<RateLimitUsers, { [Name in keyof TBounds]: TValue }>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** An entry's rate limits as its row holds them; null lifts one. */
|
|
157
|
+
export type RateLimits<TBounds> = Record<
|
|
158
|
+
RateLimitUsers,
|
|
159
|
+
{ [Name in keyof TBounds]: number | null }
|
|
160
|
+
>;
|
|
161
|
+
|
|
162
|
+
/** Every limit of a group at its default, as a row that sets none stores them. */
|
|
163
|
+
export function rateLimitDefaults<TBounds extends Record<string, RateLimitBounds>>(
|
|
164
|
+
bounds: TBounds,
|
|
165
|
+
) {
|
|
166
|
+
return mapRateLimits(bounds, (bound, users) => bound[users]) satisfies RateLimits<TBounds>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** One rate limit as a manifest writes it: null lifts it, and left out it takes its default. */
|
|
170
|
+
function rateLimit(bound: RateLimitBounds, users: RateLimitUsers) {
|
|
171
|
+
return z.number().int().min(1).max(bound.max).nullable().default(bound[users]);
|
|
172
|
+
}
|
|
173
|
+
|
|
102
174
|
/**
|
|
103
175
|
* The fields of an entry users may change in Cortex after an apply, each
|
|
104
176
|
* once, from the ones `fields` allows; none when left out. Every other field
|
|
105
177
|
* is the manifest's alone. An apply keeps a live change to a listed field
|
|
106
178
|
* unless the manifest changed that field too (a conflict, see
|
|
107
179
|
* `manifestApplyInputSchema`). Identity, what is fixed at creation, the
|
|
108
|
-
* audience,
|
|
109
|
-
*
|
|
180
|
+
* audience, and whatever an apply checks against the manifest's own value
|
|
181
|
+
* are never offered.
|
|
110
182
|
*/
|
|
111
183
|
function overridable<const TField extends string>(fields: readonly [TField, ...TField[]]) {
|
|
112
184
|
return z
|
|
113
185
|
.array(z.enum(fields))
|
|
114
|
-
.refine(
|
|
115
|
-
uniqueBy((field: TField) => field),
|
|
116
|
-
"a field repeats",
|
|
117
|
-
)
|
|
186
|
+
.refine((listed) => new Set(listed).size === listed.length, "a field repeats")
|
|
118
187
|
.default([]);
|
|
119
188
|
}
|
|
120
189
|
|
|
121
190
|
/** Consumer-facing slugs: the manifest id, service keys, skill names and trigger names share it. */
|
|
122
191
|
export const SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
123
|
-
const
|
|
192
|
+
export const SLUG_MAX_LENGTH = 64;
|
|
193
|
+
const slugSchema = z.string().regex(SLUG_PATTERN).max(SLUG_MAX_LENGTH);
|
|
124
194
|
|
|
125
195
|
/** A DNS host name in lower case, the way `URL` reports one: dot-separated labels of letters, digits and inner hyphens. */
|
|
126
196
|
export const HOSTNAME_PATTERN =
|
|
@@ -243,6 +313,92 @@ export const toolConfigByType = {
|
|
|
243
313
|
client: z.object({}),
|
|
244
314
|
};
|
|
245
315
|
|
|
316
|
+
/** Every tool type, in the order the database enum lists them; see `TOOL_TYPES`. */
|
|
317
|
+
export const TOOL_TYPE_NAMES = ["http", "embedded", "soap", "native", "client"] as const;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* What each tool type is, so no reader restates it: who answers its calls
|
|
321
|
+
* (`kind`: Cortex for a server tool, the browser for a client one), whether
|
|
322
|
+
* it calls an endpoint, whether a call may wait for the end user's
|
|
323
|
+
* approval, whether an input field may be filled from a source Cortex holds
|
|
324
|
+
* (`x-source`) or name its place in the request (`x-target`), whether an
|
|
325
|
+
* author creates one, and the `method` or `readOnly` the type pins whatever
|
|
326
|
+
* the author says. An embedded tool's initiate is never read-only, so a
|
|
327
|
+
* widget reload rejoins its session, and asks no approval, since the widget
|
|
328
|
+
* answers it. SOAP and native tools are POSTed, and a native tool comes from
|
|
329
|
+
* its server's manifest alone, with no field Cortex fills or places. A
|
|
330
|
+
* client tool calls nothing: the client answers it.
|
|
331
|
+
*/
|
|
332
|
+
export const TOOL_TYPES = {
|
|
333
|
+
http: {
|
|
334
|
+
kind: "server",
|
|
335
|
+
calls: true,
|
|
336
|
+
approval: true,
|
|
337
|
+
sources: true,
|
|
338
|
+
targets: true,
|
|
339
|
+
creatable: true,
|
|
340
|
+
},
|
|
341
|
+
embedded: {
|
|
342
|
+
kind: "client",
|
|
343
|
+
calls: true,
|
|
344
|
+
approval: false,
|
|
345
|
+
sources: true,
|
|
346
|
+
targets: false,
|
|
347
|
+
creatable: true,
|
|
348
|
+
readOnly: false,
|
|
349
|
+
},
|
|
350
|
+
soap: {
|
|
351
|
+
kind: "server",
|
|
352
|
+
calls: true,
|
|
353
|
+
approval: true,
|
|
354
|
+
sources: true,
|
|
355
|
+
targets: true,
|
|
356
|
+
creatable: true,
|
|
357
|
+
method: "POST",
|
|
358
|
+
},
|
|
359
|
+
native: {
|
|
360
|
+
kind: "server",
|
|
361
|
+
calls: true,
|
|
362
|
+
approval: true,
|
|
363
|
+
sources: false,
|
|
364
|
+
targets: false,
|
|
365
|
+
creatable: false,
|
|
366
|
+
method: "POST",
|
|
367
|
+
},
|
|
368
|
+
client: {
|
|
369
|
+
kind: "client",
|
|
370
|
+
calls: false,
|
|
371
|
+
approval: false,
|
|
372
|
+
sources: false,
|
|
373
|
+
targets: false,
|
|
374
|
+
creatable: true,
|
|
375
|
+
readOnly: true,
|
|
376
|
+
},
|
|
377
|
+
} as const satisfies Record<
|
|
378
|
+
(typeof TOOL_TYPE_NAMES)[number],
|
|
379
|
+
{
|
|
380
|
+
kind: "server" | "client";
|
|
381
|
+
calls: boolean;
|
|
382
|
+
approval: boolean;
|
|
383
|
+
sources: boolean;
|
|
384
|
+
targets: boolean;
|
|
385
|
+
creatable: boolean;
|
|
386
|
+
method?: (typeof TOOL_METHODS)[number];
|
|
387
|
+
readOnly?: boolean;
|
|
388
|
+
}
|
|
389
|
+
>;
|
|
390
|
+
|
|
391
|
+
type CreatableToolType = {
|
|
392
|
+
[TType in keyof typeof TOOL_TYPES]: (typeof TOOL_TYPES)[TType]["creatable"] extends true
|
|
393
|
+
? TType
|
|
394
|
+
: never;
|
|
395
|
+
}[keyof typeof TOOL_TYPES];
|
|
396
|
+
|
|
397
|
+
/** The types an author creates a tool as, in `TOOL_TYPE_NAMES` order; a native tool comes from its server's manifest. */
|
|
398
|
+
export const CREATABLE_TOOL_TYPES = TOOL_TYPE_NAMES.filter(
|
|
399
|
+
(type): type is CreatableToolType => TOOL_TYPES[type].creatable,
|
|
400
|
+
);
|
|
401
|
+
|
|
246
402
|
/** A `{name}` parameter in a tool's endpoint URL, filled from the input on each call. */
|
|
247
403
|
export const ENDPOINT_PARAM_PATTERN = /\{([^{}]+)\}/g;
|
|
248
404
|
|
|
@@ -261,9 +417,27 @@ export function confinesEndpointParams(url: string) {
|
|
|
261
417
|
/** An upstream Cortex calls: http or https. */
|
|
262
418
|
const upstreamUrlSchema = z.url({ protocol: /^https?$/ });
|
|
263
419
|
|
|
420
|
+
/**
|
|
421
|
+
* The headers a call carries: a header name and the prefix before the value
|
|
422
|
+
* in characters, and how many headers at most. The console caps its header
|
|
423
|
+
* rows at the same.
|
|
424
|
+
*/
|
|
425
|
+
export const HEADER_LIMITS = { headerLength: 120, prefixLength: 40, headers: 8 } as const;
|
|
426
|
+
|
|
264
427
|
/** An HTTP header field name (RFC 9110 token). */
|
|
265
428
|
export const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
266
|
-
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* A header value fetch sends: visible ASCII, tab, space and obs-text.
|
|
432
|
+
* Control characters, and any character past one byte, make fetch throw.
|
|
433
|
+
*/
|
|
434
|
+
export const HEADER_VALUE_PATTERN = /^[\t\x20-\x7e\x80-\xff]*$/;
|
|
435
|
+
const headerNameSchema = z
|
|
436
|
+
.string()
|
|
437
|
+
.trim()
|
|
438
|
+
.min(1)
|
|
439
|
+
.max(HEADER_LIMITS.headerLength)
|
|
440
|
+
.regex(HEADER_NAME_PATTERN);
|
|
267
441
|
|
|
268
442
|
/** Headers Cortex or fetch fills on every request. Lower-cased. */
|
|
269
443
|
export const transportSetHeaders = ["accept", "content-type", "content-length", "host"] as const;
|
|
@@ -279,24 +453,21 @@ export const transportForbiddenHeaders = [
|
|
|
279
453
|
"proxy-authorization",
|
|
280
454
|
] as const;
|
|
281
455
|
|
|
282
|
-
/** Headers the MCP transport sets on its own requests: no MCP server's
|
|
456
|
+
/** Headers the MCP transport sets on its own requests: no header an MCP server's entry declares travels under one. Lower-cased. */
|
|
283
457
|
export const mcpTransportHeaders = new Set<string>([
|
|
284
458
|
"mcp-session-id",
|
|
285
459
|
"mcp-protocol-version",
|
|
286
460
|
"last-event-id",
|
|
287
461
|
]);
|
|
288
462
|
|
|
289
|
-
/** Headers no
|
|
463
|
+
/** Headers no declared header or field may travel under; test lower-cased names. */
|
|
290
464
|
export const transportOwnedHeaders = new Set<string>([
|
|
291
465
|
...transportSetHeaders,
|
|
292
466
|
...transportForbiddenHeaders,
|
|
293
467
|
]);
|
|
294
468
|
|
|
295
|
-
/**
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
/** The index of each entry presenting a header an earlier entry already presents, compared case-insensitively. */
|
|
299
|
-
export function repeatedHeaders(entries: ReadonlyArray<{ header: string }>) {
|
|
469
|
+
/** The index of each entry declaring a header an earlier entry already declares, compared case-insensitively. */
|
|
470
|
+
function repeatedHeaders(entries: ReadonlyArray<{ header: string }>) {
|
|
300
471
|
const seen = new Set<string>();
|
|
301
472
|
return entries.flatMap((entry, index) => {
|
|
302
473
|
const name = entry.header.toLowerCase();
|
|
@@ -306,8 +477,10 @@ export function repeatedHeaders(entries: ReadonlyArray<{ header: string }>) {
|
|
|
306
477
|
});
|
|
307
478
|
}
|
|
308
479
|
|
|
480
|
+
/** The longest name Cortex holds a server, model provider, credential or secret under. */
|
|
481
|
+
export const NAME_MAX_LENGTH = 120;
|
|
309
482
|
/** A server, model provider, credential or secret, by the name Cortex holds it under. */
|
|
310
|
-
const nameSchema = z.string().trim().min(1).max(
|
|
483
|
+
const nameSchema = z.string().trim().min(1).max(NAME_MAX_LENGTH);
|
|
311
484
|
|
|
312
485
|
const presentedHeaderSchema = headerNameSchema.refine(
|
|
313
486
|
(name) => !transportOwnedHeaders.has(name.toLowerCase()),
|
|
@@ -315,31 +488,94 @@ const presentedHeaderSchema = headerNameSchema.refine(
|
|
|
315
488
|
);
|
|
316
489
|
|
|
317
490
|
/**
|
|
318
|
-
*
|
|
319
|
-
* it (`"Bearer "`, say), and the
|
|
320
|
-
*
|
|
491
|
+
* The headers every call carries: each entry a header, what precedes the
|
|
492
|
+
* value in it (`"Bearer "`, say), and where the value comes from, as `entry`
|
|
493
|
+
* validates it, at most `HEADER_LIMITS.headers` of them. A header is
|
|
494
|
+
* declared once; `repeated` words the refusal of a later one.
|
|
321
495
|
*/
|
|
322
|
-
function
|
|
323
|
-
|
|
324
|
-
|
|
496
|
+
export function headerListSchema<TEntry extends z.ZodType<{ header: string }>>(
|
|
497
|
+
entry: TEntry,
|
|
498
|
+
repeated: () => string,
|
|
325
499
|
) {
|
|
326
500
|
return z
|
|
327
|
-
.array(
|
|
328
|
-
.max(
|
|
501
|
+
.array(entry)
|
|
502
|
+
.max(HEADER_LIMITS.headers)
|
|
329
503
|
.superRefine((entries, context) => {
|
|
330
504
|
for (const index of repeatedHeaders(entries)) {
|
|
331
505
|
context.addIssue({
|
|
332
506
|
code: "custom",
|
|
333
507
|
path: [index, "header"],
|
|
334
|
-
message:
|
|
508
|
+
message: repeated(),
|
|
335
509
|
});
|
|
336
510
|
}
|
|
337
|
-
})
|
|
338
|
-
.default([]);
|
|
511
|
+
});
|
|
339
512
|
}
|
|
340
513
|
|
|
341
|
-
/** A
|
|
342
|
-
const
|
|
514
|
+
/** A variable by name: one the agent's server declares, or a built-in (`timezone`, `locale`, `utcTime`). */
|
|
515
|
+
const variableSourceSchema = z.object({
|
|
516
|
+
kind: z.literal("variable"),
|
|
517
|
+
name: z.string().trim().min(1).max(64),
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
/** A credential Cortex holds or acquires, by name. */
|
|
521
|
+
const credentialSourceSchema = z.object({ kind: z.literal("credential"), name: nameSchema });
|
|
522
|
+
|
|
523
|
+
/** A value written as is. */
|
|
524
|
+
const constantHeaderSourceSchema = z.object({
|
|
525
|
+
kind: z.literal("constant"),
|
|
526
|
+
value: z.string().max(4_000),
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Where a header's value comes from: a credential, the run's own token (the
|
|
531
|
+
* end user's in a chat, the trigger's in a firing), a variable, or a
|
|
532
|
+
* constant. A run token or a variable needs a run; a call without one (a
|
|
533
|
+
* webhook delivery, a catalog read) presents only credentials and constants.
|
|
534
|
+
*/
|
|
535
|
+
export const headerSourceSchema = z.discriminatedUnion("kind", [
|
|
536
|
+
credentialSourceSchema,
|
|
537
|
+
z.object({ kind: z.literal("runToken") }),
|
|
538
|
+
variableSourceSchema,
|
|
539
|
+
constantHeaderSourceSchema,
|
|
540
|
+
]);
|
|
541
|
+
export type HeaderSource = z.infer<typeof headerSourceSchema>;
|
|
542
|
+
|
|
543
|
+
/** The sources a call without a run can present. */
|
|
544
|
+
export const runlessHeaderSourceSchema = z.discriminatedUnion("kind", [
|
|
545
|
+
credentialSourceSchema,
|
|
546
|
+
constantHeaderSourceSchema,
|
|
547
|
+
]);
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* The headers a manifest entry declares: no unknown key, an omitted prefix
|
|
551
|
+
* or list empty, and a prefix or constant only of what a header can carry
|
|
552
|
+
* (`HEADER_VALUE_PATTERN`).
|
|
553
|
+
*/
|
|
554
|
+
function declaredHeadersSchema<TSource extends HeaderSource>(
|
|
555
|
+
header: z.ZodString,
|
|
556
|
+
from: z.ZodType<TSource, TSource>,
|
|
557
|
+
) {
|
|
558
|
+
return headerListSchema(
|
|
559
|
+
z
|
|
560
|
+
.strictObject({
|
|
561
|
+
header,
|
|
562
|
+
prefix: z
|
|
563
|
+
.string()
|
|
564
|
+
.max(HEADER_LIMITS.prefixLength)
|
|
565
|
+
.regex(HEADER_VALUE_PATTERN, "a prefix a header cannot carry")
|
|
566
|
+
.default(""),
|
|
567
|
+
from,
|
|
568
|
+
})
|
|
569
|
+
.refine(
|
|
570
|
+
(entry) =>
|
|
571
|
+
entry.from.kind !== "constant" || HEADER_VALUE_PATTERN.test(entry.from.value),
|
|
572
|
+
{ path: ["from", "value"], message: "a value a header cannot carry" },
|
|
573
|
+
),
|
|
574
|
+
() => "a header declared twice",
|
|
575
|
+
).default([]);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const headersSchema = declaredHeadersSchema(presentedHeaderSchema, headerSourceSchema);
|
|
343
579
|
|
|
344
580
|
/**
|
|
345
581
|
* Where Cortex fills a top-level scalar input field from, out of the model's
|
|
@@ -347,8 +583,8 @@ const credentialsSchema = presentationsSchema(presentedHeaderSchema, nameSchema.
|
|
|
347
583
|
* a constant, or the end user's bearer.
|
|
348
584
|
*/
|
|
349
585
|
export const fieldSourceSchema = z.discriminatedUnion("kind", [
|
|
350
|
-
|
|
351
|
-
z.object({ kind: z.literal("secret"), name:
|
|
586
|
+
variableSourceSchema,
|
|
587
|
+
z.object({ kind: z.literal("secret"), name: nameSchema }),
|
|
352
588
|
z.object({
|
|
353
589
|
kind: z.literal("constant"),
|
|
354
590
|
value: z.union([z.string().max(4_000), z.number(), z.boolean()]),
|
|
@@ -363,7 +599,7 @@ export type FieldSource = z.infer<typeof fieldSourceSchema>;
|
|
|
363
599
|
* string only, and a constant must be a value of the field's type. Whether a
|
|
364
600
|
* variable is declared is the agent's server's to say.
|
|
365
601
|
*/
|
|
366
|
-
|
|
602
|
+
function sourceFits(
|
|
367
603
|
source: FieldSource,
|
|
368
604
|
field: { type: unknown; enum?: ReadonlyArray<string | null> },
|
|
369
605
|
) {
|
|
@@ -426,16 +662,26 @@ export const fieldTargetSchema = z.discriminatedUnion("kind", [
|
|
|
426
662
|
]);
|
|
427
663
|
export type FieldTarget = z.infer<typeof fieldTargetSchema>;
|
|
428
664
|
|
|
429
|
-
|
|
665
|
+
/** The types a field holds one value of. */
|
|
666
|
+
export const SCALAR_TYPES = ["string", "number", "integer", "boolean"] as const;
|
|
430
667
|
const schemaTypeSchema = z.enum([...SCALAR_TYPES, "object", "array"]);
|
|
431
668
|
|
|
669
|
+
/** A field's name and description in characters, and the values its enum lists. The console caps its field rows at the same. */
|
|
670
|
+
export const FIELD_LIMITS = { nameLength: 120, descriptionLength: 500, enumValues: 50 } as const;
|
|
671
|
+
|
|
432
672
|
/** Untrimmed: two names that differ only in surrounding spaces would collapse into one. */
|
|
433
673
|
const propertyNameSchema = z
|
|
434
674
|
.string()
|
|
435
675
|
.min(1)
|
|
436
|
-
.max(
|
|
676
|
+
.max(FIELD_LIMITS.nameLength)
|
|
437
677
|
.refine((name) => name === name.trim(), "no surrounding spaces");
|
|
438
678
|
|
|
679
|
+
/** The order an object's properties are shown in, each named once. */
|
|
680
|
+
const propertyOrderSchema = z
|
|
681
|
+
.array(z.string())
|
|
682
|
+
.refine((names) => new Set(names).size === names.length, "a name repeats")
|
|
683
|
+
.optional();
|
|
684
|
+
|
|
439
685
|
/** What an array holds: a scalar, or an object with properties of its own. */
|
|
440
686
|
const itemsSchema = z.strictObject({
|
|
441
687
|
type: z.enum([...SCALAR_TYPES, "object"]),
|
|
@@ -443,29 +689,32 @@ const itemsSchema = z.strictObject({
|
|
|
443
689
|
return z.record(propertyNameSchema, schemaPropertySchema).optional();
|
|
444
690
|
},
|
|
445
691
|
required: z.array(z.string()).optional(),
|
|
692
|
+
"x-order": propertyOrderSchema,
|
|
446
693
|
});
|
|
447
694
|
|
|
448
695
|
/**
|
|
449
696
|
* A JSON Schema property, in the part of JSON Schema a field editor holds:
|
|
450
697
|
* a type (with `"null"` beside it when the value may be null), an `enum` of
|
|
451
|
-
* strings for a string, an object's `properties` and
|
|
452
|
-
* array's `items`, a `description`, and
|
|
453
|
-
* (redacted from logs), `x-source` (top-level
|
|
454
|
-
* input only) and `x-target` (top-level fields of
|
|
698
|
+
* strings for a string, an object's `properties`, `required` and the
|
|
699
|
+
* `x-order` they are shown in, an array's `items`, a `description`, and
|
|
700
|
+
* the extensions `x-sensitive` (redacted from logs), `x-source` (top-level
|
|
701
|
+
* scalar fields of a tool's input only) and `x-target` (top-level fields of
|
|
702
|
+
* a tool's input only).
|
|
455
703
|
*/
|
|
456
704
|
const schemaPropertySchema = z.strictObject({
|
|
457
705
|
type: z.union([schemaTypeSchema, z.tuple([schemaTypeSchema, z.literal("null")])]),
|
|
458
|
-
description: z.string().trim().max(
|
|
706
|
+
description: z.string().trim().max(FIELD_LIMITS.descriptionLength).optional(),
|
|
459
707
|
/** A nullable enum lists `null` beside its values, as the console stores one. */
|
|
460
708
|
enum: z
|
|
461
709
|
.array(z.union([z.string().trim().min(1), z.null()]))
|
|
462
710
|
.min(1)
|
|
463
|
-
.max(
|
|
711
|
+
.max(FIELD_LIMITS.enumValues + 1)
|
|
464
712
|
.optional(),
|
|
465
713
|
get properties() {
|
|
466
714
|
return z.record(propertyNameSchema, schemaPropertySchema).optional();
|
|
467
715
|
},
|
|
468
716
|
required: z.array(z.string()).optional(),
|
|
717
|
+
"x-order": propertyOrderSchema,
|
|
469
718
|
get items() {
|
|
470
719
|
return itemsSchema.optional();
|
|
471
720
|
},
|
|
@@ -483,24 +732,30 @@ function typeOf(node: { type: SchemaProperty["type"] }) {
|
|
|
483
732
|
/** A refusal below a node: where, relative to it, and why. */
|
|
484
733
|
type RowIssue = { at: Array<string | number>; message: string };
|
|
485
734
|
|
|
486
|
-
/**
|
|
487
|
-
|
|
735
|
+
/** A node holding properties: a property, an array's items, or an input or output schema. */
|
|
736
|
+
type ObjectNode = {
|
|
488
737
|
type: SchemaProperty["type"];
|
|
489
738
|
properties?: Record<string, SchemaProperty>;
|
|
490
739
|
required?: string[];
|
|
491
|
-
|
|
740
|
+
"x-order"?: string[];
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
/** What a node's own keywords break: `properties`, `required` or `x-order` off an object, or the last two naming no property. */
|
|
744
|
+
function nodeIssues(node: ObjectNode) {
|
|
492
745
|
const issues = new Array<RowIssue>();
|
|
493
746
|
const object = typeOf(node) === "object";
|
|
494
|
-
for (const keyword of ["properties", "required"] as const) {
|
|
747
|
+
for (const keyword of ["properties", "required", "x-order"] as const) {
|
|
495
748
|
if (!object && node[keyword] !== undefined) {
|
|
496
749
|
issues.push({ at: [keyword], message: "only an object has it" });
|
|
497
750
|
}
|
|
498
751
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
752
|
+
for (const keyword of ["required", "x-order"] as const) {
|
|
753
|
+
node[keyword]?.forEach((name, index) => {
|
|
754
|
+
if (!Object.hasOwn(node.properties ?? {}, name)) {
|
|
755
|
+
issues.push({ at: [keyword, index], message: "names no property" });
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
504
759
|
return issues;
|
|
505
760
|
}
|
|
506
761
|
|
|
@@ -513,20 +768,24 @@ function enumIssueOf(property: SchemaProperty, type: string | undefined) {
|
|
|
513
768
|
if (property.enum === undefined) return undefined;
|
|
514
769
|
if (type !== "string") return "an enum lists a string's values";
|
|
515
770
|
const values = property.enum.filter((value) => value !== null);
|
|
516
|
-
if (values.length === 0 || values.length >
|
|
771
|
+
if (values.length === 0 || values.length > FIELD_LIMITS.enumValues) {
|
|
772
|
+
return `an enum lists 1 to ${FIELD_LIMITS.enumValues} values`;
|
|
773
|
+
}
|
|
517
774
|
const nullable = Array.isArray(property.type);
|
|
518
775
|
if (nullable === property.enum.includes(null)) return undefined;
|
|
519
776
|
return nullable ? "a nullable enum lists null" : "only a nullable enum lists null";
|
|
520
777
|
}
|
|
521
778
|
|
|
779
|
+
/** The extensions a tool's input binds a field with: a source filling it, a place in the request. */
|
|
780
|
+
type Binding = "x-source" | "x-target";
|
|
781
|
+
|
|
522
782
|
/**
|
|
523
783
|
* What one property breaks of itself: an `enum` off a string, with no value,
|
|
524
784
|
* or listing `null` when the type does not allow it or the other way round;
|
|
525
|
-
* `items` off an array or an array without them;
|
|
526
|
-
*
|
|
527
|
-
* `x-target` off a `bindable` node.
|
|
785
|
+
* `items` off an array or an array without them; and what its bindings
|
|
786
|
+
* break (`bindingIssues`).
|
|
528
787
|
*/
|
|
529
|
-
function propertyIssues(property: SchemaProperty,
|
|
788
|
+
function propertyIssues(property: SchemaProperty, bindings: ReadonlyArray<Binding> | undefined) {
|
|
530
789
|
const issues = new Array<RowIssue>();
|
|
531
790
|
const type = typeOf(property);
|
|
532
791
|
const enumIssue = enumIssueOf(property, type);
|
|
@@ -537,21 +796,41 @@ function propertyIssues(property: SchemaProperty, bindable: boolean) {
|
|
|
537
796
|
message: type === "array" ? "an array declares its items" : "only an array has it",
|
|
538
797
|
});
|
|
539
798
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
799
|
+
return [...issues, ...bindingIssues(property, type, bindings)];
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* What a property's bindings break: one other than `bindings` directly under
|
|
804
|
+
* a tool's input (none anywhere else), an `x-source` off a scalar, or a
|
|
805
|
+
* source that does not fit the field.
|
|
806
|
+
*/
|
|
807
|
+
function bindingIssues(
|
|
808
|
+
property: SchemaProperty,
|
|
809
|
+
type: string | undefined,
|
|
810
|
+
bindings: ReadonlyArray<Binding> | undefined,
|
|
811
|
+
) {
|
|
812
|
+
const issues = new Array<RowIssue>();
|
|
813
|
+
const scalar = SCALAR_TYPES.some((scalar) => scalar === type);
|
|
814
|
+
// A source fills a scalar; a target places a field of any type.
|
|
815
|
+
const fields = { "x-source": "a top-level scalar field", "x-target": "a top-level field" };
|
|
816
|
+
for (const binding of ["x-source", "x-target"] as const) {
|
|
817
|
+
if (property[binding] === undefined) continue;
|
|
818
|
+
const reaches = bindings !== undefined && (binding === "x-target" || scalar);
|
|
819
|
+
if (reaches && bindings.includes(binding)) continue;
|
|
548
820
|
issues.push({
|
|
549
|
-
at: [
|
|
550
|
-
message:
|
|
821
|
+
at: [binding],
|
|
822
|
+
message: reaches
|
|
823
|
+
? "this type of tool takes none"
|
|
824
|
+
: `only ${fields[binding]} of a tool's input has it`,
|
|
551
825
|
});
|
|
552
826
|
}
|
|
553
827
|
const source = property["x-source"];
|
|
554
|
-
if (
|
|
828
|
+
if (
|
|
829
|
+
scalar &&
|
|
830
|
+
bindings?.includes("x-source") &&
|
|
831
|
+
source &&
|
|
832
|
+
!sourceFits(source, { type, enum: property.enum })
|
|
833
|
+
) {
|
|
555
834
|
issues.push({ at: ["x-source"], message: "the source does not fit the field's type" });
|
|
556
835
|
}
|
|
557
836
|
return issues;
|
|
@@ -560,18 +839,15 @@ function propertyIssues(property: SchemaProperty, bindable: boolean) {
|
|
|
560
839
|
/**
|
|
561
840
|
* Refuses, at every depth below `path`, what the console's field rows cannot
|
|
562
841
|
* hold, since it stores a schema as rows and would drop or change it in
|
|
563
|
-
* silence (`nodeIssues`, `propertyIssues`);
|
|
564
|
-
*
|
|
842
|
+
* silence (`nodeIssues`, `propertyIssues`); a binding only one of
|
|
843
|
+
* `bindings`, directly under the node they are given for, and `x-source`
|
|
844
|
+
* only on a scalar.
|
|
565
845
|
*/
|
|
566
846
|
function heldByRows(
|
|
567
|
-
node:
|
|
568
|
-
type: SchemaProperty["type"];
|
|
569
|
-
properties?: Record<string, SchemaProperty>;
|
|
570
|
-
required?: string[];
|
|
571
|
-
},
|
|
847
|
+
node: ObjectNode,
|
|
572
848
|
context: z.RefinementCtx,
|
|
573
849
|
path: Array<string | number>,
|
|
574
|
-
|
|
850
|
+
bindings: ReadonlyArray<Binding> | undefined,
|
|
575
851
|
) {
|
|
576
852
|
const report = (under: Array<string | number>, issues: ReadonlyArray<RowIssue>) => {
|
|
577
853
|
for (const { at, message } of issues) {
|
|
@@ -581,27 +857,29 @@ function heldByRows(
|
|
|
581
857
|
report(path, nodeIssues(node));
|
|
582
858
|
for (const [name, property] of Object.entries(node.properties ?? {})) {
|
|
583
859
|
const at = [...path, "properties", name];
|
|
584
|
-
report(at, propertyIssues(property,
|
|
585
|
-
heldByRows(property, context, at,
|
|
586
|
-
if (property.items) heldByRows(property.items, context, [...at, "items"],
|
|
860
|
+
report(at, propertyIssues(property, bindings));
|
|
861
|
+
heldByRows(property, context, at, undefined);
|
|
862
|
+
if (property.items) heldByRows(property.items, context, [...at, "items"], undefined);
|
|
587
863
|
}
|
|
588
864
|
}
|
|
589
865
|
|
|
590
866
|
/**
|
|
591
867
|
* An object's JSON Schema, the shape of a tool's or trigger's input and
|
|
592
|
-
* output
|
|
593
|
-
*
|
|
868
|
+
* output, and of what the console's field rows write. `bindings`, given for
|
|
869
|
+
* a tool's input, are the extensions its top-level fields may carry:
|
|
870
|
+
* `x-source` on a scalar, `x-target` on any field.
|
|
594
871
|
*/
|
|
595
|
-
function
|
|
872
|
+
export function objectJsonSchemaOf(bindings?: ReadonlyArray<Binding>) {
|
|
596
873
|
return z
|
|
597
874
|
.strictObject({
|
|
598
875
|
type: z.literal("object"),
|
|
599
876
|
properties: z.record(propertyNameSchema, schemaPropertySchema).optional(),
|
|
600
877
|
required: z.array(z.string()).optional(),
|
|
878
|
+
"x-order": propertyOrderSchema,
|
|
601
879
|
})
|
|
602
|
-
.superRefine((schema, context) => heldByRows(schema, context, [],
|
|
880
|
+
.superRefine((schema, context) => heldByRows(schema, context, [], bindings));
|
|
603
881
|
}
|
|
604
|
-
const objectSchema =
|
|
882
|
+
const objectSchema = objectJsonSchemaOf();
|
|
605
883
|
|
|
606
884
|
const EMPTY_OBJECT = { type: "object" } as const satisfies z.input<typeof objectSchema>;
|
|
607
885
|
|
|
@@ -617,82 +895,126 @@ const audienceSchema = z.union([
|
|
|
617
895
|
}),
|
|
618
896
|
]);
|
|
619
897
|
|
|
898
|
+
/** A model provider's base URL and each model it lists in characters, and how many it lists. The console caps a provider at the same. */
|
|
899
|
+
export const MODEL_PROVIDER_LIMITS = { urlLength: 2_048, modelLength: 200, models: 100 } as const;
|
|
900
|
+
|
|
620
901
|
/** A model a provider lists, and the reasoning effort sent with it. */
|
|
621
902
|
const modelSchema = z.strictObject({
|
|
622
903
|
provider: nameSchema,
|
|
623
|
-
model: z.string().trim().min(1).max(
|
|
904
|
+
model: z.string().trim().min(1).max(MODEL_PROVIDER_LIMITS.modelLength),
|
|
624
905
|
effort: z.string().trim().min(1).max(64).optional(),
|
|
625
906
|
});
|
|
626
907
|
|
|
908
|
+
/** A service's name and description, in characters per locale. The console caps its editor at the same lengths. */
|
|
909
|
+
export const SERVICE_LIMITS = { nameLength: 120, descriptionLength: 4_000 } as const;
|
|
910
|
+
|
|
627
911
|
/** One line per locale, trimmed. */
|
|
628
|
-
const localizedLineSchema = z.record(
|
|
629
|
-
|
|
912
|
+
const localizedLineSchema = z.record(
|
|
913
|
+
localeSchema,
|
|
914
|
+
z.string().trim().min(1).max(SERVICE_LIMITS.nameLength),
|
|
915
|
+
);
|
|
916
|
+
const localizedTextSchema = z.record(
|
|
917
|
+
localeSchema,
|
|
918
|
+
z.string().trim().min(1).max(SERVICE_LIMITS.descriptionLength),
|
|
919
|
+
);
|
|
630
920
|
|
|
631
921
|
const { tool: toolBounds } = SETTING_BOUNDS;
|
|
922
|
+
const { tool: toolRateLimits, datasetHost: datasetHostRateLimits } = RATE_LIMITS;
|
|
923
|
+
|
|
924
|
+
/** A `type` tool's input schema, its fields bound only as `TOOL_TYPES` lets the type: filled from a source, placed in the request. */
|
|
925
|
+
function toolInputSchemaOf(type: (typeof TOOL_TYPE_NAMES)[number]) {
|
|
926
|
+
const { sources, targets } = TOOL_TYPES[type];
|
|
927
|
+
return objectJsonSchemaOf([
|
|
928
|
+
...(sources ? ["x-source" as const] : []),
|
|
929
|
+
...(targets ? ["x-target" as const] : []),
|
|
930
|
+
]).default(EMPTY_OBJECT);
|
|
931
|
+
}
|
|
632
932
|
|
|
633
|
-
/**
|
|
933
|
+
/** What a live write may change on a manifest's tool, and keep through the next apply. */
|
|
934
|
+
const TOOL_OVERRIDABLES = [
|
|
935
|
+
"description",
|
|
936
|
+
"timeoutMs",
|
|
937
|
+
"maxResponseBytes",
|
|
938
|
+
"rateLimits",
|
|
939
|
+
"pinned",
|
|
940
|
+
"tags",
|
|
941
|
+
] as const;
|
|
942
|
+
|
|
943
|
+
/** The same for a tool that calls an endpoint, whose headers are its own. */
|
|
944
|
+
const CALLED_TOOL_OVERRIDABLES = [...TOOL_OVERRIDABLES, "headers"] as const;
|
|
945
|
+
|
|
946
|
+
/** Shared by every tool type; a tool that calls nothing (`client`) has no endpoint, headers, approval or bound fields. */
|
|
634
947
|
const toolBaseSchema = z.strictObject({
|
|
635
948
|
...serverToolSchema.pick({ name: true, description: true }).shape,
|
|
636
|
-
inputSchema:
|
|
949
|
+
inputSchema: toolInputSchemaOf("client"),
|
|
637
950
|
outputSchema: objectSchema.default(EMPTY_OBJECT),
|
|
638
951
|
timeoutMs: setting(toolBounds.timeoutMs),
|
|
639
952
|
maxResponseBytes: setting(toolBounds.maxResponseBytes),
|
|
640
|
-
|
|
641
|
-
|
|
953
|
+
rateLimits: z
|
|
954
|
+
.strictObject({
|
|
955
|
+
allUsers: z
|
|
956
|
+
.strictObject({
|
|
957
|
+
callsPerMinute: rateLimit(toolRateLimits.callsPerMinute, "allUsers"),
|
|
958
|
+
})
|
|
959
|
+
.prefault({}),
|
|
960
|
+
perUser: z
|
|
961
|
+
.strictObject({
|
|
962
|
+
callsPerMinute: rateLimit(toolRateLimits.callsPerMinute, "perUser"),
|
|
963
|
+
})
|
|
964
|
+
.prefault({}),
|
|
965
|
+
})
|
|
966
|
+
.prefault({}),
|
|
642
967
|
/** Trims the upstream's answer to what the output schema declares. */
|
|
643
968
|
projectOutput: z.boolean().default(false),
|
|
644
969
|
/** Listed in every turn of every agent the tool reaches, whatever the question. */
|
|
645
970
|
pinned: z.boolean().default(false),
|
|
646
971
|
tags: toolTagsSchema.default([]),
|
|
647
972
|
audience: audienceSchema,
|
|
648
|
-
overridable: overridable(
|
|
649
|
-
"description",
|
|
650
|
-
"timeoutMs",
|
|
651
|
-
"maxResponseBytes",
|
|
652
|
-
"rateLimitRequests",
|
|
653
|
-
"rateLimitWindowSeconds",
|
|
654
|
-
"pinned",
|
|
655
|
-
"tags",
|
|
656
|
-
]),
|
|
973
|
+
overridable: overridable(TOOL_OVERRIDABLES),
|
|
657
974
|
});
|
|
658
975
|
|
|
659
976
|
/** A tool that calls an endpoint; with `requiresApproval`, every call waits for the end user to approve it. */
|
|
660
977
|
const calledToolSchema = toolBaseSchema.extend({
|
|
661
|
-
inputSchema: objectSchemaOf(true).default(EMPTY_OBJECT),
|
|
662
978
|
url: upstreamUrlSchema.refine(
|
|
663
979
|
confinesEndpointParams,
|
|
664
980
|
"a {parameter} outside the path or query",
|
|
665
981
|
),
|
|
666
|
-
|
|
982
|
+
headers: headersSchema,
|
|
667
983
|
requiresApproval: z.boolean().default(false),
|
|
984
|
+
overridable: overridable(CALLED_TOOL_OVERRIDABLES),
|
|
668
985
|
});
|
|
669
986
|
|
|
670
987
|
const manifestToolSchema = z
|
|
671
988
|
.discriminatedUnion("type", [
|
|
672
989
|
calledToolSchema.extend({
|
|
673
990
|
type: z.literal("http"),
|
|
991
|
+
inputSchema: toolInputSchemaOf("http"),
|
|
674
992
|
method: z.enum(TOOL_METHODS),
|
|
675
993
|
readOnly: z.boolean(),
|
|
676
994
|
...toolConfigByType.http.shape,
|
|
677
995
|
}),
|
|
678
|
-
/**
|
|
679
|
-
|
|
996
|
+
/**
|
|
997
|
+
* The endpoint is the initiate call that opens the page; never read-only, so a reload rejoins it.
|
|
998
|
+
* No `requiresApproval`: the widget answers the call, so nothing would ask for it. No field
|
|
999
|
+
* names its place in the request (`x-target`).
|
|
1000
|
+
*/
|
|
1001
|
+
calledToolSchema.omit({ requiresApproval: true }).extend({
|
|
680
1002
|
type: z.literal("embedded"),
|
|
1003
|
+
inputSchema: toolInputSchemaOf("embedded"),
|
|
681
1004
|
method: z.enum(TOOL_METHODS),
|
|
682
1005
|
...toolConfigByType.embedded.shape,
|
|
683
1006
|
}),
|
|
684
1007
|
/** Always POSTed. */
|
|
685
1008
|
calledToolSchema.extend({
|
|
686
1009
|
type: z.literal("soap"),
|
|
1010
|
+
inputSchema: toolInputSchemaOf("soap"),
|
|
687
1011
|
readOnly: z.boolean(),
|
|
688
1012
|
...toolConfigByType.soap.shape,
|
|
689
1013
|
}),
|
|
690
1014
|
/** Answered by your app: the model calls it and the widget hands the call over. */
|
|
691
1015
|
toolBaseSchema.extend({ type: z.literal("client") }),
|
|
692
1016
|
])
|
|
693
|
-
.superRefine((tool, context) =>
|
|
694
|
-
if (tool.type !== "client") refuseBlockedApprovalTitle(tool, context, []);
|
|
695
|
-
});
|
|
1017
|
+
.superRefine((tool, context) => refuseBlockedApprovalTitle(tool, context, []));
|
|
696
1018
|
export type ManifestTool = z.input<typeof manifestToolSchema>;
|
|
697
1019
|
|
|
698
1020
|
const toolNamesSchema = z.array(serverToolSchema.shape.name).max(500).default([]);
|
|
@@ -701,16 +1023,16 @@ const toolNamesSchema = z.array(serverToolSchema.shape.name).max(500).default([]
|
|
|
701
1023
|
* An OpenAPI document whose operations become HTTP tools this manifest owns.
|
|
702
1024
|
* Each apply reads it, imports every operation Cortex supports, keeps those
|
|
703
1025
|
* tools in step with the document and deletes the ones it no longer has.
|
|
704
|
-
* Every tool
|
|
1026
|
+
* Every tool carries the source's `headers`. A GET call runs at once; any other
|
|
705
1027
|
* method waits for the end user's approval, unless `approval` names the
|
|
706
1028
|
* tool the other way.
|
|
707
1029
|
*/
|
|
708
1030
|
const manifestOpenApiSchema = z.strictObject({
|
|
709
1031
|
/** Names the source in what an apply reports. */
|
|
710
1032
|
name: nameSchema,
|
|
711
|
-
/** Where the document is read, with no
|
|
1033
|
+
/** Where the document is read, with no headers: OpenAPI 3 or Swagger 2, JSON or YAML. */
|
|
712
1034
|
url: upstreamUrlSchema,
|
|
713
|
-
|
|
1035
|
+
headers: headersSchema,
|
|
714
1036
|
/** Where every operation's successful response keeps its result, when the API wraps each answer. */
|
|
715
1037
|
resultPath: jsonPointerSchema.optional(),
|
|
716
1038
|
/** Tools listed in every turn of every agent they reach, whatever the question. */
|
|
@@ -723,6 +1045,8 @@ const manifestOpenApiSchema = z.strictObject({
|
|
|
723
1045
|
"a tool both never and always needs approval",
|
|
724
1046
|
),
|
|
725
1047
|
audience: audienceSchema,
|
|
1048
|
+
/** What a live write may change on each of the source's tools, and keep through the next apply. */
|
|
1049
|
+
overridable: overridable(CALLED_TOOL_OVERRIDABLES),
|
|
726
1050
|
});
|
|
727
1051
|
export type ManifestOpenApi = z.input<typeof manifestOpenApiSchema>;
|
|
728
1052
|
|
|
@@ -738,9 +1062,12 @@ const manifestServiceSchema = z.strictObject({
|
|
|
738
1062
|
});
|
|
739
1063
|
export type ManifestService = z.input<typeof manifestServiceSchema>;
|
|
740
1064
|
|
|
1065
|
+
/** A skill's description in characters. The console caps its editor at the same. */
|
|
1066
|
+
export const SKILL_LIMITS = { descriptionLength: 500 } as const;
|
|
1067
|
+
|
|
741
1068
|
const manifestSkillSchema = z.strictObject({
|
|
742
1069
|
name: slugSchema,
|
|
743
|
-
description: z.string().trim().min(1).max(
|
|
1070
|
+
description: z.string().trim().min(1).max(SKILL_LIMITS.descriptionLength),
|
|
744
1071
|
/** Rich text: it may mention tools, skills and datasets, and read `{{variables}}`. */
|
|
745
1072
|
body: z.string().trim().min(1),
|
|
746
1073
|
/** The key of the service the skill renders inside; fixed once created. */
|
|
@@ -767,11 +1094,14 @@ export const triggerThreadPolicySchema = z.discriminatedUnion("policy", [
|
|
|
767
1094
|
]);
|
|
768
1095
|
export type TriggerThreadPolicy = z.infer<typeof triggerThreadPolicySchema>;
|
|
769
1096
|
|
|
1097
|
+
/** A trigger's description and instructions in characters. The console caps its editor at the same. */
|
|
1098
|
+
export const TRIGGER_LIMITS = { descriptionLength: 500, instructionsLength: 20_000 } as const;
|
|
1099
|
+
|
|
770
1100
|
/** Fired with its secret, which the apply that creates it answers once and no manifest holds. */
|
|
771
1101
|
const manifestTriggerSchema = z.strictObject({
|
|
772
1102
|
/** `message` is the built-in trigger every agent has. */
|
|
773
1103
|
name: slugSchema.refine((name) => name !== MESSAGE_TRIGGER_NAME, "the built-in trigger's name"),
|
|
774
|
-
description: z.string().trim().min(1).max(
|
|
1104
|
+
description: z.string().trim().min(1).max(TRIGGER_LIMITS.descriptionLength),
|
|
775
1105
|
inputSchema: objectSchema.default(EMPTY_OBJECT),
|
|
776
1106
|
/** The shape the run answers in; absent, the run answers in free text. */
|
|
777
1107
|
outputSchema: objectSchema
|
|
@@ -781,7 +1111,7 @@ const manifestTriggerSchema = z.strictObject({
|
|
|
781
1111
|
)
|
|
782
1112
|
.optional(),
|
|
783
1113
|
/** Rich text: it may mention tools, skills and datasets, and read `{{variables}}` and `{{/payload/pointer}}`s. */
|
|
784
|
-
instructions: z.string().trim().min(1).max(
|
|
1114
|
+
instructions: z.string().trim().min(1).max(TRIGGER_LIMITS.instructionsLength),
|
|
785
1115
|
threadPolicy: triggerThreadPolicySchema.default({ policy: "single" }),
|
|
786
1116
|
/** Firings a day per agent; absent, no limit. */
|
|
787
1117
|
budgetPerDay: z.number().int().positive().optional(),
|
|
@@ -818,14 +1148,34 @@ const manifestDatasetHostSchema = z
|
|
|
818
1148
|
.strictObject({
|
|
819
1149
|
name: datasetIdentifierSchema,
|
|
820
1150
|
url: upstreamUrlSchema,
|
|
821
|
-
|
|
1151
|
+
headers: headersSchema,
|
|
822
1152
|
timeoutMs: setting(SETTING_BOUNDS.datasetHost.timeoutMs),
|
|
1153
|
+
rateLimits: z
|
|
1154
|
+
.strictObject({
|
|
1155
|
+
allUsers: z
|
|
1156
|
+
.strictObject({
|
|
1157
|
+
queriesPerMinute: rateLimit(
|
|
1158
|
+
datasetHostRateLimits.queriesPerMinute,
|
|
1159
|
+
"allUsers",
|
|
1160
|
+
),
|
|
1161
|
+
})
|
|
1162
|
+
.prefault({}),
|
|
1163
|
+
perUser: z
|
|
1164
|
+
.strictObject({
|
|
1165
|
+
queriesPerMinute: rateLimit(
|
|
1166
|
+
datasetHostRateLimits.queriesPerMinute,
|
|
1167
|
+
"perUser",
|
|
1168
|
+
),
|
|
1169
|
+
})
|
|
1170
|
+
.prefault({}),
|
|
1171
|
+
})
|
|
1172
|
+
.prefault({}),
|
|
823
1173
|
/** The datasets the host serves that agents may query. */
|
|
824
1174
|
expose: z.array(datasetIdentifierSchema).min(1).max(500),
|
|
825
1175
|
/** Among those, the ones listed in every turn, whatever the question. */
|
|
826
1176
|
pinned: z.array(datasetIdentifierSchema).max(50).default([]),
|
|
827
1177
|
audience: audienceSchema,
|
|
828
|
-
overridable: overridable(["timeoutMs"]),
|
|
1178
|
+
overridable: overridable(["timeoutMs", "rateLimits", "headers"]),
|
|
829
1179
|
})
|
|
830
1180
|
.superRefine((host, context) => {
|
|
831
1181
|
const unexposed = unexposedPins(host);
|
|
@@ -853,50 +1203,53 @@ export const mcpToolNamesSchema = z
|
|
|
853
1203
|
|
|
854
1204
|
const manifestMcpServerSchema = z.strictObject({
|
|
855
1205
|
/** Prefixes every tool the server contributes: scripts call `tools.<name>_<tool>()`. */
|
|
856
|
-
name:
|
|
1206
|
+
name: identifierSchema,
|
|
857
1207
|
url: upstreamUrlSchema,
|
|
858
|
-
|
|
1208
|
+
headers: declaredHeadersSchema(
|
|
859
1209
|
presentedHeaderSchema.refine(
|
|
860
1210
|
(name) => !mcpTransportHeaders.has(name.toLowerCase()),
|
|
861
1211
|
"a header the MCP transport sets",
|
|
862
1212
|
),
|
|
863
|
-
|
|
1213
|
+
headerSourceSchema,
|
|
864
1214
|
),
|
|
865
1215
|
/** The server's own tool names to offer; empty offers every tool. */
|
|
866
1216
|
include: mcpToolNamesSchema.default([]),
|
|
867
1217
|
exclude: mcpToolNamesSchema.default([]),
|
|
868
1218
|
audience: audienceSchema,
|
|
869
|
-
overridable: overridable(["include", "exclude"]),
|
|
1219
|
+
overridable: overridable(["include", "exclude", "headers"]),
|
|
870
1220
|
});
|
|
871
1221
|
export type ManifestMcpServer = z.input<typeof manifestMcpServerSchema>;
|
|
872
1222
|
|
|
873
1223
|
/**
|
|
874
1224
|
* Delivers its event to a receiver while it is declared. A delivery carries
|
|
875
|
-
* no run, so
|
|
876
|
-
* header that names the event.
|
|
1225
|
+
* no run, so its headers come from credentials and constants, and none
|
|
1226
|
+
* travels under the header that names the event.
|
|
877
1227
|
*/
|
|
878
1228
|
const manifestWebhookSchema = z.strictObject({
|
|
879
1229
|
name: slugSchema,
|
|
880
1230
|
description: z.string().trim().max(500).default(""),
|
|
881
1231
|
event: z.enum(SERVER_EVENTS),
|
|
882
1232
|
url: upstreamUrlSchema,
|
|
883
|
-
|
|
1233
|
+
headers: declaredHeadersSchema(
|
|
884
1234
|
presentedHeaderSchema.refine(
|
|
885
1235
|
(name) => name.toLowerCase() !== EVENT_HEADER.toLowerCase(),
|
|
886
1236
|
"the header that names the event",
|
|
887
1237
|
),
|
|
888
|
-
|
|
1238
|
+
runlessHeaderSourceSchema,
|
|
889
1239
|
),
|
|
890
1240
|
timeoutMs: setting(SETTING_BOUNDS.webhook.timeoutMs),
|
|
891
1241
|
audience: audienceSchema,
|
|
892
|
-
overridable: overridable(["description", "timeoutMs"]),
|
|
1242
|
+
overridable: overridable(["description", "timeoutMs", "headers"]),
|
|
893
1243
|
});
|
|
894
1244
|
export type ManifestWebhook = z.input<typeof manifestWebhookSchema>;
|
|
895
1245
|
|
|
1246
|
+
/** A collection's name and description in characters. The console caps its editor at the same. */
|
|
1247
|
+
export const COLLECTION_LIMITS = { nameLength: 80, descriptionLength: 2_000 } as const;
|
|
1248
|
+
|
|
896
1249
|
/** Holds documents, which are uploaded to it apart from the manifest. */
|
|
897
1250
|
const manifestCollectionSchema = z.strictObject({
|
|
898
|
-
name: z.string().trim().min(1).max(
|
|
899
|
-
description: z.string().trim().max(
|
|
1251
|
+
name: z.string().trim().min(1).max(COLLECTION_LIMITS.nameLength),
|
|
1252
|
+
description: z.string().trim().max(COLLECTION_LIMITS.descriptionLength).default(""),
|
|
900
1253
|
/** The key of the service every document in the collection belongs to. */
|
|
901
1254
|
service: slugSchema.optional(),
|
|
902
1255
|
/** Whether answers may name its documents; false keeps their names, sections and pages from the model. */
|
|
@@ -916,9 +1269,9 @@ const tokenAudienceSchema = z.string().trim().min(1).max(2048).optional();
|
|
|
916
1269
|
* How the agent verifies end-user tokens: against the keys its issuer
|
|
917
1270
|
* publishes at a JWKS URI, with a shared secret named in Cortex's secret
|
|
918
1271
|
* store and the one algorithm tokens are signed with, through its server's
|
|
919
|
-
* `/verify`, or not at all.
|
|
1272
|
+
* `/verify`, or not at all. Cortex stores it on the agent as written here.
|
|
920
1273
|
*/
|
|
921
|
-
const agentAuthSchema = z.discriminatedUnion("kind", [
|
|
1274
|
+
export const agentAuthSchema = z.discriminatedUnion("kind", [
|
|
922
1275
|
z.strictObject({
|
|
923
1276
|
kind: z.literal("jwks"),
|
|
924
1277
|
jwksUri: tlsUrlSchema,
|
|
@@ -932,14 +1285,38 @@ const agentAuthSchema = z.discriminatedUnion("kind", [
|
|
|
932
1285
|
issuer: issuerSchema,
|
|
933
1286
|
audience: tokenAudienceSchema,
|
|
934
1287
|
}),
|
|
935
|
-
|
|
1288
|
+
/**
|
|
1289
|
+
* Each visitor gets an id of their own from Cortex, kept in their
|
|
1290
|
+
* browser. With `recaptcha`, reCAPTCHA Enterprise scores each new
|
|
1291
|
+
* visitor first, and one below `minScore` gets none.
|
|
1292
|
+
*/
|
|
1293
|
+
z.strictObject({
|
|
1294
|
+
kind: z.literal("anonymous"),
|
|
1295
|
+
recaptcha: z
|
|
1296
|
+
.strictObject({
|
|
1297
|
+
siteKey: z.string().trim().min(1).max(200),
|
|
1298
|
+
projectId: z.string().trim().min(1).max(200),
|
|
1299
|
+
/** The secret holding a Google Cloud API key allowed to create assessments. */
|
|
1300
|
+
secret: nameSchema,
|
|
1301
|
+
minScore: z
|
|
1302
|
+
.number()
|
|
1303
|
+
.min(SETTING_BOUNDS.recaptcha.minScore.min)
|
|
1304
|
+
.max(SETTING_BOUNDS.recaptcha.minScore.max)
|
|
1305
|
+
.default(SETTING_BOUNDS.recaptcha.minScore.default),
|
|
1306
|
+
})
|
|
1307
|
+
.optional(),
|
|
1308
|
+
}),
|
|
936
1309
|
z.strictObject({ kind: z.literal("server") }),
|
|
937
1310
|
]);
|
|
938
1311
|
|
|
939
|
-
const {
|
|
1312
|
+
const { agentContext, agentLimits } = SETTING_BOUNDS;
|
|
1313
|
+
const { agent: agentRateLimits } = RATE_LIMITS;
|
|
940
1314
|
|
|
941
|
-
/**
|
|
942
|
-
|
|
1315
|
+
/**
|
|
1316
|
+
* How the agent runs; every setting left out keeps its default. The
|
|
1317
|
+
* console's settings input requires each of them.
|
|
1318
|
+
*/
|
|
1319
|
+
export const agentSettingsSchema = z.strictObject({
|
|
943
1320
|
/** Lets `searchRegistries` search the knowledge in the collections the agent sees. */
|
|
944
1321
|
ragEnabled: z.boolean().default(false),
|
|
945
1322
|
/** Lets `searchRegistries` search the tools the agent sees, beyond the pinned and mentioned ones. */
|
|
@@ -957,10 +1334,23 @@ const agentSettingsSchema = z.strictObject({
|
|
|
957
1334
|
media: z.array(z.enum(MEDIA_FORMS)).default([...DEFAULT_MEDIA_FORMS]),
|
|
958
1335
|
/** Off, end users cannot message the agent: only its triggers reach it. */
|
|
959
1336
|
messageTrigger: z.boolean().default(true),
|
|
960
|
-
defaultLocale: localeSchema.default(
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1337
|
+
defaultLocale: localeSchema.default(BASE_LOCALE),
|
|
1338
|
+
rateLimits: z
|
|
1339
|
+
.strictObject({
|
|
1340
|
+
allUsers: z
|
|
1341
|
+
.strictObject({
|
|
1342
|
+
messagesPerMinute: rateLimit(agentRateLimits.messagesPerMinute, "allUsers"),
|
|
1343
|
+
tokensPerDay: rateLimit(agentRateLimits.tokensPerDay, "allUsers"),
|
|
1344
|
+
})
|
|
1345
|
+
.prefault({}),
|
|
1346
|
+
perUser: z
|
|
1347
|
+
.strictObject({
|
|
1348
|
+
messagesPerMinute: rateLimit(agentRateLimits.messagesPerMinute, "perUser"),
|
|
1349
|
+
tokensPerDay: rateLimit(agentRateLimits.tokensPerDay, "perUser"),
|
|
1350
|
+
})
|
|
1351
|
+
.prefault({}),
|
|
1352
|
+
})
|
|
1353
|
+
.prefault({}),
|
|
964
1354
|
context: z
|
|
965
1355
|
.strictObject({
|
|
966
1356
|
maxContextTokens: setting(agentContext.maxContextTokens),
|
|
@@ -982,9 +1372,12 @@ const agentSettingsSchema = z.strictObject({
|
|
|
982
1372
|
.prefault({}),
|
|
983
1373
|
});
|
|
984
1374
|
|
|
1375
|
+
/** An agent's name in characters. The console caps its editor at the same. */
|
|
1376
|
+
export const AGENT_LIMITS = { nameLength: 120 } as const;
|
|
1377
|
+
|
|
985
1378
|
const manifestAgentSchema = z.strictObject({
|
|
986
1379
|
slug: agentSlugSchema,
|
|
987
|
-
name: z.string().trim().min(1).max(
|
|
1380
|
+
name: z.string().trim().min(1).max(AGENT_LIMITS.nameLength),
|
|
988
1381
|
/** The server it belongs to, by name; a manifest never creates one. */
|
|
989
1382
|
server: nameSchema,
|
|
990
1383
|
/** Rich text: it may mention tools and skills, and read `{{variables}}`. */
|
|
@@ -1002,23 +1395,61 @@ const manifestAgentSchema = z.strictObject({
|
|
|
1002
1395
|
})
|
|
1003
1396
|
.default({}),
|
|
1004
1397
|
settings: agentSettingsSchema.prefault({}),
|
|
1005
|
-
suggestions: suggestionsSchema.default(
|
|
1006
|
-
greeting: greetingSchema.default(
|
|
1398
|
+
suggestions: suggestionsSchema.default(() => byLocale(() => [])),
|
|
1399
|
+
greeting: greetingSchema.default(() => byLocale(() => "")),
|
|
1007
1400
|
overridable: overridable(["name", "prompt", "models", "settings", "suggestions", "greeting"]),
|
|
1008
1401
|
});
|
|
1009
1402
|
export type ManifestAgent = z.input<typeof manifestAgentSchema>;
|
|
1010
1403
|
|
|
1011
|
-
/**
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1404
|
+
/**
|
|
1405
|
+
* Every kind of entry a manifest owns a row for, in the order an apply
|
|
1406
|
+
* reports a name another row holds and a kind its actor may not write. An
|
|
1407
|
+
* OpenAPI source is none: its operations become `tools` entries.
|
|
1408
|
+
*/
|
|
1409
|
+
export const MANIFEST_KINDS = [
|
|
1410
|
+
"agents",
|
|
1411
|
+
"services",
|
|
1412
|
+
"tools",
|
|
1413
|
+
"skills",
|
|
1414
|
+
"triggers",
|
|
1415
|
+
"datasetHosts",
|
|
1416
|
+
"mcpServers",
|
|
1417
|
+
"webhooks",
|
|
1418
|
+
"collections",
|
|
1419
|
+
] as const;
|
|
1420
|
+
export type ManifestKind = (typeof MANIFEST_KINDS)[number];
|
|
1015
1421
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1422
|
+
/**
|
|
1423
|
+
* The field each kind's entries go by, which its row's name holds: an
|
|
1424
|
+
* agent's slug, a service's key, any other entry's name. A list holds each
|
|
1425
|
+
* once, so one entry means one row.
|
|
1426
|
+
*/
|
|
1427
|
+
export const MANIFEST_NAME_FIELDS = {
|
|
1428
|
+
agents: "slug",
|
|
1429
|
+
tools: "name",
|
|
1430
|
+
services: "key",
|
|
1431
|
+
skills: "name",
|
|
1432
|
+
triggers: "name",
|
|
1433
|
+
datasetHosts: "name",
|
|
1434
|
+
mcpServers: "name",
|
|
1435
|
+
webhooks: "name",
|
|
1436
|
+
collections: "name",
|
|
1437
|
+
} as const satisfies Record<ManifestKind, string>;
|
|
1438
|
+
|
|
1439
|
+
/** A list of entries named by their `field`, refusing a name they repeat. */
|
|
1440
|
+
function listOf<TField extends string, TEntry extends z.ZodType<Record<TField, string>>>(
|
|
1441
|
+
field: TField,
|
|
1442
|
+
entry: TEntry,
|
|
1443
|
+
) {
|
|
1444
|
+
return z
|
|
1445
|
+
.array(entry)
|
|
1446
|
+
.refine(
|
|
1447
|
+
(entries) => new Set(entries.map((value) => value[field])).size === entries.length,
|
|
1448
|
+
"a name repeats",
|
|
1449
|
+
)
|
|
1450
|
+
.default([]);
|
|
1018
1451
|
}
|
|
1019
1452
|
|
|
1020
|
-
const byName = (entry: { name: string }) => entry.name;
|
|
1021
|
-
|
|
1022
1453
|
/** The id of Cortex's own manifest, which only Cortex applies: the management API refuses any other under it. */
|
|
1023
1454
|
export const CORTEX_MANIFEST_ID = "cortex";
|
|
1024
1455
|
|
|
@@ -1032,24 +1463,26 @@ export const CORTEX_MANIFEST_ID = "cortex";
|
|
|
1032
1463
|
export const manifestSchema = z.strictObject({
|
|
1033
1464
|
/** Fixed for the deployment it describes: the rows an apply owns carry it. */
|
|
1034
1465
|
id: slugSchema,
|
|
1035
|
-
agents: listOf(
|
|
1036
|
-
tools: listOf(
|
|
1037
|
-
openapi: listOf(
|
|
1038
|
-
services: listOf(
|
|
1039
|
-
skills: listOf(
|
|
1040
|
-
triggers: listOf(
|
|
1041
|
-
datasetHosts: listOf(
|
|
1042
|
-
mcpServers: listOf(
|
|
1043
|
-
webhooks: listOf(
|
|
1044
|
-
collections: listOf(
|
|
1045
|
-
});
|
|
1466
|
+
agents: listOf(MANIFEST_NAME_FIELDS.agents, manifestAgentSchema),
|
|
1467
|
+
tools: listOf(MANIFEST_NAME_FIELDS.tools, manifestToolSchema),
|
|
1468
|
+
openapi: listOf("name", manifestOpenApiSchema),
|
|
1469
|
+
services: listOf(MANIFEST_NAME_FIELDS.services, manifestServiceSchema),
|
|
1470
|
+
skills: listOf(MANIFEST_NAME_FIELDS.skills, manifestSkillSchema),
|
|
1471
|
+
triggers: listOf(MANIFEST_NAME_FIELDS.triggers, manifestTriggerSchema),
|
|
1472
|
+
datasetHosts: listOf(MANIFEST_NAME_FIELDS.datasetHosts, manifestDatasetHostSchema),
|
|
1473
|
+
mcpServers: listOf(MANIFEST_NAME_FIELDS.mcpServers, manifestMcpServerSchema),
|
|
1474
|
+
webhooks: listOf(MANIFEST_NAME_FIELDS.webhooks, manifestWebhookSchema),
|
|
1475
|
+
collections: listOf(MANIFEST_NAME_FIELDS.collections, manifestCollectionSchema),
|
|
1476
|
+
} satisfies Record<ManifestKind | "id" | "openapi", z.ZodType>);
|
|
1046
1477
|
/** A manifest as written: every field with a default may be left out. */
|
|
1047
1478
|
export type Manifest = z.input<typeof manifestSchema>;
|
|
1048
1479
|
|
|
1049
|
-
|
|
1050
|
-
const manifestKindSchema = manifestSchema.keyof().exclude(["id", "openapi"]);
|
|
1480
|
+
const manifestKindSchema = z.enum(MANIFEST_KINDS);
|
|
1051
1481
|
|
|
1052
|
-
/**
|
|
1482
|
+
/**
|
|
1483
|
+
* Entry names per kind, each by its `MANIFEST_NAME_FIELDS` field. An OpenAPI
|
|
1484
|
+
* source's operations are counted among `tools`.
|
|
1485
|
+
*/
|
|
1053
1486
|
const namesByKindSchema = z.record(manifestKindSchema, z.array(z.string()));
|
|
1054
1487
|
|
|
1055
1488
|
/** What an apply creates, updates and deletes, kind by kind; a preview answers the same without doing it. */
|