@m6d/cortex-cli 0.20.0 → 0.21.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/management/procedures.generated.ts +345 -162
- package/src/contracts/manifest/index.ts +62 -91
- package/src/contracts/server/index.ts +33 -9
- package/src/contracts/triggers/index.ts +9 -9
- package/src/contracts/wire/index.ts +11 -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);
|
|
@@ -1204,6 +1199,7 @@ export const mcpToolNamesSchema = z
|
|
|
1204
1199
|
const manifestMcpServerSchema = z.strictObject({
|
|
1205
1200
|
/** Prefixes every tool the server contributes: scripts call `tools.<name>_<tool>()`. */
|
|
1206
1201
|
name: identifierSchema,
|
|
1202
|
+
displayName: displayNameSchema.default({}),
|
|
1207
1203
|
url: upstreamUrlSchema,
|
|
1208
1204
|
headers: declaredHeadersSchema(
|
|
1209
1205
|
presentedHeaderSchema.refine(
|
|
@@ -1216,7 +1212,7 @@ const manifestMcpServerSchema = z.strictObject({
|
|
|
1216
1212
|
include: mcpToolNamesSchema.default([]),
|
|
1217
1213
|
exclude: mcpToolNamesSchema.default([]),
|
|
1218
1214
|
audience: audienceSchema,
|
|
1219
|
-
overridable: overridable(["include", "exclude", "headers"]),
|
|
1215
|
+
overridable: overridable(["displayName", "include", "exclude", "headers"]),
|
|
1220
1216
|
});
|
|
1221
1217
|
export type ManifestMcpServer = z.input<typeof manifestMcpServerSchema>;
|
|
1222
1218
|
|
|
@@ -1226,7 +1222,8 @@ export type ManifestMcpServer = z.input<typeof manifestMcpServerSchema>;
|
|
|
1226
1222
|
* travels under the header that names the event.
|
|
1227
1223
|
*/
|
|
1228
1224
|
const manifestWebhookSchema = z.strictObject({
|
|
1229
|
-
name:
|
|
1225
|
+
name: nameSchema,
|
|
1226
|
+
displayName: displayNameSchema.default({}),
|
|
1230
1227
|
description: z.string().trim().max(500).default(""),
|
|
1231
1228
|
event: z.enum(SERVER_EVENTS),
|
|
1232
1229
|
url: upstreamUrlSchema,
|
|
@@ -1239,19 +1236,15 @@ const manifestWebhookSchema = z.strictObject({
|
|
|
1239
1236
|
),
|
|
1240
1237
|
timeoutMs: setting(SETTING_BOUNDS.webhook.timeoutMs),
|
|
1241
1238
|
audience: audienceSchema,
|
|
1242
|
-
overridable: overridable(["description", "timeoutMs", "headers"]),
|
|
1239
|
+
overridable: overridable(["displayName", "description", "timeoutMs", "headers"]),
|
|
1243
1240
|
});
|
|
1244
1241
|
export type ManifestWebhook = z.input<typeof manifestWebhookSchema>;
|
|
1245
1242
|
|
|
1246
1243
|
/**
|
|
1247
|
-
* A collection's
|
|
1248
|
-
*
|
|
1244
|
+
* A collection's description in characters, and how many documents a
|
|
1245
|
+
* manifest lists in one. The console caps its editor at the same.
|
|
1249
1246
|
*/
|
|
1250
|
-
export const COLLECTION_LIMITS = {
|
|
1251
|
-
nameLength: 80,
|
|
1252
|
-
descriptionLength: 2_000,
|
|
1253
|
-
documents: 100,
|
|
1254
|
-
} as const;
|
|
1247
|
+
export const COLLECTION_LIMITS = { descriptionLength: 2_000, documents: 100 } as const;
|
|
1255
1248
|
|
|
1256
1249
|
/** A knowledge document's name in characters and its size in bytes, however it arrives. */
|
|
1257
1250
|
export const DOCUMENT_LIMITS = { nameLength: 255, bytes: 10 * 1024 * 1024 } as const;
|
|
@@ -1377,15 +1370,16 @@ function collectionSchema<TDocument extends z.ZodType<Parameters<typeof document
|
|
|
1377
1370
|
document: TDocument,
|
|
1378
1371
|
) {
|
|
1379
1372
|
return z.strictObject({
|
|
1380
|
-
name:
|
|
1373
|
+
name: nameSchema,
|
|
1374
|
+
displayName: displayNameSchema.default({}),
|
|
1381
1375
|
description: z.string().trim().max(COLLECTION_LIMITS.descriptionLength).default(""),
|
|
1382
|
-
/** The
|
|
1383
|
-
service:
|
|
1376
|
+
/** The name of the service every document in the collection belongs to. */
|
|
1377
|
+
service: nameSchema.optional(),
|
|
1384
1378
|
/** Whether answers may name its documents; false keeps their names, sections and pages from the model. */
|
|
1385
1379
|
citable: z.boolean().default(true),
|
|
1386
1380
|
documents: documentsSchema(document),
|
|
1387
1381
|
audience: audienceSchema,
|
|
1388
|
-
overridable: overridable(["description", "documents"]),
|
|
1382
|
+
overridable: overridable(["displayName", "description", "documents"]),
|
|
1389
1383
|
});
|
|
1390
1384
|
}
|
|
1391
1385
|
|
|
@@ -1497,13 +1491,10 @@ export const agentSettingsSchema = z.strictObject({
|
|
|
1497
1491
|
.prefault({}),
|
|
1498
1492
|
});
|
|
1499
1493
|
|
|
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
1494
|
const manifestAgentSchema = z
|
|
1504
1495
|
.strictObject({
|
|
1505
|
-
|
|
1506
|
-
|
|
1496
|
+
name: nameSchema,
|
|
1497
|
+
displayName: displayNameSchema.default({}),
|
|
1507
1498
|
/**
|
|
1508
1499
|
* The server it belongs to, by name; a manifest never creates one. Left
|
|
1509
1500
|
* out, the agent has no server: no native tools, no server variables,
|
|
@@ -1545,7 +1536,7 @@ const manifestAgentSchema = z
|
|
|
1545
1536
|
suggestions: suggestionsSchema.default(() => byLocale(() => [])),
|
|
1546
1537
|
greeting: greetingSchema.default(() => byLocale(() => "")),
|
|
1547
1538
|
overridable: overridable([
|
|
1548
|
-
"
|
|
1539
|
+
"displayName",
|
|
1549
1540
|
"prompt",
|
|
1550
1541
|
"models",
|
|
1551
1542
|
"settings",
|
|
@@ -1592,31 +1583,14 @@ export const MANIFEST_KINDS = [
|
|
|
1592
1583
|
export type ManifestKind = (typeof MANIFEST_KINDS)[number];
|
|
1593
1584
|
|
|
1594
1585
|
/**
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1597
|
-
* once, so one entry means one row.
|
|
1586
|
+
* A list of entries, each going by the name its row holds, refusing a name
|
|
1587
|
+
* they repeat: one entry means one row.
|
|
1598
1588
|
*/
|
|
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
|
-
) {
|
|
1589
|
+
function listOf<TEntry extends z.ZodType<{ name: string }>>(entry: TEntry) {
|
|
1616
1590
|
return z
|
|
1617
1591
|
.array(entry)
|
|
1618
1592
|
.refine(
|
|
1619
|
-
(entries) => new Set(entries.map((value) => value
|
|
1593
|
+
(entries) => new Set(entries.map((value) => value.name)).size === entries.length,
|
|
1620
1594
|
"a name repeats",
|
|
1621
1595
|
)
|
|
1622
1596
|
.default([]);
|
|
@@ -1634,17 +1608,17 @@ export const CORTEX_MANIFEST_ID = "cortex";
|
|
|
1634
1608
|
*/
|
|
1635
1609
|
export const manifestSchema = z.strictObject({
|
|
1636
1610
|
/** 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(
|
|
1611
|
+
id: nameSchema,
|
|
1612
|
+
agents: listOf(manifestAgentSchema),
|
|
1613
|
+
tools: listOf(manifestToolSchema),
|
|
1614
|
+
openapi: listOf(manifestOpenApiSchema),
|
|
1615
|
+
services: listOf(manifestServiceSchema),
|
|
1616
|
+
skills: listOf(manifestSkillSchema),
|
|
1617
|
+
triggers: listOf(manifestTriggerSchema),
|
|
1618
|
+
datasetHosts: listOf(manifestDatasetHostSchema),
|
|
1619
|
+
mcpServers: listOf(manifestMcpServerSchema),
|
|
1620
|
+
webhooks: listOf(manifestWebhookSchema),
|
|
1621
|
+
collections: listOf(manifestCollectionSchema),
|
|
1648
1622
|
} satisfies Record<ManifestKind | "id" | "openapi", z.ZodType>);
|
|
1649
1623
|
/** A manifest as written: every field with a default may be left out. */
|
|
1650
1624
|
export type Manifest = z.input<typeof manifestSchema>;
|
|
@@ -1655,15 +1629,12 @@ export type Manifest = z.input<typeof manifestSchema>;
|
|
|
1655
1629
|
* beside it.
|
|
1656
1630
|
*/
|
|
1657
1631
|
export const resolvedManifestSchema = manifestSchema.extend({
|
|
1658
|
-
collections: listOf(
|
|
1632
|
+
collections: listOf(resolvedCollectionSchema),
|
|
1659
1633
|
});
|
|
1660
1634
|
|
|
1661
1635
|
const manifestKindSchema = z.enum(MANIFEST_KINDS);
|
|
1662
1636
|
|
|
1663
|
-
/**
|
|
1664
|
-
* Entry names per kind, each by its `MANIFEST_NAME_FIELDS` field. An OpenAPI
|
|
1665
|
-
* source's operations are counted among `tools`.
|
|
1666
|
-
*/
|
|
1637
|
+
/** Entry names per kind. An OpenAPI source's operations are counted among `tools`. */
|
|
1667
1638
|
const namesByKindSchema = z.record(manifestKindSchema, z.array(z.string()));
|
|
1668
1639
|
|
|
1669
1640
|
/** What an apply creates, updates and deletes, kind by kind; a preview answers the same without doing it. */
|
|
@@ -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,
|
|
@@ -146,12 +168,14 @@ export type ServerVariable = z.infer<typeof serverVariableSchema>;
|
|
|
146
168
|
|
|
147
169
|
/**
|
|
148
170
|
* A tool the server runs. Its input is an object, the `input` its calls
|
|
149
|
-
* carry. `
|
|
150
|
-
*
|
|
151
|
-
*
|
|
171
|
+
* carry. `displayName` is what the console shows it as; the model only ever
|
|
172
|
+
* sees its name. `requiresApproval` parks every call for the end user's
|
|
173
|
+
* approval; such a tool's input cannot declare `APPROVAL_TITLE_FIELD`, which
|
|
174
|
+
* the model fills for the approval card.
|
|
152
175
|
*/
|
|
153
176
|
export const serverToolSchema = z.object({
|
|
154
177
|
name: declaredToolNameSchema,
|
|
178
|
+
displayName: displayNameSchema.optional(),
|
|
155
179
|
description: z.string().trim().min(1).max(SERVER_MANIFEST_LIMITS.description),
|
|
156
180
|
inputSchema: jsonSchemaSchema.refine(
|
|
157
181
|
(schema) => schema.type === "object",
|
|
@@ -179,7 +203,7 @@ export const EVENT_HEADER = "X-Cortex-Event";
|
|
|
179
203
|
*/
|
|
180
204
|
export const approvalRequestedEventSchema = z.object({
|
|
181
205
|
event: z.literal("approval_requested"),
|
|
182
|
-
|
|
206
|
+
agentName: nameSchema,
|
|
183
207
|
agentId: z.uuid(),
|
|
184
208
|
threadId: z.string(),
|
|
185
209
|
runId: z.string(),
|
|
@@ -285,7 +309,7 @@ export const serverManifestSchema = z.object({
|
|
|
285
309
|
export type ServerManifest = z.infer<typeof serverManifestSchema>;
|
|
286
310
|
|
|
287
311
|
/** The agent a call is for. */
|
|
288
|
-
const agentSchema = z.object({ id: z.uuid(),
|
|
312
|
+
const agentSchema = z.object({ id: z.uuid(), name: nameSchema });
|
|
289
313
|
|
|
290
314
|
/** The end user a run acts for, as their token was verified; null for a trigger's run. */
|
|
291
315
|
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(),
|
|
@@ -169,6 +169,8 @@ export type ClientToolBinding = {
|
|
|
169
169
|
* The widget hides UI for anything absent; Cortex enforces each.
|
|
170
170
|
*/
|
|
171
171
|
export type AgentCapabilities = {
|
|
172
|
+
/** The agent's display name per locale, which titles a new chat; `{}` for an agent without one. */
|
|
173
|
+
displayName: Partial<LocalizedText>;
|
|
172
174
|
attachments: boolean;
|
|
173
175
|
/**
|
|
174
176
|
* False when this caller may not send to the chat: the agent's `message`
|
|
@@ -214,6 +216,15 @@ export type Greeting = Record<Locale, string>;
|
|
|
214
216
|
/** Author-written copy the widget shows verbatim, one string per locale. */
|
|
215
217
|
export type LocalizedText = Record<Locale, string>;
|
|
216
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Resolves a per-locale value against `locale`: exact match, then English,
|
|
221
|
+
* then any entry, so a sparse map still renders something. An entity's
|
|
222
|
+
* label is `localized(entity.displayName, locale) ?? entity.name`.
|
|
223
|
+
*/
|
|
224
|
+
export function localized<T>(value: Partial<Record<Locale, T>>, locale: Locale) {
|
|
225
|
+
return value[locale] ?? value.en ?? Object.values(value)[0];
|
|
226
|
+
}
|
|
227
|
+
|
|
217
228
|
/**
|
|
218
229
|
* Cortex's own tools whose input carries `status`: the model's one-line
|
|
219
230
|
* statement of the call, which the widget draws in place of a generic label.
|
package/src/scaffold/files.ts
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* a standalone scaffold has no workspace.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { NAME_MAX_LENGTH } from "@cortex/contracts/manifest";
|
|
12
|
-
|
|
13
11
|
import pkg from "../../package.json";
|
|
14
12
|
import serverTs from "@/scaffold/templates/server.ts.txt";
|
|
15
13
|
import setupTs from "@/scaffold/templates/setup.ts.txt";
|
|
@@ -30,7 +28,7 @@ import { applyPorts, type Ports } from "@/scaffold/ports";
|
|
|
30
28
|
* `@m6d/cortex` range below are stamped by scripts/sync-package-versions.ts
|
|
31
29
|
* before the CLI publishes, so a scaffold pins what was current then.
|
|
32
30
|
*/
|
|
33
|
-
const CORTEX_VERSION = "0.
|
|
31
|
+
const CORTEX_VERSION = "0.20.0";
|
|
34
32
|
|
|
35
33
|
/** This machine as Cortex's containers name it: the one private host the stack's egress policy admits. */
|
|
36
34
|
export const DOCKER_HOST = "host.docker.internal";
|
|
@@ -40,9 +38,9 @@ const SERVER_URL = `http://${DOCKER_HOST}:{{port.server}}`;
|
|
|
40
38
|
|
|
41
39
|
/**
|
|
42
40
|
* The agent the manifest declares and the README sends you to chat with.
|
|
43
|
-
* Not `assistant`: Cortex holds that
|
|
41
|
+
* Not `assistant`: Cortex holds that name for its console's own assistant.
|
|
44
42
|
*/
|
|
45
|
-
export const
|
|
43
|
+
export const AGENT_NAME = "helper";
|
|
46
44
|
|
|
47
45
|
/**
|
|
48
46
|
* The embedding model the stack asks for, and the dimension the image's
|
|
@@ -111,7 +109,7 @@ function runAll(runner: "mprocs" | "concurrently", processes: Record<string, str
|
|
|
111
109
|
return `${runner} --names ${Object.keys(processes).join(",")} ${commands.join(" ")}`;
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
/** The name as the manifest `id`
|
|
112
|
+
/** The name as the manifest `id` and the server's name take it: lowercase words and hyphens. */
|
|
115
113
|
export function manifestIdOf(name: string) {
|
|
116
114
|
return name
|
|
117
115
|
.replace(/[^a-z0-9]+/g, "-")
|
|
@@ -149,7 +147,7 @@ function packageJson({ name, datasets, setup }: Project) {
|
|
|
149
147
|
verify: "bun run format:check && bun run lint && bun run spellcheck && bun run check && bun run audit",
|
|
150
148
|
},
|
|
151
149
|
dependencies: {
|
|
152
|
-
"@m6d/cortex": "~0.
|
|
150
|
+
"@m6d/cortex": "~0.8.0",
|
|
153
151
|
// drizzle 1.0 is in beta; this is the build `@m6d/cortex/drizzle` is tested against.
|
|
154
152
|
...(datasets ? { "drizzle-orm": "1.0.0-beta.15-859cf75" } : {}),
|
|
155
153
|
hono: "^4.12.3",
|
|
@@ -218,8 +216,8 @@ export default defineManifest({
|
|
|
218
216
|
id: ${JSON.stringify(manifestIdOf(name))},
|
|
219
217
|
agents: [
|
|
220
218
|
{
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
name: "${AGENT_NAME}",
|
|
220
|
+
displayName: { en: "Helper" },
|
|
223
221
|
server,
|
|
224
222
|
prompt: "You are a helpful assistant. Address the user as {{userName}}.",
|
|
225
223
|
// No identity provider yet: each browser is a visitor of its own.
|
|
@@ -460,7 +458,7 @@ function readme({ name, server, datasets, setup }: Project) {
|
|
|
460
458
|
const gettingStarted = setup
|
|
461
459
|
? `That starts Cortex (\`docker compose up\`), this backend, and \`cortex setup\` in one dashboard.
|
|
462
460
|
Setup waits for Cortex, registers this backend as the server \`${server}\`, adds the model provider
|
|
463
|
-
in \`.env\`, and applies the manifest. Then it prints where to chat with \`${
|
|
461
|
+
in \`.env\`, and applies the manifest. Then it prints where to chat with \`${AGENT_NAME}\`, and the first
|
|
464
462
|
time, how to sign in. It adds only what is missing, so every \`bun run dev\` runs it again.
|
|
465
463
|
|
|
466
464
|
While \`MODEL_PROVIDER_*\` in \`.env\` is blank, setup registers the server and applies nothing:
|
|
@@ -487,7 +485,7 @@ A backend for Cortex agents, and Cortex itself to run them on.
|
|
|
487
485
|
? "\n- `datasets.ts` serves an `orders` table as a dataset host, for agents to ask grouped\n questions of."
|
|
488
486
|
: ""
|
|
489
487
|
}
|
|
490
|
-
- \`cortex.manifest.ts\` declares the \`${
|
|
488
|
+
- \`cortex.manifest.ts\` declares the \`${AGENT_NAME}\` agent on this server${
|
|
491
489
|
datasets ? " and the dataset host" : ""
|
|
492
490
|
}. \`cortex apply\` makes Cortex match it.
|
|
493
491
|
- \`docker-compose.yml\` runs Cortex: its app, worker and engine, and the stores they use.
|
|
@@ -507,7 +505,7 @@ bun run dev
|
|
|
507
505
|
|
|
508
506
|
${gettingStarted}
|
|
509
507
|
|
|
510
|
-
Then chat with \`${
|
|
508
|
+
Then chat with \`${AGENT_NAME}\` on the console's **Playground** page. Edit \`cortex.manifest.ts\` and run
|
|
511
509
|
\`bun cortex apply\` to change it; an apply changes only what the edit changed. The CLI reaches this
|
|
512
510
|
checkout's Cortex through \`CORTEX_URL\` and \`CORTEX_KEY\` in \`.env\`, so it needs no login, and
|
|
513
511
|
\`-p <profile>\` picks a saved one instead.
|
|
@@ -538,7 +536,7 @@ pre-commit hook or CI job should run. Each is also available on its own: \`forma
|
|
|
538
536
|
* are drawn by the caller.
|
|
539
537
|
*/
|
|
540
538
|
export function scaffoldFiles(input: Input) {
|
|
541
|
-
const project = { ...input, server: input.name
|
|
539
|
+
const project = { ...input, server: manifestIdOf(input.name) };
|
|
542
540
|
const files = {
|
|
543
541
|
"server.ts": serverTs,
|
|
544
542
|
"setup.ts": setupTs,
|