@m6d/cortex-cli 0.20.0 → 0.22.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 +3 -3
- package/package.json +1 -1
- package/src/commands/new.ts +4 -4
- package/src/commands/setup.ts +19 -10
- package/src/contracts/client-tools/index.ts +21 -0
- package/src/contracts/egress-policy/index.ts +1 -0
- package/src/contracts/egress-policy/url.ts +1 -1
- package/src/contracts/management/procedures.generated.ts +452 -171
- package/src/contracts/manifest/index.ts +121 -106
- package/src/contracts/server/index.ts +35 -10
- package/src/contracts/triggers/index.ts +9 -9
- package/src/contracts/wire/index.ts +54 -0
- package/src/scaffold/files.ts +11 -13
|
@@ -2,10 +2,11 @@ import { z } from "zod";
|
|
|
2
2
|
import { datasetIdentifierSchema } from "../datasets/index";
|
|
3
3
|
import { canCarrySecret } from "../egress-policy/index";
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
displayNameSchema,
|
|
6
6
|
EVENT_HEADER,
|
|
7
7
|
identifierSchema,
|
|
8
8
|
localeSchema,
|
|
9
|
+
nameSchema,
|
|
9
10
|
SERVER_EVENTS,
|
|
10
11
|
refuseBlockedApprovalTitle,
|
|
11
12
|
serverToolSchema,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
* A manifest: the agents and components a code-first deployment declares,
|
|
26
27
|
* written by hand (`cortex.manifest.ts` or JSON) and applied in one
|
|
27
28
|
* transaction. Everything it points at, it names: a server, a model
|
|
28
|
-
* provider, a service
|
|
29
|
+
* provider, a service, an agent, and a stored secret
|
|
29
30
|
* or credential by the noun of what it names (`secret: "signing-key"`,
|
|
30
31
|
* `credential: "petstore"`). A tool input field's `x-source` is the one
|
|
31
32
|
* exception: `inputSchema` is the JSON Schema the tool stores, verbatim, so
|
|
@@ -187,11 +188,6 @@ function overridable<const TField extends string>(fields: readonly [TField, ...T
|
|
|
187
188
|
.default([]);
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
/** Consumer-facing slugs: the manifest id, service keys, skill names and trigger names share it. */
|
|
191
|
-
export const SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
192
|
-
export const SLUG_MAX_LENGTH = 64;
|
|
193
|
-
const slugSchema = z.string().regex(SLUG_PATTERN).max(SLUG_MAX_LENGTH);
|
|
194
|
-
|
|
195
191
|
/** A DNS host name in lower case, the way `URL` reports one: dot-separated labels of letters, digits and inner hyphens. */
|
|
196
192
|
export const HOSTNAME_PATTERN =
|
|
197
193
|
/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/;
|
|
@@ -477,11 +473,6 @@ function repeatedHeaders(entries: ReadonlyArray<{ header: string }>) {
|
|
|
477
473
|
});
|
|
478
474
|
}
|
|
479
475
|
|
|
480
|
-
/** The longest name Cortex holds a server, model provider, credential or secret under. */
|
|
481
|
-
export const NAME_MAX_LENGTH = 120;
|
|
482
|
-
/** A server, model provider, credential or secret, by the name Cortex holds it under. */
|
|
483
|
-
const nameSchema = z.string().trim().min(1).max(NAME_MAX_LENGTH);
|
|
484
|
-
|
|
485
476
|
const presentedHeaderSchema = headerNameSchema.refine(
|
|
486
477
|
(name) => !transportOwnedHeaders.has(name.toLowerCase()),
|
|
487
478
|
"a header the transport sets",
|
|
@@ -518,7 +509,10 @@ const variableSourceSchema = z.object({
|
|
|
518
509
|
});
|
|
519
510
|
|
|
520
511
|
/** A credential Cortex holds or acquires, by name. */
|
|
521
|
-
const credentialSourceSchema = z.object({
|
|
512
|
+
const credentialSourceSchema = z.object({
|
|
513
|
+
kind: z.literal("credential"),
|
|
514
|
+
name: nameSchema,
|
|
515
|
+
});
|
|
522
516
|
|
|
523
517
|
/** A value written as is. */
|
|
524
518
|
const constantHeaderSourceSchema = z.object({
|
|
@@ -883,15 +877,15 @@ const objectSchema = objectJsonSchemaOf();
|
|
|
883
877
|
|
|
884
878
|
const EMPTY_OBJECT = { type: "object" } as const satisfies z.input<typeof objectSchema>;
|
|
885
879
|
|
|
886
|
-
/** Who a component reaches: every agent, every agent on one server, or the agents listed by
|
|
880
|
+
/** Who a component reaches: every agent, every agent on one server, or the agents listed by name. */
|
|
887
881
|
const audienceSchema = z.union([
|
|
888
882
|
z.literal("all"),
|
|
889
883
|
z.strictObject({ server: nameSchema }),
|
|
890
884
|
z.strictObject({
|
|
891
885
|
agents: z
|
|
892
|
-
.array(
|
|
886
|
+
.array(nameSchema)
|
|
893
887
|
.min(1)
|
|
894
|
-
.refine((
|
|
888
|
+
.refine((names) => new Set(names).size === names.length, "an agent repeats"),
|
|
895
889
|
}),
|
|
896
890
|
]);
|
|
897
891
|
|
|
@@ -905,14 +899,9 @@ const modelSchema = z.strictObject({
|
|
|
905
899
|
effort: z.string().trim().min(1).max(64).optional(),
|
|
906
900
|
});
|
|
907
901
|
|
|
908
|
-
/** A service's
|
|
909
|
-
export const SERVICE_LIMITS = {
|
|
902
|
+
/** A service's description, in characters per locale. The console caps its editor at the same length. */
|
|
903
|
+
export const SERVICE_LIMITS = { descriptionLength: 4_000 } as const;
|
|
910
904
|
|
|
911
|
-
/** One line per locale, trimmed. */
|
|
912
|
-
const localizedLineSchema = z.record(
|
|
913
|
-
localeSchema,
|
|
914
|
-
z.string().trim().min(1).max(SERVICE_LIMITS.nameLength),
|
|
915
|
-
);
|
|
916
905
|
const localizedTextSchema = z.record(
|
|
917
906
|
localeSchema,
|
|
918
907
|
z.string().trim().min(1).max(SERVICE_LIMITS.descriptionLength),
|
|
@@ -932,6 +921,7 @@ function toolInputSchemaOf(type: (typeof TOOL_TYPE_NAMES)[number]) {
|
|
|
932
921
|
|
|
933
922
|
/** What a live write may change on a manifest's tool, and keep through the next apply. */
|
|
934
923
|
const TOOL_OVERRIDABLES = [
|
|
924
|
+
"displayName",
|
|
935
925
|
"description",
|
|
936
926
|
"timeoutMs",
|
|
937
927
|
"maxResponseBytes",
|
|
@@ -946,6 +936,7 @@ const CALLED_TOOL_OVERRIDABLES = [...TOOL_OVERRIDABLES, "headers"] as const;
|
|
|
946
936
|
/** Shared by every tool type; a tool that calls nothing (`client`) has no endpoint, headers, approval or bound fields. */
|
|
947
937
|
const toolBaseSchema = z.strictObject({
|
|
948
938
|
...serverToolSchema.pick({ name: true, description: true }).shape,
|
|
939
|
+
displayName: displayNameSchema.default({}),
|
|
949
940
|
inputSchema: toolInputSchemaOf("client"),
|
|
950
941
|
outputSchema: objectSchema.default(EMPTY_OBJECT),
|
|
951
942
|
timeoutMs: setting(toolBounds.timeoutMs),
|
|
@@ -1050,15 +1041,15 @@ const manifestOpenApiSchema = z.strictObject({
|
|
|
1050
1041
|
});
|
|
1051
1042
|
export type ManifestOpenApi = z.input<typeof manifestOpenApiSchema>;
|
|
1052
1043
|
|
|
1053
|
-
/**
|
|
1044
|
+
/** Skills and collections scope themselves by a service's `name`. */
|
|
1054
1045
|
const manifestServiceSchema = z.strictObject({
|
|
1055
|
-
/** What consumers parameterize their tools with
|
|
1056
|
-
|
|
1057
|
-
|
|
1046
|
+
/** What consumers parameterize their tools with. */
|
|
1047
|
+
name: nameSchema,
|
|
1048
|
+
displayName: displayNameSchema.default({}),
|
|
1058
1049
|
/** Rich text: it may mention tools and skills. */
|
|
1059
1050
|
description: localizedTextSchema,
|
|
1060
1051
|
audience: audienceSchema,
|
|
1061
|
-
overridable: overridable(["description"]),
|
|
1052
|
+
overridable: overridable(["displayName", "description"]),
|
|
1062
1053
|
});
|
|
1063
1054
|
export type ManifestService = z.input<typeof manifestServiceSchema>;
|
|
1064
1055
|
|
|
@@ -1066,14 +1057,15 @@ export type ManifestService = z.input<typeof manifestServiceSchema>;
|
|
|
1066
1057
|
export const SKILL_LIMITS = { descriptionLength: 500 } as const;
|
|
1067
1058
|
|
|
1068
1059
|
const manifestSkillSchema = z.strictObject({
|
|
1069
|
-
name:
|
|
1060
|
+
name: nameSchema,
|
|
1061
|
+
displayName: displayNameSchema.default({}),
|
|
1070
1062
|
description: z.string().trim().min(1).max(SKILL_LIMITS.descriptionLength),
|
|
1071
1063
|
/** Rich text: it may mention tools, skills and datasets, and read `{{variables}}`. */
|
|
1072
1064
|
body: z.string().trim().min(1),
|
|
1073
|
-
/** The
|
|
1074
|
-
service:
|
|
1065
|
+
/** The name of the service the skill renders inside; fixed once created. */
|
|
1066
|
+
service: nameSchema.optional(),
|
|
1075
1067
|
audience: audienceSchema,
|
|
1076
|
-
overridable: overridable(["description", "body"]),
|
|
1068
|
+
overridable: overridable(["displayName", "description", "body"]),
|
|
1077
1069
|
});
|
|
1078
1070
|
export type ManifestSkill = z.input<typeof manifestSkillSchema>;
|
|
1079
1071
|
|
|
@@ -1100,7 +1092,8 @@ export const TRIGGER_LIMITS = { descriptionLength: 500, instructionsLength: 20_0
|
|
|
1100
1092
|
/** Fired with its secret, which the apply that creates it answers once and no manifest holds. */
|
|
1101
1093
|
const manifestTriggerSchema = z.strictObject({
|
|
1102
1094
|
/** `message` is the built-in trigger every agent has. */
|
|
1103
|
-
name:
|
|
1095
|
+
name: nameSchema.refine((name) => name !== MESSAGE_TRIGGER_NAME, "the built-in trigger's name"),
|
|
1096
|
+
displayName: displayNameSchema.default({}),
|
|
1104
1097
|
description: z.string().trim().min(1).max(TRIGGER_LIMITS.descriptionLength),
|
|
1105
1098
|
inputSchema: objectSchema.default(EMPTY_OBJECT),
|
|
1106
1099
|
/** The shape the run answers in; absent, the run answers in free text. */
|
|
@@ -1125,6 +1118,7 @@ const manifestTriggerSchema = z.strictObject({
|
|
|
1125
1118
|
model: modelSchema.optional(),
|
|
1126
1119
|
audience: audienceSchema,
|
|
1127
1120
|
overridable: overridable([
|
|
1121
|
+
"displayName",
|
|
1128
1122
|
"description",
|
|
1129
1123
|
"instructions",
|
|
1130
1124
|
"threadPolicy",
|
|
@@ -1147,6 +1141,7 @@ export function unexposedPins(host: {
|
|
|
1147
1141
|
const manifestDatasetHostSchema = z
|
|
1148
1142
|
.strictObject({
|
|
1149
1143
|
name: datasetIdentifierSchema,
|
|
1144
|
+
displayName: displayNameSchema.default({}),
|
|
1150
1145
|
url: upstreamUrlSchema,
|
|
1151
1146
|
headers: headersSchema,
|
|
1152
1147
|
timeoutMs: setting(SETTING_BOUNDS.datasetHost.timeoutMs),
|
|
@@ -1175,7 +1170,7 @@ const manifestDatasetHostSchema = z
|
|
|
1175
1170
|
/** Among those, the ones listed in every turn, whatever the question. */
|
|
1176
1171
|
pinned: z.array(datasetIdentifierSchema).max(50).default([]),
|
|
1177
1172
|
audience: audienceSchema,
|
|
1178
|
-
overridable: overridable(["timeoutMs", "rateLimits", "headers"]),
|
|
1173
|
+
overridable: overridable(["displayName", "timeoutMs", "rateLimits", "headers"]),
|
|
1179
1174
|
})
|
|
1180
1175
|
.superRefine((host, context) => {
|
|
1181
1176
|
const unexposed = unexposedPins(host);
|
|
@@ -1201,23 +1196,68 @@ export const mcpToolNamesSchema = z
|
|
|
1201
1196
|
)
|
|
1202
1197
|
.max(200);
|
|
1203
1198
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
audience: audienceSchema,
|
|
1219
|
-
overridable: overridable(["include", "exclude", "headers"]),
|
|
1199
|
+
/** Who holds an MCP server's sign-in: one connection an operator makes for every agent, or one per end user of each agent. */
|
|
1200
|
+
export const MCP_CONNECTION_OWNERS = ["shared", "endUser"] as const;
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* How Cortex signs in to an MCP server that takes OAuth: who connects, the
|
|
1204
|
+
* scope it asks for (absent, what the server advertises), and, for an
|
|
1205
|
+
* authorization server that takes neither a client ID document nor dynamic
|
|
1206
|
+
* registration, the credential holding the app registered there beforehand:
|
|
1207
|
+
* an OAuth2 client credentials one naming that server as its issuer.
|
|
1208
|
+
*/
|
|
1209
|
+
export const mcpSignInSchema = z.strictObject({
|
|
1210
|
+
connects: z.enum(MCP_CONNECTION_OWNERS),
|
|
1211
|
+
scope: z.string().trim().min(1).max(1_000).optional(),
|
|
1212
|
+
credential: nameSchema.optional(),
|
|
1220
1213
|
});
|
|
1214
|
+
export type McpSignIn = z.infer<typeof mcpSignInSchema>;
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* The index of the Authorization header a server that signs in declares
|
|
1218
|
+
* beside its sign-in, whose token travels under that header; -1 when it
|
|
1219
|
+
* signs in without one, or declares one without signing in.
|
|
1220
|
+
*/
|
|
1221
|
+
export function authorizationBesideSignIn(server: {
|
|
1222
|
+
signIn?: McpSignIn | null;
|
|
1223
|
+
headers: ReadonlyArray<{ header: string }>;
|
|
1224
|
+
}) {
|
|
1225
|
+
return server.signIn
|
|
1226
|
+
? server.headers.findIndex((entry) => entry.header.toLowerCase() === "authorization")
|
|
1227
|
+
: -1;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
const manifestMcpServerSchema = z
|
|
1231
|
+
.strictObject({
|
|
1232
|
+
/** Prefixes every tool the server contributes: scripts call `tools.<name>_<tool>()`. */
|
|
1233
|
+
name: identifierSchema,
|
|
1234
|
+
displayName: displayNameSchema.default({}),
|
|
1235
|
+
url: upstreamUrlSchema,
|
|
1236
|
+
headers: declaredHeadersSchema(
|
|
1237
|
+
presentedHeaderSchema.refine(
|
|
1238
|
+
(name) => !mcpTransportHeaders.has(name.toLowerCase()),
|
|
1239
|
+
"a header the MCP transport sets",
|
|
1240
|
+
),
|
|
1241
|
+
headerSourceSchema,
|
|
1242
|
+
),
|
|
1243
|
+
/** Absent, every request carries only its headers. */
|
|
1244
|
+
signIn: mcpSignInSchema.optional(),
|
|
1245
|
+
/** The server's own tool names to offer; empty offers every tool. */
|
|
1246
|
+
include: mcpToolNamesSchema.default([]),
|
|
1247
|
+
exclude: mcpToolNamesSchema.default([]),
|
|
1248
|
+
audience: audienceSchema,
|
|
1249
|
+
overridable: overridable(["displayName", "include", "exclude", "headers", "signIn"]),
|
|
1250
|
+
})
|
|
1251
|
+
.superRefine((server, context) => {
|
|
1252
|
+
const index = authorizationBesideSignIn(server);
|
|
1253
|
+
if (index >= 0) {
|
|
1254
|
+
context.addIssue({
|
|
1255
|
+
code: "custom",
|
|
1256
|
+
path: ["headers", index, "header"],
|
|
1257
|
+
message: "the sign-in's token travels under Authorization",
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1221
1261
|
export type ManifestMcpServer = z.input<typeof manifestMcpServerSchema>;
|
|
1222
1262
|
|
|
1223
1263
|
/**
|
|
@@ -1226,7 +1266,8 @@ export type ManifestMcpServer = z.input<typeof manifestMcpServerSchema>;
|
|
|
1226
1266
|
* travels under the header that names the event.
|
|
1227
1267
|
*/
|
|
1228
1268
|
const manifestWebhookSchema = z.strictObject({
|
|
1229
|
-
name:
|
|
1269
|
+
name: nameSchema,
|
|
1270
|
+
displayName: displayNameSchema.default({}),
|
|
1230
1271
|
description: z.string().trim().max(500).default(""),
|
|
1231
1272
|
event: z.enum(SERVER_EVENTS),
|
|
1232
1273
|
url: upstreamUrlSchema,
|
|
@@ -1239,19 +1280,15 @@ const manifestWebhookSchema = z.strictObject({
|
|
|
1239
1280
|
),
|
|
1240
1281
|
timeoutMs: setting(SETTING_BOUNDS.webhook.timeoutMs),
|
|
1241
1282
|
audience: audienceSchema,
|
|
1242
|
-
overridable: overridable(["description", "timeoutMs", "headers"]),
|
|
1283
|
+
overridable: overridable(["displayName", "description", "timeoutMs", "headers"]),
|
|
1243
1284
|
});
|
|
1244
1285
|
export type ManifestWebhook = z.input<typeof manifestWebhookSchema>;
|
|
1245
1286
|
|
|
1246
1287
|
/**
|
|
1247
|
-
* A collection's
|
|
1248
|
-
*
|
|
1288
|
+
* A collection's description in characters, and how many documents a
|
|
1289
|
+
* manifest lists in one. The console caps its editor at the same.
|
|
1249
1290
|
*/
|
|
1250
|
-
export const COLLECTION_LIMITS = {
|
|
1251
|
-
nameLength: 80,
|
|
1252
|
-
descriptionLength: 2_000,
|
|
1253
|
-
documents: 100,
|
|
1254
|
-
} as const;
|
|
1291
|
+
export const COLLECTION_LIMITS = { descriptionLength: 2_000, documents: 100 } as const;
|
|
1255
1292
|
|
|
1256
1293
|
/** A knowledge document's name in characters and its size in bytes, however it arrives. */
|
|
1257
1294
|
export const DOCUMENT_LIMITS = { nameLength: 255, bytes: 10 * 1024 * 1024 } as const;
|
|
@@ -1377,15 +1414,16 @@ function collectionSchema<TDocument extends z.ZodType<Parameters<typeof document
|
|
|
1377
1414
|
document: TDocument,
|
|
1378
1415
|
) {
|
|
1379
1416
|
return z.strictObject({
|
|
1380
|
-
name:
|
|
1417
|
+
name: nameSchema,
|
|
1418
|
+
displayName: displayNameSchema.default({}),
|
|
1381
1419
|
description: z.string().trim().max(COLLECTION_LIMITS.descriptionLength).default(""),
|
|
1382
|
-
/** The
|
|
1383
|
-
service:
|
|
1420
|
+
/** The name of the service every document in the collection belongs to. */
|
|
1421
|
+
service: nameSchema.optional(),
|
|
1384
1422
|
/** Whether answers may name its documents; false keeps their names, sections and pages from the model. */
|
|
1385
1423
|
citable: z.boolean().default(true),
|
|
1386
1424
|
documents: documentsSchema(document),
|
|
1387
1425
|
audience: audienceSchema,
|
|
1388
|
-
overridable: overridable(["description", "documents"]),
|
|
1426
|
+
overridable: overridable(["displayName", "description", "documents"]),
|
|
1389
1427
|
});
|
|
1390
1428
|
}
|
|
1391
1429
|
|
|
@@ -1497,13 +1535,10 @@ export const agentSettingsSchema = z.strictObject({
|
|
|
1497
1535
|
.prefault({}),
|
|
1498
1536
|
});
|
|
1499
1537
|
|
|
1500
|
-
/** An agent's name in characters. The console caps its editor at the same. */
|
|
1501
|
-
export const AGENT_LIMITS = { nameLength: 120 } as const;
|
|
1502
|
-
|
|
1503
1538
|
const manifestAgentSchema = z
|
|
1504
1539
|
.strictObject({
|
|
1505
|
-
|
|
1506
|
-
|
|
1540
|
+
name: nameSchema,
|
|
1541
|
+
displayName: displayNameSchema.default({}),
|
|
1507
1542
|
/**
|
|
1508
1543
|
* The server it belongs to, by name; a manifest never creates one. Left
|
|
1509
1544
|
* out, the agent has no server: no native tools, no server variables,
|
|
@@ -1545,7 +1580,7 @@ const manifestAgentSchema = z
|
|
|
1545
1580
|
suggestions: suggestionsSchema.default(() => byLocale(() => [])),
|
|
1546
1581
|
greeting: greetingSchema.default(() => byLocale(() => "")),
|
|
1547
1582
|
overridable: overridable([
|
|
1548
|
-
"
|
|
1583
|
+
"displayName",
|
|
1549
1584
|
"prompt",
|
|
1550
1585
|
"models",
|
|
1551
1586
|
"settings",
|
|
@@ -1592,31 +1627,14 @@ export const MANIFEST_KINDS = [
|
|
|
1592
1627
|
export type ManifestKind = (typeof MANIFEST_KINDS)[number];
|
|
1593
1628
|
|
|
1594
1629
|
/**
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1597
|
-
* once, so one entry means one row.
|
|
1630
|
+
* A list of entries, each going by the name its row holds, refusing a name
|
|
1631
|
+
* they repeat: one entry means one row.
|
|
1598
1632
|
*/
|
|
1599
|
-
|
|
1600
|
-
agents: "slug",
|
|
1601
|
-
tools: "name",
|
|
1602
|
-
services: "key",
|
|
1603
|
-
skills: "name",
|
|
1604
|
-
triggers: "name",
|
|
1605
|
-
datasetHosts: "name",
|
|
1606
|
-
mcpServers: "name",
|
|
1607
|
-
webhooks: "name",
|
|
1608
|
-
collections: "name",
|
|
1609
|
-
} as const satisfies Record<ManifestKind, string>;
|
|
1610
|
-
|
|
1611
|
-
/** A list of entries named by their `field`, refusing a name they repeat. */
|
|
1612
|
-
function listOf<TField extends string, TEntry extends z.ZodType<Record<TField, string>>>(
|
|
1613
|
-
field: TField,
|
|
1614
|
-
entry: TEntry,
|
|
1615
|
-
) {
|
|
1633
|
+
function listOf<TEntry extends z.ZodType<{ name: string }>>(entry: TEntry) {
|
|
1616
1634
|
return z
|
|
1617
1635
|
.array(entry)
|
|
1618
1636
|
.refine(
|
|
1619
|
-
(entries) => new Set(entries.map((value) => value
|
|
1637
|
+
(entries) => new Set(entries.map((value) => value.name)).size === entries.length,
|
|
1620
1638
|
"a name repeats",
|
|
1621
1639
|
)
|
|
1622
1640
|
.default([]);
|
|
@@ -1634,17 +1652,17 @@ export const CORTEX_MANIFEST_ID = "cortex";
|
|
|
1634
1652
|
*/
|
|
1635
1653
|
export const manifestSchema = z.strictObject({
|
|
1636
1654
|
/** Fixed for the deployment it describes: the rows an apply owns carry it. */
|
|
1637
|
-
id:
|
|
1638
|
-
agents: listOf(
|
|
1639
|
-
tools: listOf(
|
|
1640
|
-
openapi: listOf(
|
|
1641
|
-
services: listOf(
|
|
1642
|
-
skills: listOf(
|
|
1643
|
-
triggers: listOf(
|
|
1644
|
-
datasetHosts: listOf(
|
|
1645
|
-
mcpServers: listOf(
|
|
1646
|
-
webhooks: listOf(
|
|
1647
|
-
collections: listOf(
|
|
1655
|
+
id: nameSchema,
|
|
1656
|
+
agents: listOf(manifestAgentSchema),
|
|
1657
|
+
tools: listOf(manifestToolSchema),
|
|
1658
|
+
openapi: listOf(manifestOpenApiSchema),
|
|
1659
|
+
services: listOf(manifestServiceSchema),
|
|
1660
|
+
skills: listOf(manifestSkillSchema),
|
|
1661
|
+
triggers: listOf(manifestTriggerSchema),
|
|
1662
|
+
datasetHosts: listOf(manifestDatasetHostSchema),
|
|
1663
|
+
mcpServers: listOf(manifestMcpServerSchema),
|
|
1664
|
+
webhooks: listOf(manifestWebhookSchema),
|
|
1665
|
+
collections: listOf(manifestCollectionSchema),
|
|
1648
1666
|
} satisfies Record<ManifestKind | "id" | "openapi", z.ZodType>);
|
|
1649
1667
|
/** A manifest as written: every field with a default may be left out. */
|
|
1650
1668
|
export type Manifest = z.input<typeof manifestSchema>;
|
|
@@ -1655,15 +1673,12 @@ export type Manifest = z.input<typeof manifestSchema>;
|
|
|
1655
1673
|
* beside it.
|
|
1656
1674
|
*/
|
|
1657
1675
|
export const resolvedManifestSchema = manifestSchema.extend({
|
|
1658
|
-
collections: listOf(
|
|
1676
|
+
collections: listOf(resolvedCollectionSchema),
|
|
1659
1677
|
});
|
|
1660
1678
|
|
|
1661
1679
|
const manifestKindSchema = z.enum(MANIFEST_KINDS);
|
|
1662
1680
|
|
|
1663
|
-
/**
|
|
1664
|
-
* Entry names per kind, each by its `MANIFEST_NAME_FIELDS` field. An OpenAPI
|
|
1665
|
-
* source's operations are counted among `tools`.
|
|
1666
|
-
*/
|
|
1681
|
+
/** Entry names per kind. An OpenAPI source's operations are counted among `tools`. */
|
|
1667
1682
|
const namesByKindSchema = z.record(manifestKindSchema, z.array(z.string()));
|
|
1668
1683
|
|
|
1669
1684
|
/** What an apply creates, updates and deletes, kind by kind; a preview answers the same without doing it. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { REQUEST_LOCATION_TOOL } from "../client-tools/index";
|
|
2
|
+
import { CONNECT_ACCOUNT_TOOL, REQUEST_LOCATION_TOOL } from "../client-tools/index";
|
|
3
3
|
import { backendErrorSchema } from "../errors/index";
|
|
4
4
|
import { pendingApprovalSchema, TRIGGER_ANSWER_TOOL_NAME } from "../triggers/index";
|
|
5
5
|
import { isRecord, readJsonPointer } from "../json/index";
|
|
@@ -18,10 +18,6 @@ import { APPROVAL_TITLE_FIELD, LOCALES } from "../wire/index";
|
|
|
18
18
|
/** The run's end-user token, which Cortex forwards verbatim to a tool's backend, a dataset host or the agent's server. */
|
|
19
19
|
export const END_USER_TOKEN_HEADER = "X-End-User-Token";
|
|
20
20
|
|
|
21
|
-
/** The agent slug Cortex's routes name an agent by. */
|
|
22
|
-
export const AGENT_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
23
|
-
export const agentSlugSchema = z.string().regex(AGENT_SLUG_PATTERN);
|
|
24
|
-
|
|
25
21
|
/** The longest end-user id Cortex stores. */
|
|
26
22
|
export const USER_ID_MAX_LENGTH = 255;
|
|
27
23
|
|
|
@@ -40,9 +36,35 @@ export const IDENTIFIER_MAX_LENGTH = 64;
|
|
|
40
36
|
|
|
41
37
|
export const identifierSchema = z.string().regex(IDENTIFIER_PATTERN).max(IDENTIFIER_MAX_LENGTH);
|
|
42
38
|
|
|
39
|
+
/**
|
|
40
|
+
* A kebab name: lowercase letters and digits in words joined by single
|
|
41
|
+
* hyphens. What the manifest id and every name Cortex holds must be, save a
|
|
42
|
+
* tool's, an MCP server's and a dataset host's, and what its routes name an
|
|
43
|
+
* agent by.
|
|
44
|
+
*/
|
|
45
|
+
export const NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
46
|
+
|
|
47
|
+
/** The longest kebab name Cortex stores. */
|
|
48
|
+
export const NAME_MAX_LENGTH = 64;
|
|
49
|
+
|
|
50
|
+
export const nameSchema = z.string().regex(NAME_PATTERN).max(NAME_MAX_LENGTH);
|
|
51
|
+
|
|
43
52
|
/** A localized value's key. */
|
|
44
53
|
export const localeSchema = z.enum(LOCALES);
|
|
45
54
|
|
|
55
|
+
/** The longest display name Cortex stores, in any locale. */
|
|
56
|
+
export const DISPLAY_NAME_MAX_LENGTH = 120;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* What a console shows an entity as, per locale; a locale without one falls
|
|
60
|
+
* back as `localized` says, and an entity with none goes by its name. Each
|
|
61
|
+
* value is trimmed, and a blank one is dropped as absent.
|
|
62
|
+
*/
|
|
63
|
+
export const displayNameSchema = z
|
|
64
|
+
.partialRecord(localeSchema, z.string().trim().max(DISPLAY_NAME_MAX_LENGTH))
|
|
65
|
+
.transform((value) => Object.fromEntries(Object.entries(value).filter(([, text]) => text)))
|
|
66
|
+
.pipe(z.partialRecord(localeSchema, z.string()));
|
|
67
|
+
|
|
46
68
|
/** How many variables and tools a server's manifest declares at most, and how long each of their texts runs. */
|
|
47
69
|
export const SERVER_MANIFEST_LIMITS = {
|
|
48
70
|
variables: 100,
|
|
@@ -117,6 +139,7 @@ export const BUILT_IN_TOOL_NAMES = [
|
|
|
117
139
|
"searchRegistries",
|
|
118
140
|
"searchThreadHistory",
|
|
119
141
|
REQUEST_LOCATION_TOOL,
|
|
142
|
+
CONNECT_ACCOUNT_TOOL,
|
|
120
143
|
TRIGGER_ANSWER_TOOL_NAME,
|
|
121
144
|
] as const;
|
|
122
145
|
|
|
@@ -146,12 +169,14 @@ export type ServerVariable = z.infer<typeof serverVariableSchema>;
|
|
|
146
169
|
|
|
147
170
|
/**
|
|
148
171
|
* A tool the server runs. Its input is an object, the `input` its calls
|
|
149
|
-
* carry. `
|
|
150
|
-
*
|
|
151
|
-
*
|
|
172
|
+
* carry. `displayName` is what the console shows it as; the model only ever
|
|
173
|
+
* sees its name. `requiresApproval` parks every call for the end user's
|
|
174
|
+
* approval; such a tool's input cannot declare `APPROVAL_TITLE_FIELD`, which
|
|
175
|
+
* the model fills for the approval card.
|
|
152
176
|
*/
|
|
153
177
|
export const serverToolSchema = z.object({
|
|
154
178
|
name: declaredToolNameSchema,
|
|
179
|
+
displayName: displayNameSchema.optional(),
|
|
155
180
|
description: z.string().trim().min(1).max(SERVER_MANIFEST_LIMITS.description),
|
|
156
181
|
inputSchema: jsonSchemaSchema.refine(
|
|
157
182
|
(schema) => schema.type === "object",
|
|
@@ -179,7 +204,7 @@ export const EVENT_HEADER = "X-Cortex-Event";
|
|
|
179
204
|
*/
|
|
180
205
|
export const approvalRequestedEventSchema = z.object({
|
|
181
206
|
event: z.literal("approval_requested"),
|
|
182
|
-
|
|
207
|
+
agentName: nameSchema,
|
|
183
208
|
agentId: z.uuid(),
|
|
184
209
|
threadId: z.string(),
|
|
185
210
|
runId: z.string(),
|
|
@@ -285,7 +310,7 @@ export const serverManifestSchema = z.object({
|
|
|
285
310
|
export type ServerManifest = z.infer<typeof serverManifestSchema>;
|
|
286
311
|
|
|
287
312
|
/** The agent a call is for. */
|
|
288
|
-
const agentSchema = z.object({ id: z.uuid(),
|
|
313
|
+
const agentSchema = z.object({ id: z.uuid(), name: nameSchema });
|
|
289
314
|
|
|
290
315
|
/** The end user a run acts for, as their token was verified; null for a trigger's run. */
|
|
291
316
|
const serverUserSchema = z
|
|
@@ -6,7 +6,7 @@ import { TRIGGER_DROP_REASONS, TRIGGER_REASONS } from "../wire/index";
|
|
|
6
6
|
* Triggers: named entry points on an agent. A firing is one invocation with a
|
|
7
7
|
* payload and starts at most one run. The end user's message is the built-in
|
|
8
8
|
* `message` trigger; every other trigger is fired by a caller over
|
|
9
|
-
* `POST /agents/:
|
|
9
|
+
* `POST /agents/:agentName/triggers/:name` with the trigger's secret as its
|
|
10
10
|
* bearer. These are the shapes Cortex answers that caller with.
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -26,14 +26,14 @@ export const TRIGGER_OUTCOMES = [
|
|
|
26
26
|
] as const;
|
|
27
27
|
export type TriggerOutcome = (typeof TRIGGER_OUTCOMES)[number];
|
|
28
28
|
|
|
29
|
-
/** The engine route of agent `
|
|
30
|
-
export function agentRoute(
|
|
31
|
-
return `/agents/${encodeURIComponent(
|
|
29
|
+
/** The engine route of agent `agentName`, which the widget chats with and its triggers sit under. */
|
|
30
|
+
export function agentRoute(agentName: string) {
|
|
31
|
+
return `/agents/${encodeURIComponent(agentName)}`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/** The engine route a trigger is fired at on agent `
|
|
35
|
-
export function triggerRoute(
|
|
36
|
-
return `${agentRoute(
|
|
34
|
+
/** The engine route a trigger is fired at on agent `agentName`; its firings are read and answered under it. */
|
|
35
|
+
export function triggerRoute(agentName: string, name: string) {
|
|
36
|
+
return `${agentRoute(agentName)}/triggers/${encodeURIComponent(name)}`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** One server tool call a parked run waits on; `POST .../firings/:id/answer` decides it. */
|
|
@@ -88,7 +88,7 @@ export const triggerFiringResponseSchema = z.discriminatedUnion("outcome", [
|
|
|
88
88
|
export type TriggerFiringResponse = z.infer<typeof triggerFiringResponseSchema>;
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* `POST /agents/:
|
|
91
|
+
* `POST /agents/:agentName/triggers/:name/firings/:id/answer`: the decision on one parked call.
|
|
92
92
|
* A call id is as long as the model provider made it (some carry a signature), so only the
|
|
93
93
|
* request's body limit bounds it.
|
|
94
94
|
*/
|
|
@@ -98,7 +98,7 @@ export const triggerFiringAnswerSchema = z.object({
|
|
|
98
98
|
});
|
|
99
99
|
export type TriggerFiringAnswer = z.infer<typeof triggerFiringAnswerSchema>;
|
|
100
100
|
|
|
101
|
-
/** One row of `GET /agents/:
|
|
101
|
+
/** One row of `GET /agents/:agentName/triggers/:name/firings`, and the answer of `.../firings/:id`. */
|
|
102
102
|
export const triggerFiringRecordSchema = z.object({
|
|
103
103
|
id: z.uuid(),
|
|
104
104
|
triggerName: z.string(),
|
|
@@ -164,11 +164,54 @@ export type ClientToolBinding = {
|
|
|
164
164
|
embedOrigin: string;
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* How the widget connects an end user's account for a `connectAccount`
|
|
169
|
+
* call, stamped on the call: where the engine starts a sign-in, at the URL
|
|
170
|
+
* browsers reach it, the agent it is for, and each server the call may
|
|
171
|
+
* name, by name: its display name, and the scope it still needs when the
|
|
172
|
+
* account is connected but lacks access.
|
|
173
|
+
*/
|
|
174
|
+
export type ConnectAccountBinding = {
|
|
175
|
+
startUrl: string;
|
|
176
|
+
agent: string;
|
|
177
|
+
servers: Record<string, { displayName: Partial<LocalizedText>; scope?: string }>;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Response of `GET /connections/:server?call=<toolCallId>`: where the
|
|
182
|
+
* caller's own account stands with the agent's MCP server of that name, for
|
|
183
|
+
* the card of that `connectAccount` call. `connected` with all a sign-in
|
|
184
|
+
* may ask for; `pending` while it is not connected, or lacks access a
|
|
185
|
+
* sign-in may still grant; `refused` once the card's own sign-in came back
|
|
186
|
+
* without it, or no sign-in may ask again.
|
|
187
|
+
*/
|
|
188
|
+
export type AccountConnection = {
|
|
189
|
+
status: "connected" | "pending" | "refused";
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The form `POST /oauth/start` takes, posted into the window a sign-in runs
|
|
194
|
+
* in with its bearer in the body, so no link can start one: the console's
|
|
195
|
+
* operator token and the server's id for the shared connection.
|
|
196
|
+
*/
|
|
197
|
+
export type SharedSignInStart = { operatorToken: string; mcpServerId: string };
|
|
198
|
+
|
|
199
|
+
/** An end user's own start: their token, their agent, the server's name, and the `connectAccount` call it answers. */
|
|
200
|
+
export type EndUserSignInStart = {
|
|
201
|
+
token: string;
|
|
202
|
+
agent: string;
|
|
203
|
+
server: string;
|
|
204
|
+
threadId: string;
|
|
205
|
+
toolCallId: string;
|
|
206
|
+
};
|
|
207
|
+
|
|
167
208
|
/**
|
|
168
209
|
* What Cortex offers this agent's clients, fetched once at widget startup.
|
|
169
210
|
* The widget hides UI for anything absent; Cortex enforces each.
|
|
170
211
|
*/
|
|
171
212
|
export type AgentCapabilities = {
|
|
213
|
+
/** The agent's display name per locale, which titles a new chat; `{}` for an agent without one. */
|
|
214
|
+
displayName: Partial<LocalizedText>;
|
|
172
215
|
attachments: boolean;
|
|
173
216
|
/**
|
|
174
217
|
* False when this caller may not send to the chat: the agent's `message`
|
|
@@ -214,6 +257,15 @@ export type Greeting = Record<Locale, string>;
|
|
|
214
257
|
/** Author-written copy the widget shows verbatim, one string per locale. */
|
|
215
258
|
export type LocalizedText = Record<Locale, string>;
|
|
216
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Resolves a per-locale value against `locale`: exact match, then English,
|
|
262
|
+
* then any entry, so a sparse map still renders something. An entity's
|
|
263
|
+
* label is `localized(entity.displayName, locale) ?? entity.name`.
|
|
264
|
+
*/
|
|
265
|
+
export function localized<T>(value: Partial<Record<Locale, T>>, locale: Locale) {
|
|
266
|
+
return value[locale] ?? value.en ?? Object.values(value)[0];
|
|
267
|
+
}
|
|
268
|
+
|
|
217
269
|
/**
|
|
218
270
|
* Cortex's own tools whose input carries `status`: the model's one-line
|
|
219
271
|
* statement of the call, which the widget draws in place of a generic label.
|
|
@@ -252,6 +304,8 @@ export type ToolCallMetadata = {
|
|
|
252
304
|
answeredBy?: "client";
|
|
253
305
|
/** Set beside `answeredBy` on a call of a client tool with an embedded page: the widget mounts the embed for it. */
|
|
254
306
|
embed?: ClientToolBinding;
|
|
307
|
+
/** Set beside `answeredBy` on a `connectAccount` call: the widget starts the sign-in with it. */
|
|
308
|
+
connect?: ConnectAccountBinding;
|
|
255
309
|
};
|
|
256
310
|
|
|
257
311
|
/** The `metadata.cortex` a tool call carries, when Cortex stamped one. */
|