@m6d/cortex-cli 0.15.0 → 0.16.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 +38 -22
- 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 +77 -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 +369 -124
- package/src/contracts/server/index.ts +44 -53
- package/src/contracts/triggers/index.ts +13 -9
- package/src/contracts/wire/index.ts +126 -25
- package/src/management/client.ts +79 -117
- package/src/management/profiles.ts +11 -22
- package/src/scaffold/files.ts +11 -9
- 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,8 +62,9 @@ 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: {
|
|
@@ -99,6 +102,16 @@ function setting({ min, max, default: fallback }: Bounds) {
|
|
|
99
102
|
return z.number().int().min(min).max(max).default(fallback);
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
/** One number per setting a group of `SETTING_BOUNDS` names. */
|
|
106
|
+
export type SettingValues<TBounds> = { [Name in keyof TBounds]: number };
|
|
107
|
+
|
|
108
|
+
/** Every setting of a group at its default, as a row that sets none stores them. */
|
|
109
|
+
export function settingDefaults<TBounds extends Record<string, Bounds>>(bounds: TBounds) {
|
|
110
|
+
return Object.fromEntries(
|
|
111
|
+
Object.entries(bounds).map(([name, bound]) => [name, bound.default]),
|
|
112
|
+
) as SettingValues<TBounds>;
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
/**
|
|
103
116
|
* The fields of an entry users may change in Cortex after an apply, each
|
|
104
117
|
* once, from the ones `fields` allows; none when left out. Every other field
|
|
@@ -111,16 +124,14 @@ function setting({ min, max, default: fallback }: Bounds) {
|
|
|
111
124
|
function overridable<const TField extends string>(fields: readonly [TField, ...TField[]]) {
|
|
112
125
|
return z
|
|
113
126
|
.array(z.enum(fields))
|
|
114
|
-
.refine(
|
|
115
|
-
uniqueBy((field: TField) => field),
|
|
116
|
-
"a field repeats",
|
|
117
|
-
)
|
|
127
|
+
.refine((listed) => new Set(listed).size === listed.length, "a field repeats")
|
|
118
128
|
.default([]);
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
/** Consumer-facing slugs: the manifest id, service keys, skill names and trigger names share it. */
|
|
122
132
|
export const SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
123
|
-
const
|
|
133
|
+
export const SLUG_MAX_LENGTH = 64;
|
|
134
|
+
const slugSchema = z.string().regex(SLUG_PATTERN).max(SLUG_MAX_LENGTH);
|
|
124
135
|
|
|
125
136
|
/** A DNS host name in lower case, the way `URL` reports one: dot-separated labels of letters, digits and inner hyphens. */
|
|
126
137
|
export const HOSTNAME_PATTERN =
|
|
@@ -243,6 +254,92 @@ export const toolConfigByType = {
|
|
|
243
254
|
client: z.object({}),
|
|
244
255
|
};
|
|
245
256
|
|
|
257
|
+
/** Every tool type, in the order the database enum lists them; see `TOOL_TYPES`. */
|
|
258
|
+
export const TOOL_TYPE_NAMES = ["http", "embedded", "soap", "native", "client"] as const;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* What each tool type is, so no reader restates it: who answers its calls
|
|
262
|
+
* (`kind`: Cortex for a server tool, the browser for a client one), whether
|
|
263
|
+
* it calls an endpoint, whether a call may wait for the end user's
|
|
264
|
+
* approval, whether an input field may be filled from a source Cortex holds
|
|
265
|
+
* (`x-source`) or name its place in the request (`x-target`), whether an
|
|
266
|
+
* author creates one, and the `method` or `readOnly` the type pins whatever
|
|
267
|
+
* the author says. An embedded tool's initiate is never read-only, so a
|
|
268
|
+
* widget reload rejoins its session, and asks no approval, since the widget
|
|
269
|
+
* answers it. SOAP and native tools are POSTed, and a native tool comes from
|
|
270
|
+
* its server's manifest alone, with no field Cortex fills or places. A
|
|
271
|
+
* client tool calls nothing: the client answers it.
|
|
272
|
+
*/
|
|
273
|
+
export const TOOL_TYPES = {
|
|
274
|
+
http: {
|
|
275
|
+
kind: "server",
|
|
276
|
+
calls: true,
|
|
277
|
+
approval: true,
|
|
278
|
+
sources: true,
|
|
279
|
+
targets: true,
|
|
280
|
+
creatable: true,
|
|
281
|
+
},
|
|
282
|
+
embedded: {
|
|
283
|
+
kind: "client",
|
|
284
|
+
calls: true,
|
|
285
|
+
approval: false,
|
|
286
|
+
sources: true,
|
|
287
|
+
targets: false,
|
|
288
|
+
creatable: true,
|
|
289
|
+
readOnly: false,
|
|
290
|
+
},
|
|
291
|
+
soap: {
|
|
292
|
+
kind: "server",
|
|
293
|
+
calls: true,
|
|
294
|
+
approval: true,
|
|
295
|
+
sources: true,
|
|
296
|
+
targets: true,
|
|
297
|
+
creatable: true,
|
|
298
|
+
method: "POST",
|
|
299
|
+
},
|
|
300
|
+
native: {
|
|
301
|
+
kind: "server",
|
|
302
|
+
calls: true,
|
|
303
|
+
approval: true,
|
|
304
|
+
sources: false,
|
|
305
|
+
targets: false,
|
|
306
|
+
creatable: false,
|
|
307
|
+
method: "POST",
|
|
308
|
+
},
|
|
309
|
+
client: {
|
|
310
|
+
kind: "client",
|
|
311
|
+
calls: false,
|
|
312
|
+
approval: false,
|
|
313
|
+
sources: false,
|
|
314
|
+
targets: false,
|
|
315
|
+
creatable: true,
|
|
316
|
+
readOnly: true,
|
|
317
|
+
},
|
|
318
|
+
} as const satisfies Record<
|
|
319
|
+
(typeof TOOL_TYPE_NAMES)[number],
|
|
320
|
+
{
|
|
321
|
+
kind: "server" | "client";
|
|
322
|
+
calls: boolean;
|
|
323
|
+
approval: boolean;
|
|
324
|
+
sources: boolean;
|
|
325
|
+
targets: boolean;
|
|
326
|
+
creatable: boolean;
|
|
327
|
+
method?: (typeof TOOL_METHODS)[number];
|
|
328
|
+
readOnly?: boolean;
|
|
329
|
+
}
|
|
330
|
+
>;
|
|
331
|
+
|
|
332
|
+
type CreatableToolType = {
|
|
333
|
+
[TType in keyof typeof TOOL_TYPES]: (typeof TOOL_TYPES)[TType]["creatable"] extends true
|
|
334
|
+
? TType
|
|
335
|
+
: never;
|
|
336
|
+
}[keyof typeof TOOL_TYPES];
|
|
337
|
+
|
|
338
|
+
/** The types an author creates a tool as, in `TOOL_TYPE_NAMES` order; a native tool comes from its server's manifest. */
|
|
339
|
+
export const CREATABLE_TOOL_TYPES = TOOL_TYPE_NAMES.filter(
|
|
340
|
+
(type): type is CreatableToolType => TOOL_TYPES[type].creatable,
|
|
341
|
+
);
|
|
342
|
+
|
|
246
343
|
/** A `{name}` parameter in a tool's endpoint URL, filled from the input on each call. */
|
|
247
344
|
export const ENDPOINT_PARAM_PATTERN = /\{([^{}]+)\}/g;
|
|
248
345
|
|
|
@@ -261,9 +358,21 @@ export function confinesEndpointParams(url: string) {
|
|
|
261
358
|
/** An upstream Cortex calls: http or https. */
|
|
262
359
|
const upstreamUrlSchema = z.url({ protocol: /^https?$/ });
|
|
263
360
|
|
|
361
|
+
/**
|
|
362
|
+
* What a call presents: a header name and the prefix before the value in
|
|
363
|
+
* characters, and how many headers at most (a real upstream wants one or
|
|
364
|
+
* two). The console caps its credential rows at the same.
|
|
365
|
+
*/
|
|
366
|
+
export const CREDENTIAL_LIMITS = { headerLength: 120, prefixLength: 40, presentations: 8 } as const;
|
|
367
|
+
|
|
264
368
|
/** An HTTP header field name (RFC 9110 token). */
|
|
265
369
|
export const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
266
|
-
const headerNameSchema = z
|
|
370
|
+
const headerNameSchema = z
|
|
371
|
+
.string()
|
|
372
|
+
.trim()
|
|
373
|
+
.min(1)
|
|
374
|
+
.max(CREDENTIAL_LIMITS.headerLength)
|
|
375
|
+
.regex(HEADER_NAME_PATTERN);
|
|
267
376
|
|
|
268
377
|
/** Headers Cortex or fetch fills on every request. Lower-cased. */
|
|
269
378
|
export const transportSetHeaders = ["accept", "content-type", "content-length", "host"] as const;
|
|
@@ -292,11 +401,8 @@ export const transportOwnedHeaders = new Set<string>([
|
|
|
292
401
|
...transportForbiddenHeaders,
|
|
293
402
|
]);
|
|
294
403
|
|
|
295
|
-
/** At most this many headers a call presents; a real upstream wants one or two. */
|
|
296
|
-
export const CREDENTIAL_PRESENTATION_LIMIT = 8;
|
|
297
|
-
|
|
298
404
|
/** The index of each entry presenting a header an earlier entry already presents, compared case-insensitively. */
|
|
299
|
-
|
|
405
|
+
function repeatedHeaders(entries: ReadonlyArray<{ header: string }>) {
|
|
300
406
|
const seen = new Set<string>();
|
|
301
407
|
return entries.flatMap((entry, index) => {
|
|
302
408
|
const name = entry.header.toLowerCase();
|
|
@@ -306,8 +412,10 @@ export function repeatedHeaders(entries: ReadonlyArray<{ header: string }>) {
|
|
|
306
412
|
});
|
|
307
413
|
}
|
|
308
414
|
|
|
415
|
+
/** The longest name Cortex holds a server, model provider, credential or secret under. */
|
|
416
|
+
export const NAME_MAX_LENGTH = 120;
|
|
309
417
|
/** A server, model provider, credential or secret, by the name Cortex holds it under. */
|
|
310
|
-
const nameSchema = z.string().trim().min(1).max(
|
|
418
|
+
const nameSchema = z.string().trim().min(1).max(NAME_MAX_LENGTH);
|
|
311
419
|
|
|
312
420
|
const presentedHeaderSchema = headerNameSchema.refine(
|
|
313
421
|
(name) => !transportOwnedHeaders.has(name.toLowerCase()),
|
|
@@ -316,30 +424,45 @@ const presentedHeaderSchema = headerNameSchema.refine(
|
|
|
316
424
|
|
|
317
425
|
/**
|
|
318
426
|
* What every call carries: each entry a header, what precedes the value in
|
|
319
|
-
* it (`"Bearer "`, say), and the credential by name
|
|
320
|
-
*
|
|
427
|
+
* it (`"Bearer "`, say), and the credential by name, as `entry` validates
|
|
428
|
+
* it, at most `CREDENTIAL_LIMITS.presentations` of them. A header is
|
|
429
|
+
* presented once; `repeated` words the refusal of a later one.
|
|
321
430
|
*/
|
|
322
|
-
function presentationsSchema<
|
|
323
|
-
|
|
324
|
-
|
|
431
|
+
export function presentationsSchema<TEntry extends z.ZodType<{ header: string }>>(
|
|
432
|
+
entry: TEntry,
|
|
433
|
+
repeated: () => string,
|
|
325
434
|
) {
|
|
326
435
|
return z
|
|
327
|
-
.array(
|
|
328
|
-
.max(
|
|
436
|
+
.array(entry)
|
|
437
|
+
.max(CREDENTIAL_LIMITS.presentations)
|
|
329
438
|
.superRefine((entries, context) => {
|
|
330
439
|
for (const index of repeatedHeaders(entries)) {
|
|
331
440
|
context.addIssue({
|
|
332
441
|
code: "custom",
|
|
333
442
|
path: [index, "header"],
|
|
334
|
-
message:
|
|
443
|
+
message: repeated(),
|
|
335
444
|
});
|
|
336
445
|
}
|
|
337
|
-
})
|
|
338
|
-
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** The presentations a manifest entry declares: no unknown key, an omitted prefix or list empty. */
|
|
450
|
+
function declaredPresentationsSchema<TCredential extends string | null>(
|
|
451
|
+
header: z.ZodString,
|
|
452
|
+
credential: z.ZodType<TCredential, TCredential>,
|
|
453
|
+
) {
|
|
454
|
+
return presentationsSchema(
|
|
455
|
+
z.strictObject({
|
|
456
|
+
header,
|
|
457
|
+
prefix: z.string().max(CREDENTIAL_LIMITS.prefixLength).default(""),
|
|
458
|
+
credential,
|
|
459
|
+
}),
|
|
460
|
+
() => "a header presented twice",
|
|
461
|
+
).default([]);
|
|
339
462
|
}
|
|
340
463
|
|
|
341
464
|
/** A credential, or `null` for the run's own token (the end user's in a chat, the trigger's in a firing). */
|
|
342
|
-
const credentialsSchema =
|
|
465
|
+
const credentialsSchema = declaredPresentationsSchema(presentedHeaderSchema, nameSchema.nullable());
|
|
343
466
|
|
|
344
467
|
/**
|
|
345
468
|
* Where Cortex fills a top-level scalar input field from, out of the model's
|
|
@@ -348,7 +471,7 @@ const credentialsSchema = presentationsSchema(presentedHeaderSchema, nameSchema.
|
|
|
348
471
|
*/
|
|
349
472
|
export const fieldSourceSchema = z.discriminatedUnion("kind", [
|
|
350
473
|
z.object({ kind: z.literal("variable"), name: z.string().trim().min(1).max(64) }),
|
|
351
|
-
z.object({ kind: z.literal("secret"), name:
|
|
474
|
+
z.object({ kind: z.literal("secret"), name: nameSchema }),
|
|
352
475
|
z.object({
|
|
353
476
|
kind: z.literal("constant"),
|
|
354
477
|
value: z.union([z.string().max(4_000), z.number(), z.boolean()]),
|
|
@@ -363,7 +486,7 @@ export type FieldSource = z.infer<typeof fieldSourceSchema>;
|
|
|
363
486
|
* string only, and a constant must be a value of the field's type. Whether a
|
|
364
487
|
* variable is declared is the agent's server's to say.
|
|
365
488
|
*/
|
|
366
|
-
|
|
489
|
+
function sourceFits(
|
|
367
490
|
source: FieldSource,
|
|
368
491
|
field: { type: unknown; enum?: ReadonlyArray<string | null> },
|
|
369
492
|
) {
|
|
@@ -426,16 +549,26 @@ export const fieldTargetSchema = z.discriminatedUnion("kind", [
|
|
|
426
549
|
]);
|
|
427
550
|
export type FieldTarget = z.infer<typeof fieldTargetSchema>;
|
|
428
551
|
|
|
429
|
-
|
|
552
|
+
/** The types a field holds one value of. */
|
|
553
|
+
export const SCALAR_TYPES = ["string", "number", "integer", "boolean"] as const;
|
|
430
554
|
const schemaTypeSchema = z.enum([...SCALAR_TYPES, "object", "array"]);
|
|
431
555
|
|
|
556
|
+
/** A field's name and description in characters, and the values its enum lists. The console caps its field rows at the same. */
|
|
557
|
+
export const FIELD_LIMITS = { nameLength: 120, descriptionLength: 500, enumValues: 50 } as const;
|
|
558
|
+
|
|
432
559
|
/** Untrimmed: two names that differ only in surrounding spaces would collapse into one. */
|
|
433
560
|
const propertyNameSchema = z
|
|
434
561
|
.string()
|
|
435
562
|
.min(1)
|
|
436
|
-
.max(
|
|
563
|
+
.max(FIELD_LIMITS.nameLength)
|
|
437
564
|
.refine((name) => name === name.trim(), "no surrounding spaces");
|
|
438
565
|
|
|
566
|
+
/** The order an object's properties are shown in, each named once. */
|
|
567
|
+
const propertyOrderSchema = z
|
|
568
|
+
.array(z.string())
|
|
569
|
+
.refine((names) => new Set(names).size === names.length, "a name repeats")
|
|
570
|
+
.optional();
|
|
571
|
+
|
|
439
572
|
/** What an array holds: a scalar, or an object with properties of its own. */
|
|
440
573
|
const itemsSchema = z.strictObject({
|
|
441
574
|
type: z.enum([...SCALAR_TYPES, "object"]),
|
|
@@ -443,29 +576,32 @@ const itemsSchema = z.strictObject({
|
|
|
443
576
|
return z.record(propertyNameSchema, schemaPropertySchema).optional();
|
|
444
577
|
},
|
|
445
578
|
required: z.array(z.string()).optional(),
|
|
579
|
+
"x-order": propertyOrderSchema,
|
|
446
580
|
});
|
|
447
581
|
|
|
448
582
|
/**
|
|
449
583
|
* A JSON Schema property, in the part of JSON Schema a field editor holds:
|
|
450
584
|
* 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
|
|
585
|
+
* strings for a string, an object's `properties`, `required` and the
|
|
586
|
+
* `x-order` they are shown in, an array's `items`, a `description`, and
|
|
587
|
+
* the extensions `x-sensitive` (redacted from logs), `x-source` (top-level
|
|
588
|
+
* scalar fields of a tool's input only) and `x-target` (top-level fields of
|
|
589
|
+
* a tool's input only).
|
|
455
590
|
*/
|
|
456
591
|
const schemaPropertySchema = z.strictObject({
|
|
457
592
|
type: z.union([schemaTypeSchema, z.tuple([schemaTypeSchema, z.literal("null")])]),
|
|
458
|
-
description: z.string().trim().max(
|
|
593
|
+
description: z.string().trim().max(FIELD_LIMITS.descriptionLength).optional(),
|
|
459
594
|
/** A nullable enum lists `null` beside its values, as the console stores one. */
|
|
460
595
|
enum: z
|
|
461
596
|
.array(z.union([z.string().trim().min(1), z.null()]))
|
|
462
597
|
.min(1)
|
|
463
|
-
.max(
|
|
598
|
+
.max(FIELD_LIMITS.enumValues + 1)
|
|
464
599
|
.optional(),
|
|
465
600
|
get properties() {
|
|
466
601
|
return z.record(propertyNameSchema, schemaPropertySchema).optional();
|
|
467
602
|
},
|
|
468
603
|
required: z.array(z.string()).optional(),
|
|
604
|
+
"x-order": propertyOrderSchema,
|
|
469
605
|
get items() {
|
|
470
606
|
return itemsSchema.optional();
|
|
471
607
|
},
|
|
@@ -483,24 +619,30 @@ function typeOf(node: { type: SchemaProperty["type"] }) {
|
|
|
483
619
|
/** A refusal below a node: where, relative to it, and why. */
|
|
484
620
|
type RowIssue = { at: Array<string | number>; message: string };
|
|
485
621
|
|
|
486
|
-
/**
|
|
487
|
-
|
|
622
|
+
/** A node holding properties: a property, an array's items, or an input or output schema. */
|
|
623
|
+
type ObjectNode = {
|
|
488
624
|
type: SchemaProperty["type"];
|
|
489
625
|
properties?: Record<string, SchemaProperty>;
|
|
490
626
|
required?: string[];
|
|
491
|
-
|
|
627
|
+
"x-order"?: string[];
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
/** What a node's own keywords break: `properties`, `required` or `x-order` off an object, or the last two naming no property. */
|
|
631
|
+
function nodeIssues(node: ObjectNode) {
|
|
492
632
|
const issues = new Array<RowIssue>();
|
|
493
633
|
const object = typeOf(node) === "object";
|
|
494
|
-
for (const keyword of ["properties", "required"] as const) {
|
|
634
|
+
for (const keyword of ["properties", "required", "x-order"] as const) {
|
|
495
635
|
if (!object && node[keyword] !== undefined) {
|
|
496
636
|
issues.push({ at: [keyword], message: "only an object has it" });
|
|
497
637
|
}
|
|
498
638
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
639
|
+
for (const keyword of ["required", "x-order"] as const) {
|
|
640
|
+
node[keyword]?.forEach((name, index) => {
|
|
641
|
+
if (!Object.hasOwn(node.properties ?? {}, name)) {
|
|
642
|
+
issues.push({ at: [keyword, index], message: "names no property" });
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
504
646
|
return issues;
|
|
505
647
|
}
|
|
506
648
|
|
|
@@ -513,20 +655,24 @@ function enumIssueOf(property: SchemaProperty, type: string | undefined) {
|
|
|
513
655
|
if (property.enum === undefined) return undefined;
|
|
514
656
|
if (type !== "string") return "an enum lists a string's values";
|
|
515
657
|
const values = property.enum.filter((value) => value !== null);
|
|
516
|
-
if (values.length === 0 || values.length >
|
|
658
|
+
if (values.length === 0 || values.length > FIELD_LIMITS.enumValues) {
|
|
659
|
+
return `an enum lists 1 to ${FIELD_LIMITS.enumValues} values`;
|
|
660
|
+
}
|
|
517
661
|
const nullable = Array.isArray(property.type);
|
|
518
662
|
if (nullable === property.enum.includes(null)) return undefined;
|
|
519
663
|
return nullable ? "a nullable enum lists null" : "only a nullable enum lists null";
|
|
520
664
|
}
|
|
521
665
|
|
|
666
|
+
/** The extensions a tool's input binds a field with: a source filling it, a place in the request. */
|
|
667
|
+
type Binding = "x-source" | "x-target";
|
|
668
|
+
|
|
522
669
|
/**
|
|
523
670
|
* What one property breaks of itself: an `enum` off a string, with no value,
|
|
524
671
|
* 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.
|
|
672
|
+
* `items` off an array or an array without them; and what its bindings
|
|
673
|
+
* break (`bindingIssues`).
|
|
528
674
|
*/
|
|
529
|
-
function propertyIssues(property: SchemaProperty,
|
|
675
|
+
function propertyIssues(property: SchemaProperty, bindings: ReadonlyArray<Binding> | undefined) {
|
|
530
676
|
const issues = new Array<RowIssue>();
|
|
531
677
|
const type = typeOf(property);
|
|
532
678
|
const enumIssue = enumIssueOf(property, type);
|
|
@@ -537,21 +683,41 @@ function propertyIssues(property: SchemaProperty, bindable: boolean) {
|
|
|
537
683
|
message: type === "array" ? "an array declares its items" : "only an array has it",
|
|
538
684
|
});
|
|
539
685
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
686
|
+
return [...issues, ...bindingIssues(property, type, bindings)];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* What a property's bindings break: one other than `bindings` directly under
|
|
691
|
+
* a tool's input (none anywhere else), an `x-source` off a scalar, or a
|
|
692
|
+
* source that does not fit the field.
|
|
693
|
+
*/
|
|
694
|
+
function bindingIssues(
|
|
695
|
+
property: SchemaProperty,
|
|
696
|
+
type: string | undefined,
|
|
697
|
+
bindings: ReadonlyArray<Binding> | undefined,
|
|
698
|
+
) {
|
|
699
|
+
const issues = new Array<RowIssue>();
|
|
700
|
+
const scalar = SCALAR_TYPES.some((scalar) => scalar === type);
|
|
701
|
+
// A source fills a scalar; a target places a field of any type.
|
|
702
|
+
const fields = { "x-source": "a top-level scalar field", "x-target": "a top-level field" };
|
|
703
|
+
for (const binding of ["x-source", "x-target"] as const) {
|
|
704
|
+
if (property[binding] === undefined) continue;
|
|
705
|
+
const reaches = bindings !== undefined && (binding === "x-target" || scalar);
|
|
706
|
+
if (reaches && bindings.includes(binding)) continue;
|
|
548
707
|
issues.push({
|
|
549
|
-
at: [
|
|
550
|
-
message:
|
|
708
|
+
at: [binding],
|
|
709
|
+
message: reaches
|
|
710
|
+
? "this type of tool takes none"
|
|
711
|
+
: `only ${fields[binding]} of a tool's input has it`,
|
|
551
712
|
});
|
|
552
713
|
}
|
|
553
714
|
const source = property["x-source"];
|
|
554
|
-
if (
|
|
715
|
+
if (
|
|
716
|
+
scalar &&
|
|
717
|
+
bindings?.includes("x-source") &&
|
|
718
|
+
source &&
|
|
719
|
+
!sourceFits(source, { type, enum: property.enum })
|
|
720
|
+
) {
|
|
555
721
|
issues.push({ at: ["x-source"], message: "the source does not fit the field's type" });
|
|
556
722
|
}
|
|
557
723
|
return issues;
|
|
@@ -560,18 +726,15 @@ function propertyIssues(property: SchemaProperty, bindable: boolean) {
|
|
|
560
726
|
/**
|
|
561
727
|
* Refuses, at every depth below `path`, what the console's field rows cannot
|
|
562
728
|
* hold, since it stores a schema as rows and would drop or change it in
|
|
563
|
-
* silence (`nodeIssues`, `propertyIssues`);
|
|
564
|
-
*
|
|
729
|
+
* silence (`nodeIssues`, `propertyIssues`); a binding only one of
|
|
730
|
+
* `bindings`, directly under the node they are given for, and `x-source`
|
|
731
|
+
* only on a scalar.
|
|
565
732
|
*/
|
|
566
733
|
function heldByRows(
|
|
567
|
-
node:
|
|
568
|
-
type: SchemaProperty["type"];
|
|
569
|
-
properties?: Record<string, SchemaProperty>;
|
|
570
|
-
required?: string[];
|
|
571
|
-
},
|
|
734
|
+
node: ObjectNode,
|
|
572
735
|
context: z.RefinementCtx,
|
|
573
736
|
path: Array<string | number>,
|
|
574
|
-
|
|
737
|
+
bindings: ReadonlyArray<Binding> | undefined,
|
|
575
738
|
) {
|
|
576
739
|
const report = (under: Array<string | number>, issues: ReadonlyArray<RowIssue>) => {
|
|
577
740
|
for (const { at, message } of issues) {
|
|
@@ -581,27 +744,29 @@ function heldByRows(
|
|
|
581
744
|
report(path, nodeIssues(node));
|
|
582
745
|
for (const [name, property] of Object.entries(node.properties ?? {})) {
|
|
583
746
|
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"],
|
|
747
|
+
report(at, propertyIssues(property, bindings));
|
|
748
|
+
heldByRows(property, context, at, undefined);
|
|
749
|
+
if (property.items) heldByRows(property.items, context, [...at, "items"], undefined);
|
|
587
750
|
}
|
|
588
751
|
}
|
|
589
752
|
|
|
590
753
|
/**
|
|
591
754
|
* An object's JSON Schema, the shape of a tool's or trigger's input and
|
|
592
|
-
* output
|
|
593
|
-
*
|
|
755
|
+
* output, and of what the console's field rows write. `bindings`, given for
|
|
756
|
+
* a tool's input, are the extensions its top-level fields may carry:
|
|
757
|
+
* `x-source` on a scalar, `x-target` on any field.
|
|
594
758
|
*/
|
|
595
|
-
function
|
|
759
|
+
export function objectJsonSchemaOf(bindings?: ReadonlyArray<Binding>) {
|
|
596
760
|
return z
|
|
597
761
|
.strictObject({
|
|
598
762
|
type: z.literal("object"),
|
|
599
763
|
properties: z.record(propertyNameSchema, schemaPropertySchema).optional(),
|
|
600
764
|
required: z.array(z.string()).optional(),
|
|
765
|
+
"x-order": propertyOrderSchema,
|
|
601
766
|
})
|
|
602
|
-
.superRefine((schema, context) => heldByRows(schema, context, [],
|
|
767
|
+
.superRefine((schema, context) => heldByRows(schema, context, [], bindings));
|
|
603
768
|
}
|
|
604
|
-
const objectSchema =
|
|
769
|
+
const objectSchema = objectJsonSchemaOf();
|
|
605
770
|
|
|
606
771
|
const EMPTY_OBJECT = { type: "object" } as const satisfies z.input<typeof objectSchema>;
|
|
607
772
|
|
|
@@ -617,23 +782,44 @@ const audienceSchema = z.union([
|
|
|
617
782
|
}),
|
|
618
783
|
]);
|
|
619
784
|
|
|
785
|
+
/** 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. */
|
|
786
|
+
export const MODEL_PROVIDER_LIMITS = { urlLength: 2_048, modelLength: 200, models: 100 } as const;
|
|
787
|
+
|
|
620
788
|
/** A model a provider lists, and the reasoning effort sent with it. */
|
|
621
789
|
const modelSchema = z.strictObject({
|
|
622
790
|
provider: nameSchema,
|
|
623
|
-
model: z.string().trim().min(1).max(
|
|
791
|
+
model: z.string().trim().min(1).max(MODEL_PROVIDER_LIMITS.modelLength),
|
|
624
792
|
effort: z.string().trim().min(1).max(64).optional(),
|
|
625
793
|
});
|
|
626
794
|
|
|
795
|
+
/** A service's name and description, in characters per locale. The console caps its editor at the same lengths. */
|
|
796
|
+
export const SERVICE_LIMITS = { nameLength: 120, descriptionLength: 4_000 } as const;
|
|
797
|
+
|
|
627
798
|
/** One line per locale, trimmed. */
|
|
628
|
-
const localizedLineSchema = z.record(
|
|
629
|
-
|
|
799
|
+
const localizedLineSchema = z.record(
|
|
800
|
+
localeSchema,
|
|
801
|
+
z.string().trim().min(1).max(SERVICE_LIMITS.nameLength),
|
|
802
|
+
);
|
|
803
|
+
const localizedTextSchema = z.record(
|
|
804
|
+
localeSchema,
|
|
805
|
+
z.string().trim().min(1).max(SERVICE_LIMITS.descriptionLength),
|
|
806
|
+
);
|
|
630
807
|
|
|
631
808
|
const { tool: toolBounds } = SETTING_BOUNDS;
|
|
632
809
|
|
|
810
|
+
/** A `type` tool's input schema, its fields bound only as `TOOL_TYPES` lets the type: filled from a source, placed in the request. */
|
|
811
|
+
function toolInputSchemaOf(type: (typeof TOOL_TYPE_NAMES)[number]) {
|
|
812
|
+
const { sources, targets } = TOOL_TYPES[type];
|
|
813
|
+
return objectJsonSchemaOf([
|
|
814
|
+
...(sources ? ["x-source" as const] : []),
|
|
815
|
+
...(targets ? ["x-target" as const] : []),
|
|
816
|
+
]).default(EMPTY_OBJECT);
|
|
817
|
+
}
|
|
818
|
+
|
|
633
819
|
/** Shared by every tool type; a tool that calls nothing (`client`) has no endpoint, credentials, approval or bound fields. */
|
|
634
820
|
const toolBaseSchema = z.strictObject({
|
|
635
821
|
...serverToolSchema.pick({ name: true, description: true }).shape,
|
|
636
|
-
inputSchema:
|
|
822
|
+
inputSchema: toolInputSchemaOf("client"),
|
|
637
823
|
outputSchema: objectSchema.default(EMPTY_OBJECT),
|
|
638
824
|
timeoutMs: setting(toolBounds.timeoutMs),
|
|
639
825
|
maxResponseBytes: setting(toolBounds.maxResponseBytes),
|
|
@@ -658,7 +844,6 @@ const toolBaseSchema = z.strictObject({
|
|
|
658
844
|
|
|
659
845
|
/** A tool that calls an endpoint; with `requiresApproval`, every call waits for the end user to approve it. */
|
|
660
846
|
const calledToolSchema = toolBaseSchema.extend({
|
|
661
|
-
inputSchema: objectSchemaOf(true).default(EMPTY_OBJECT),
|
|
662
847
|
url: upstreamUrlSchema.refine(
|
|
663
848
|
confinesEndpointParams,
|
|
664
849
|
"a {parameter} outside the path or query",
|
|
@@ -671,28 +856,33 @@ const manifestToolSchema = z
|
|
|
671
856
|
.discriminatedUnion("type", [
|
|
672
857
|
calledToolSchema.extend({
|
|
673
858
|
type: z.literal("http"),
|
|
859
|
+
inputSchema: toolInputSchemaOf("http"),
|
|
674
860
|
method: z.enum(TOOL_METHODS),
|
|
675
861
|
readOnly: z.boolean(),
|
|
676
862
|
...toolConfigByType.http.shape,
|
|
677
863
|
}),
|
|
678
|
-
/**
|
|
679
|
-
|
|
864
|
+
/**
|
|
865
|
+
* The endpoint is the initiate call that opens the page; never read-only, so a reload rejoins it.
|
|
866
|
+
* No `requiresApproval`: the widget answers the call, so nothing would ask for it. No field
|
|
867
|
+
* names its place in the request (`x-target`).
|
|
868
|
+
*/
|
|
869
|
+
calledToolSchema.omit({ requiresApproval: true }).extend({
|
|
680
870
|
type: z.literal("embedded"),
|
|
871
|
+
inputSchema: toolInputSchemaOf("embedded"),
|
|
681
872
|
method: z.enum(TOOL_METHODS),
|
|
682
873
|
...toolConfigByType.embedded.shape,
|
|
683
874
|
}),
|
|
684
875
|
/** Always POSTed. */
|
|
685
876
|
calledToolSchema.extend({
|
|
686
877
|
type: z.literal("soap"),
|
|
878
|
+
inputSchema: toolInputSchemaOf("soap"),
|
|
687
879
|
readOnly: z.boolean(),
|
|
688
880
|
...toolConfigByType.soap.shape,
|
|
689
881
|
}),
|
|
690
882
|
/** Answered by your app: the model calls it and the widget hands the call over. */
|
|
691
883
|
toolBaseSchema.extend({ type: z.literal("client") }),
|
|
692
884
|
])
|
|
693
|
-
.superRefine((tool, context) =>
|
|
694
|
-
if (tool.type !== "client") refuseBlockedApprovalTitle(tool, context, []);
|
|
695
|
-
});
|
|
885
|
+
.superRefine((tool, context) => refuseBlockedApprovalTitle(tool, context, []));
|
|
696
886
|
export type ManifestTool = z.input<typeof manifestToolSchema>;
|
|
697
887
|
|
|
698
888
|
const toolNamesSchema = z.array(serverToolSchema.shape.name).max(500).default([]);
|
|
@@ -738,9 +928,12 @@ const manifestServiceSchema = z.strictObject({
|
|
|
738
928
|
});
|
|
739
929
|
export type ManifestService = z.input<typeof manifestServiceSchema>;
|
|
740
930
|
|
|
931
|
+
/** A skill's description in characters. The console caps its editor at the same. */
|
|
932
|
+
export const SKILL_LIMITS = { descriptionLength: 500 } as const;
|
|
933
|
+
|
|
741
934
|
const manifestSkillSchema = z.strictObject({
|
|
742
935
|
name: slugSchema,
|
|
743
|
-
description: z.string().trim().min(1).max(
|
|
936
|
+
description: z.string().trim().min(1).max(SKILL_LIMITS.descriptionLength),
|
|
744
937
|
/** Rich text: it may mention tools, skills and datasets, and read `{{variables}}`. */
|
|
745
938
|
body: z.string().trim().min(1),
|
|
746
939
|
/** The key of the service the skill renders inside; fixed once created. */
|
|
@@ -767,11 +960,14 @@ export const triggerThreadPolicySchema = z.discriminatedUnion("policy", [
|
|
|
767
960
|
]);
|
|
768
961
|
export type TriggerThreadPolicy = z.infer<typeof triggerThreadPolicySchema>;
|
|
769
962
|
|
|
963
|
+
/** A trigger's description and instructions in characters. The console caps its editor at the same. */
|
|
964
|
+
export const TRIGGER_LIMITS = { descriptionLength: 500, instructionsLength: 20_000 } as const;
|
|
965
|
+
|
|
770
966
|
/** Fired with its secret, which the apply that creates it answers once and no manifest holds. */
|
|
771
967
|
const manifestTriggerSchema = z.strictObject({
|
|
772
968
|
/** `message` is the built-in trigger every agent has. */
|
|
773
969
|
name: slugSchema.refine((name) => name !== MESSAGE_TRIGGER_NAME, "the built-in trigger's name"),
|
|
774
|
-
description: z.string().trim().min(1).max(
|
|
970
|
+
description: z.string().trim().min(1).max(TRIGGER_LIMITS.descriptionLength),
|
|
775
971
|
inputSchema: objectSchema.default(EMPTY_OBJECT),
|
|
776
972
|
/** The shape the run answers in; absent, the run answers in free text. */
|
|
777
973
|
outputSchema: objectSchema
|
|
@@ -781,7 +977,7 @@ const manifestTriggerSchema = z.strictObject({
|
|
|
781
977
|
)
|
|
782
978
|
.optional(),
|
|
783
979
|
/** Rich text: it may mention tools, skills and datasets, and read `{{variables}}` and `{{/payload/pointer}}`s. */
|
|
784
|
-
instructions: z.string().trim().min(1).max(
|
|
980
|
+
instructions: z.string().trim().min(1).max(TRIGGER_LIMITS.instructionsLength),
|
|
785
981
|
threadPolicy: triggerThreadPolicySchema.default({ policy: "single" }),
|
|
786
982
|
/** Firings a day per agent; absent, no limit. */
|
|
787
983
|
budgetPerDay: z.number().int().positive().optional(),
|
|
@@ -853,9 +1049,9 @@ export const mcpToolNamesSchema = z
|
|
|
853
1049
|
|
|
854
1050
|
const manifestMcpServerSchema = z.strictObject({
|
|
855
1051
|
/** Prefixes every tool the server contributes: scripts call `tools.<name>_<tool>()`. */
|
|
856
|
-
name:
|
|
1052
|
+
name: identifierSchema,
|
|
857
1053
|
url: upstreamUrlSchema,
|
|
858
|
-
credentials:
|
|
1054
|
+
credentials: declaredPresentationsSchema(
|
|
859
1055
|
presentedHeaderSchema.refine(
|
|
860
1056
|
(name) => !mcpTransportHeaders.has(name.toLowerCase()),
|
|
861
1057
|
"a header the MCP transport sets",
|
|
@@ -880,7 +1076,7 @@ const manifestWebhookSchema = z.strictObject({
|
|
|
880
1076
|
description: z.string().trim().max(500).default(""),
|
|
881
1077
|
event: z.enum(SERVER_EVENTS),
|
|
882
1078
|
url: upstreamUrlSchema,
|
|
883
|
-
credentials:
|
|
1079
|
+
credentials: declaredPresentationsSchema(
|
|
884
1080
|
presentedHeaderSchema.refine(
|
|
885
1081
|
(name) => name.toLowerCase() !== EVENT_HEADER.toLowerCase(),
|
|
886
1082
|
"the header that names the event",
|
|
@@ -893,10 +1089,13 @@ const manifestWebhookSchema = z.strictObject({
|
|
|
893
1089
|
});
|
|
894
1090
|
export type ManifestWebhook = z.input<typeof manifestWebhookSchema>;
|
|
895
1091
|
|
|
1092
|
+
/** A collection's name and description in characters. The console caps its editor at the same. */
|
|
1093
|
+
export const COLLECTION_LIMITS = { nameLength: 80, descriptionLength: 2_000 } as const;
|
|
1094
|
+
|
|
896
1095
|
/** Holds documents, which are uploaded to it apart from the manifest. */
|
|
897
1096
|
const manifestCollectionSchema = z.strictObject({
|
|
898
|
-
name: z.string().trim().min(1).max(
|
|
899
|
-
description: z.string().trim().max(
|
|
1097
|
+
name: z.string().trim().min(1).max(COLLECTION_LIMITS.nameLength),
|
|
1098
|
+
description: z.string().trim().max(COLLECTION_LIMITS.descriptionLength).default(""),
|
|
900
1099
|
/** The key of the service every document in the collection belongs to. */
|
|
901
1100
|
service: slugSchema.optional(),
|
|
902
1101
|
/** Whether answers may name its documents; false keeps their names, sections and pages from the model. */
|
|
@@ -916,9 +1115,9 @@ const tokenAudienceSchema = z.string().trim().min(1).max(2048).optional();
|
|
|
916
1115
|
* How the agent verifies end-user tokens: against the keys its issuer
|
|
917
1116
|
* publishes at a JWKS URI, with a shared secret named in Cortex's secret
|
|
918
1117
|
* store and the one algorithm tokens are signed with, through its server's
|
|
919
|
-
* `/verify`, or not at all.
|
|
1118
|
+
* `/verify`, or not at all. Cortex stores it on the agent as written here.
|
|
920
1119
|
*/
|
|
921
|
-
const agentAuthSchema = z.discriminatedUnion("kind", [
|
|
1120
|
+
export const agentAuthSchema = z.discriminatedUnion("kind", [
|
|
922
1121
|
z.strictObject({
|
|
923
1122
|
kind: z.literal("jwks"),
|
|
924
1123
|
jwksUri: tlsUrlSchema,
|
|
@@ -938,8 +1137,11 @@ const agentAuthSchema = z.discriminatedUnion("kind", [
|
|
|
938
1137
|
|
|
939
1138
|
const { agent: agentBounds, agentContext, agentLimits } = SETTING_BOUNDS;
|
|
940
1139
|
|
|
941
|
-
/**
|
|
942
|
-
|
|
1140
|
+
/**
|
|
1141
|
+
* How the agent runs; every setting left out keeps its default. The
|
|
1142
|
+
* console's settings input requires each of them.
|
|
1143
|
+
*/
|
|
1144
|
+
export const agentSettingsSchema = z.strictObject({
|
|
943
1145
|
/** Lets `searchRegistries` search the knowledge in the collections the agent sees. */
|
|
944
1146
|
ragEnabled: z.boolean().default(false),
|
|
945
1147
|
/** Lets `searchRegistries` search the tools the agent sees, beyond the pinned and mentioned ones. */
|
|
@@ -957,7 +1159,7 @@ const agentSettingsSchema = z.strictObject({
|
|
|
957
1159
|
media: z.array(z.enum(MEDIA_FORMS)).default([...DEFAULT_MEDIA_FORMS]),
|
|
958
1160
|
/** Off, end users cannot message the agent: only its triggers reach it. */
|
|
959
1161
|
messageTrigger: z.boolean().default(true),
|
|
960
|
-
defaultLocale: localeSchema.default(
|
|
1162
|
+
defaultLocale: localeSchema.default(BASE_LOCALE),
|
|
961
1163
|
rateLimitRequests: setting(agentBounds.rateLimitRequests),
|
|
962
1164
|
rateLimitWindowSeconds: setting(agentBounds.rateLimitWindowSeconds),
|
|
963
1165
|
dailyExecutionQuota: setting(agentBounds.dailyExecutionQuota),
|
|
@@ -982,9 +1184,12 @@ const agentSettingsSchema = z.strictObject({
|
|
|
982
1184
|
.prefault({}),
|
|
983
1185
|
});
|
|
984
1186
|
|
|
1187
|
+
/** An agent's name in characters. The console caps its editor at the same. */
|
|
1188
|
+
export const AGENT_LIMITS = { nameLength: 120 } as const;
|
|
1189
|
+
|
|
985
1190
|
const manifestAgentSchema = z.strictObject({
|
|
986
1191
|
slug: agentSlugSchema,
|
|
987
|
-
name: z.string().trim().min(1).max(
|
|
1192
|
+
name: z.string().trim().min(1).max(AGENT_LIMITS.nameLength),
|
|
988
1193
|
/** The server it belongs to, by name; a manifest never creates one. */
|
|
989
1194
|
server: nameSchema,
|
|
990
1195
|
/** Rich text: it may mention tools and skills, and read `{{variables}}`. */
|
|
@@ -1002,23 +1207,61 @@ const manifestAgentSchema = z.strictObject({
|
|
|
1002
1207
|
})
|
|
1003
1208
|
.default({}),
|
|
1004
1209
|
settings: agentSettingsSchema.prefault({}),
|
|
1005
|
-
suggestions: suggestionsSchema.default(
|
|
1006
|
-
greeting: greetingSchema.default(
|
|
1210
|
+
suggestions: suggestionsSchema.default(() => byLocale(() => [])),
|
|
1211
|
+
greeting: greetingSchema.default(() => byLocale(() => "")),
|
|
1007
1212
|
overridable: overridable(["name", "prompt", "models", "settings", "suggestions", "greeting"]),
|
|
1008
1213
|
});
|
|
1009
1214
|
export type ManifestAgent = z.input<typeof manifestAgentSchema>;
|
|
1010
1215
|
|
|
1011
|
-
/**
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1216
|
+
/**
|
|
1217
|
+
* Every kind of entry a manifest owns a row for, in the order an apply
|
|
1218
|
+
* reports a name another row holds and a kind its actor may not write. An
|
|
1219
|
+
* OpenAPI source is none: its operations become `tools` entries.
|
|
1220
|
+
*/
|
|
1221
|
+
export const MANIFEST_KINDS = [
|
|
1222
|
+
"agents",
|
|
1223
|
+
"services",
|
|
1224
|
+
"tools",
|
|
1225
|
+
"skills",
|
|
1226
|
+
"triggers",
|
|
1227
|
+
"datasetHosts",
|
|
1228
|
+
"mcpServers",
|
|
1229
|
+
"webhooks",
|
|
1230
|
+
"collections",
|
|
1231
|
+
] as const;
|
|
1232
|
+
export type ManifestKind = (typeof MANIFEST_KINDS)[number];
|
|
1015
1233
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1234
|
+
/**
|
|
1235
|
+
* The field each kind's entries go by, which its row's name holds: an
|
|
1236
|
+
* agent's slug, a service's key, any other entry's name. A list holds each
|
|
1237
|
+
* once, so one entry means one row.
|
|
1238
|
+
*/
|
|
1239
|
+
export const MANIFEST_NAME_FIELDS = {
|
|
1240
|
+
agents: "slug",
|
|
1241
|
+
tools: "name",
|
|
1242
|
+
services: "key",
|
|
1243
|
+
skills: "name",
|
|
1244
|
+
triggers: "name",
|
|
1245
|
+
datasetHosts: "name",
|
|
1246
|
+
mcpServers: "name",
|
|
1247
|
+
webhooks: "name",
|
|
1248
|
+
collections: "name",
|
|
1249
|
+
} as const satisfies Record<ManifestKind, string>;
|
|
1250
|
+
|
|
1251
|
+
/** A list of entries named by their `field`, refusing a name they repeat. */
|
|
1252
|
+
function listOf<TField extends string, TEntry extends z.ZodType<Record<TField, string>>>(
|
|
1253
|
+
field: TField,
|
|
1254
|
+
entry: TEntry,
|
|
1255
|
+
) {
|
|
1256
|
+
return z
|
|
1257
|
+
.array(entry)
|
|
1258
|
+
.refine(
|
|
1259
|
+
(entries) => new Set(entries.map((value) => value[field])).size === entries.length,
|
|
1260
|
+
"a name repeats",
|
|
1261
|
+
)
|
|
1262
|
+
.default([]);
|
|
1018
1263
|
}
|
|
1019
1264
|
|
|
1020
|
-
const byName = (entry: { name: string }) => entry.name;
|
|
1021
|
-
|
|
1022
1265
|
/** The id of Cortex's own manifest, which only Cortex applies: the management API refuses any other under it. */
|
|
1023
1266
|
export const CORTEX_MANIFEST_ID = "cortex";
|
|
1024
1267
|
|
|
@@ -1032,24 +1275,26 @@ export const CORTEX_MANIFEST_ID = "cortex";
|
|
|
1032
1275
|
export const manifestSchema = z.strictObject({
|
|
1033
1276
|
/** Fixed for the deployment it describes: the rows an apply owns carry it. */
|
|
1034
1277
|
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
|
-
});
|
|
1278
|
+
agents: listOf(MANIFEST_NAME_FIELDS.agents, manifestAgentSchema),
|
|
1279
|
+
tools: listOf(MANIFEST_NAME_FIELDS.tools, manifestToolSchema),
|
|
1280
|
+
openapi: listOf("name", manifestOpenApiSchema),
|
|
1281
|
+
services: listOf(MANIFEST_NAME_FIELDS.services, manifestServiceSchema),
|
|
1282
|
+
skills: listOf(MANIFEST_NAME_FIELDS.skills, manifestSkillSchema),
|
|
1283
|
+
triggers: listOf(MANIFEST_NAME_FIELDS.triggers, manifestTriggerSchema),
|
|
1284
|
+
datasetHosts: listOf(MANIFEST_NAME_FIELDS.datasetHosts, manifestDatasetHostSchema),
|
|
1285
|
+
mcpServers: listOf(MANIFEST_NAME_FIELDS.mcpServers, manifestMcpServerSchema),
|
|
1286
|
+
webhooks: listOf(MANIFEST_NAME_FIELDS.webhooks, manifestWebhookSchema),
|
|
1287
|
+
collections: listOf(MANIFEST_NAME_FIELDS.collections, manifestCollectionSchema),
|
|
1288
|
+
} satisfies Record<ManifestKind | "id" | "openapi", z.ZodType>);
|
|
1046
1289
|
/** A manifest as written: every field with a default may be left out. */
|
|
1047
1290
|
export type Manifest = z.input<typeof manifestSchema>;
|
|
1048
1291
|
|
|
1049
|
-
|
|
1050
|
-
const manifestKindSchema = manifestSchema.keyof().exclude(["id", "openapi"]);
|
|
1292
|
+
const manifestKindSchema = z.enum(MANIFEST_KINDS);
|
|
1051
1293
|
|
|
1052
|
-
/**
|
|
1294
|
+
/**
|
|
1295
|
+
* Entry names per kind, each by its `MANIFEST_NAME_FIELDS` field. An OpenAPI
|
|
1296
|
+
* source's operations are counted among `tools`.
|
|
1297
|
+
*/
|
|
1053
1298
|
const namesByKindSchema = z.record(manifestKindSchema, z.array(z.string()));
|
|
1054
1299
|
|
|
1055
1300
|
/** What an apply creates, updates and deletes, kind by kind; a preview answers the same without doing it. */
|