@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.
@@ -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
- agentSlugSchema,
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 by its key, an agent by its slug, and a stored secret
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({ kind: z.literal("credential"), name: nameSchema });
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 slug. */
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(agentSlugSchema)
886
+ .array(nameSchema)
893
887
  .min(1)
894
- .refine((slugs) => new Set(slugs).size === slugs.length, "an agent repeats"),
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 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;
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
- /** Identified by its `key`, which skills and collections scope themselves by; `name` is its label. */
1044
+ /** Skills and collections scope themselves by a service's `name`. */
1054
1045
  const manifestServiceSchema = z.strictObject({
1055
- /** What consumers parameterize their tools with; fixed once published. */
1056
- key: slugSchema,
1057
- name: localizedLineSchema,
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: slugSchema,
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 key of the service the skill renders inside; fixed once created. */
1074
- service: slugSchema.optional(),
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: slugSchema.refine((name) => name !== MESSAGE_TRIGGER_NAME, "the built-in trigger's 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
- const manifestMcpServerSchema = z.strictObject({
1205
- /** Prefixes every tool the server contributes: scripts call `tools.<name>_<tool>()`. */
1206
- name: identifierSchema,
1207
- url: upstreamUrlSchema,
1208
- headers: declaredHeadersSchema(
1209
- presentedHeaderSchema.refine(
1210
- (name) => !mcpTransportHeaders.has(name.toLowerCase()),
1211
- "a header the MCP transport sets",
1212
- ),
1213
- headerSourceSchema,
1214
- ),
1215
- /** The server's own tool names to offer; empty offers every tool. */
1216
- include: mcpToolNamesSchema.default([]),
1217
- exclude: mcpToolNamesSchema.default([]),
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: slugSchema,
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 name and description in characters, and how many documents
1248
- * a manifest lists in one. The console caps its editor at the same.
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: z.string().trim().min(1).max(COLLECTION_LIMITS.nameLength),
1417
+ name: nameSchema,
1418
+ displayName: displayNameSchema.default({}),
1381
1419
  description: z.string().trim().max(COLLECTION_LIMITS.descriptionLength).default(""),
1382
- /** The key of the service every document in the collection belongs to. */
1383
- service: slugSchema.optional(),
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
- slug: agentSlugSchema,
1506
- name: z.string().trim().min(1).max(AGENT_LIMITS.nameLength),
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
- "name",
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
- * The field each kind's entries go by, which its row's name holds: an
1596
- * agent's slug, a service's key, any other entry's name. A list holds each
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
- export const MANIFEST_NAME_FIELDS = {
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[field])).size === entries.length,
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: slugSchema,
1638
- agents: listOf(MANIFEST_NAME_FIELDS.agents, manifestAgentSchema),
1639
- tools: listOf(MANIFEST_NAME_FIELDS.tools, manifestToolSchema),
1640
- openapi: listOf("name", manifestOpenApiSchema),
1641
- services: listOf(MANIFEST_NAME_FIELDS.services, manifestServiceSchema),
1642
- skills: listOf(MANIFEST_NAME_FIELDS.skills, manifestSkillSchema),
1643
- triggers: listOf(MANIFEST_NAME_FIELDS.triggers, manifestTriggerSchema),
1644
- datasetHosts: listOf(MANIFEST_NAME_FIELDS.datasetHosts, manifestDatasetHostSchema),
1645
- mcpServers: listOf(MANIFEST_NAME_FIELDS.mcpServers, manifestMcpServerSchema),
1646
- webhooks: listOf(MANIFEST_NAME_FIELDS.webhooks, manifestWebhookSchema),
1647
- collections: listOf(MANIFEST_NAME_FIELDS.collections, manifestCollectionSchema),
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(MANIFEST_NAME_FIELDS.collections, resolvedCollectionSchema),
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. `requiresApproval` parks every call for the end user's approval;
150
- * such a tool's input cannot declare `APPROVAL_TITLE_FIELD`, which the
151
- * model fills for the approval card.
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
- agentSlug: agentSlugSchema,
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(), slug: agentSlugSchema });
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/:agentSlug/triggers/:name` with the trigger's secret as its
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 `agentSlug`, which the widget chats with and its triggers sit under. */
30
- export function agentRoute(agentSlug: string) {
31
- return `/agents/${encodeURIComponent(agentSlug)}`;
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 `agentSlug`; its firings are read and answered under it. */
35
- export function triggerRoute(agentSlug: string, name: string) {
36
- return `${agentRoute(agentSlug)}/triggers/${encodeURIComponent(name)}`;
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/:agentSlug/triggers/:name/firings/:id/answer`: the decision on one parked call.
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/:agentSlug/triggers/:name/firings`, and the answer of `.../firings/:id`. */
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. */