@m6d/cortex-cli 0.12.0 → 0.13.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 +135 -130
- package/package.json +16 -5
- package/src/cli.ts +17 -12
- package/src/commands/apply.ts +169 -0
- package/src/commands/{cc.ts → management.ts} +150 -100
- package/src/commands/new.ts +50 -74
- package/src/contracts/charts/chart-spec.ts +1 -1
- package/src/contracts/client-tools/index.ts +3 -3
- package/src/contracts/{runtime → datasets}/datasets.ts +7 -42
- package/src/contracts/datasets/index.ts +3 -0
- package/src/contracts/egress-policy/index.ts +16 -0
- package/src/contracts/{egress → egress-policy}/scrub.ts +34 -5
- package/src/contracts/{egress → egress-policy}/url.ts +22 -9
- package/src/contracts/errors/index.ts +38 -0
- package/src/contracts/json/index.ts +5 -0
- package/src/contracts/manifest/index.ts +932 -0
- package/src/contracts/manifest-file/index.ts +16 -0
- package/src/contracts/markdown/index.ts +1 -1
- package/src/contracts/pattern/index.ts +276 -0
- package/src/contracts/server/index.ts +247 -0
- package/src/contracts/server/reserved-names.fixtures.json +18 -0
- package/src/contracts/triggers/index.ts +112 -0
- package/src/contracts/wire/index.ts +61 -32
- package/src/{cc → management}/client.ts +19 -7
- package/src/{cc → management}/flags.ts +2 -1
- package/src/{cc → management}/profiles.ts +5 -5
- package/src/scaffold/files.ts +362 -237
- package/src/scaffold/ports.ts +13 -24
- package/src/scaffold/templates/datasets.ts.txt +52 -0
- package/src/scaffold/templates/dockerignore.txt +7 -0
- package/src/scaffold/templates/fallowrc.jsonc.txt +8 -12
- package/src/scaffold/templates/gitignore.txt +2 -1
- package/src/scaffold/templates/server.ts.txt +40 -0
- package/src/scaffold/templates/setup.ts.txt +52 -0
- package/src/ui/prompts.ts +4 -31
- package/src/ui/report.ts +5 -15
- package/tsconfig.json +2 -3
- package/src/commands/graph.ts +0 -228
- package/src/commands/swagger.ts +0 -247
- package/src/config/load.ts +0 -122
- package/src/config/validate.ts +0 -46
- package/src/contracts/credentials/index.ts +0 -183
- package/src/contracts/egress/index.ts +0 -120
- package/src/contracts/graph/clients/embed.ts +0 -45
- package/src/contracts/graph/clients/neo4j.ts +0 -85
- package/src/contracts/graph/helpers.ts +0 -55
- package/src/contracts/graph/index.ts +0 -30
- package/src/contracts/graph/schema.ts +0 -65
- package/src/contracts/graph/types.ts +0 -125
- package/src/contracts/rich-text/index.ts +0 -220
- package/src/contracts/runtime/api.ts +0 -345
- package/src/contracts/runtime/auth.ts +0 -41
- package/src/contracts/runtime/grammar.ts +0 -169
- package/src/contracts/runtime/index.ts +0 -15
- package/src/contracts/runtime/models.ts +0 -50
- package/src/contracts/runtime/signature.ts +0 -255
- package/src/contracts/runtime/sync.ts +0 -196
- package/src/contracts/runtime/triggers.ts +0 -267
- package/src/graph/expand-domains.ts +0 -286
- package/src/graph/generate-cypher.ts +0 -201
- package/src/graph/seed.ts +0 -199
- package/src/graph/validate.ts +0 -78
- package/src/scaffold/features.ts +0 -190
- package/src/scaffold/templates/domains-README.md.txt +0 -16
- package/src/scaffold/templates/index.ts.txt +0 -18
- package/src/swagger/extract-endpoints.ts +0 -269
- package/src/swagger/generated-block.ts +0 -74
- package/src/swagger/openapi-schema.ts +0 -168
- package/src/swagger/parse-response.ts +0 -88
- package/src/swagger/types.ts +0 -25
- /package/src/contracts/{runtime → datasets}/dataset-sql-guide.ts +0 -0
- /package/src/contracts/{runtime → datasets}/dataset-sql.ts +0 -0
- /package/src/contracts/{runtime → datasets}/datasets.fixtures.json +0 -0
- /package/src/{cc → management}/output.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The wire contract — every shape that crosses the HTTP/WebSocket boundary
|
|
3
|
-
* between
|
|
3
|
+
* between Cortex and the client SDKs (`@m6d/cortex-angular`,
|
|
4
4
|
* `@m6d/cortex-react`).
|
|
5
5
|
*
|
|
6
6
|
* Nothing here has a runtime dependency, and nothing here may grow one: this
|
|
@@ -40,6 +40,9 @@ export const ATTACHMENT_MIME_TYPES = [
|
|
|
40
40
|
|
|
41
41
|
export const ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
42
42
|
|
|
43
|
+
/** The longest name an attachment takes, uploaded or produced by a tool. */
|
|
44
|
+
export const ATTACHMENT_FILENAME_MAX_LENGTH = 255;
|
|
45
|
+
|
|
43
46
|
export const MAX_ATTACHMENTS_PER_MESSAGE = 5;
|
|
44
47
|
|
|
45
48
|
export const ATTACHMENT_STATUSES = ["pending", "described", "failed", "skipped"] as const;
|
|
@@ -49,9 +52,11 @@ export type AttachmentStatus = (typeof ATTACHMENT_STATUSES)[number];
|
|
|
49
52
|
export const ATTACHMENT_ORIGINS = ["upload", "tool"] as const;
|
|
50
53
|
export type AttachmentOrigin = (typeof ATTACHMENT_ORIGINS)[number];
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
+
/** Whether an upload fits the attachment policy, judged by the widget before it sends one and by Cortex as it takes one. */
|
|
56
|
+
export function checkAttachmentPolicy(file: { type: string; size: number; name: string }) {
|
|
57
|
+
if (!ATTACHMENT_MIME_TYPES.some((type) => type === file.type)) return "type";
|
|
58
|
+
if (file.size > ATTACHMENT_MAX_BYTES) return "size";
|
|
59
|
+
if (file.name.length > ATTACHMENT_FILENAME_MAX_LENGTH) return "name";
|
|
55
60
|
return "ok";
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -75,6 +80,15 @@ export type ThreadSummary = {
|
|
|
75
80
|
trigger?: { name: string };
|
|
76
81
|
};
|
|
77
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Body of `POST /threads`. Only a console operator's request is read: its
|
|
85
|
+
* test thread keeps `variables` as the session the agent's server would
|
|
86
|
+
* otherwise fill per end user.
|
|
87
|
+
*/
|
|
88
|
+
export type CreateThreadRequest = {
|
|
89
|
+
variables?: Record<string, string | number | boolean>;
|
|
90
|
+
};
|
|
91
|
+
|
|
78
92
|
/**
|
|
79
93
|
* Response of `POST /chat/:threadId/abort`. `aborted: false` means there was no
|
|
80
94
|
* live run to abort — the turn had already committed — so the caller must not
|
|
@@ -100,9 +114,8 @@ export type TokenUsage = {
|
|
|
100
114
|
};
|
|
101
115
|
|
|
102
116
|
/**
|
|
103
|
-
* How
|
|
104
|
-
*
|
|
105
|
-
* the message so the binding survives server restarts while a run is parked.
|
|
117
|
+
* How Cortex opens an embedded client tool's page for one call. Stored on the
|
|
118
|
+
* call, so the binding survives a restart while a run is parked.
|
|
106
119
|
*/
|
|
107
120
|
export type ClientToolBinding = {
|
|
108
121
|
toolId: string;
|
|
@@ -113,8 +126,8 @@ export type ClientToolBinding = {
|
|
|
113
126
|
};
|
|
114
127
|
|
|
115
128
|
/**
|
|
116
|
-
* What
|
|
117
|
-
* The widget hides UI for anything absent;
|
|
129
|
+
* What Cortex offers this agent's clients, fetched once at widget startup.
|
|
130
|
+
* The widget hides UI for anything absent; Cortex enforces each.
|
|
118
131
|
*/
|
|
119
132
|
export type AgentCapabilities = {
|
|
120
133
|
attachments: boolean;
|
|
@@ -127,14 +140,13 @@ export type AgentCapabilities = {
|
|
|
127
140
|
};
|
|
128
141
|
|
|
129
142
|
export type MessageMetadata = {
|
|
130
|
-
/** ISO time
|
|
143
|
+
/** ISO time Cortex stored the message; stamped from the row when a transcript is served. */
|
|
131
144
|
createdAt?: string;
|
|
132
145
|
modelId?: string;
|
|
133
146
|
providerMetadata?: unknown;
|
|
134
147
|
isAborted?: boolean;
|
|
135
148
|
tokenUsage?: TokenUsage;
|
|
136
149
|
attachments?: AttachmentSummary[];
|
|
137
|
-
clientTools?: Record<string, ClientToolBinding>;
|
|
138
150
|
/** Set on `trigger` messages: the firing that wrote them. */
|
|
139
151
|
trigger?: TriggerFiringSummary;
|
|
140
152
|
};
|
|
@@ -145,7 +157,7 @@ export type MessageMetadata = {
|
|
|
145
157
|
*/
|
|
146
158
|
export type Suggestion = { title: string; prompt: string };
|
|
147
159
|
|
|
148
|
-
/** The
|
|
160
|
+
/** The manifest's suggestion and greeting schemas are built from these. */
|
|
149
161
|
export type Suggestions = Record<Locale, Suggestion[]>;
|
|
150
162
|
export type Greeting = Record<Locale, string>;
|
|
151
163
|
|
|
@@ -160,14 +172,27 @@ export type LocalizedText = Record<Locale, string>;
|
|
|
160
172
|
export const STATEMENT_TOOLS = ["executeCode", "queryDataset", "describeDataset"] as const;
|
|
161
173
|
|
|
162
174
|
/**
|
|
163
|
-
* What
|
|
164
|
-
*
|
|
165
|
-
* author's words rather than the function's name.
|
|
175
|
+
* What Cortex stamps on a tool call's `metadata.cortex` from its first chunk,
|
|
176
|
+
* so the live part and the stored one agree.
|
|
166
177
|
*/
|
|
167
178
|
export type ToolCallMetadata = {
|
|
179
|
+
/** The action the approval card names, in the author's words rather than the function's name. */
|
|
168
180
|
approval?: LocalizedText;
|
|
181
|
+
/**
|
|
182
|
+
* Set on every call the client answers: a client tool's, the consumer's own, or a built-in
|
|
183
|
+
* input's. Cortex runs every other call, so only these take an output from a request.
|
|
184
|
+
*/
|
|
185
|
+
answeredBy?: "client";
|
|
186
|
+
/** Set beside `answeredBy` on a call of a client tool with an embedded page: the widget mounts the embed for it. */
|
|
187
|
+
embed?: ClientToolBinding;
|
|
169
188
|
};
|
|
170
189
|
|
|
190
|
+
/** The `metadata.cortex` a tool call carries, when Cortex stamped one. */
|
|
191
|
+
export function toolCallMetadataOf(part: { metadata?: unknown }) {
|
|
192
|
+
const metadata = part.metadata as { cortex?: ToolCallMetadata | null } | null | undefined;
|
|
193
|
+
return metadata?.cortex ?? undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
171
196
|
export type TriggerFiringSummary = {
|
|
172
197
|
name: string;
|
|
173
198
|
firingId: string;
|
|
@@ -177,7 +202,7 @@ export type TriggerFiringSummary = {
|
|
|
177
202
|
|
|
178
203
|
/**
|
|
179
204
|
* A stored message as it is persisted and served: the SDK's parts plus the
|
|
180
|
-
*
|
|
205
|
+
* envelope Cortex derives for the UIs, carried in the SDK message's own
|
|
181
206
|
* `metadata` bag so it travels with the message over the stream and the
|
|
182
207
|
* transcript alike.
|
|
183
208
|
*
|
|
@@ -203,7 +228,7 @@ export function toUiMessage<TPart>(message: CortexMessage<TPart>) {
|
|
|
203
228
|
|
|
204
229
|
/**
|
|
205
230
|
* Key under the AG-UI request's `forwardedProps` carrying the answers to
|
|
206
|
-
* parked client tool calls on a continuation request.
|
|
231
|
+
* parked client tool calls on a continuation request. Cortex owns the
|
|
207
232
|
* transcript (a continuation sends `messages: []`), so the answered assistant
|
|
208
233
|
* message itself never crosses the wire — the SDK's park-boundary snapshot
|
|
209
234
|
* does not preserve its id, and round-tripping it would duplicate the stored
|
|
@@ -212,7 +237,7 @@ export function toUiMessage<TPart>(message: CortexMessage<TPart>) {
|
|
|
212
237
|
export const CLIENT_TOOL_ANSWERS_KEY = "clientToolAnswers";
|
|
213
238
|
|
|
214
239
|
/**
|
|
215
|
-
* One answered call,
|
|
240
|
+
* One answered call, which Cortex applies to the stored transcript: a client
|
|
216
241
|
* tool's output (`complete` / `error`), or the user's decision on a server
|
|
217
242
|
* tool that asked for approval (`approved` / `denied`, output null).
|
|
218
243
|
*/
|
|
@@ -232,19 +257,15 @@ export function approvalIdOf(part: { id: string; approval?: { id: string } }) {
|
|
|
232
257
|
}
|
|
233
258
|
|
|
234
259
|
/**
|
|
235
|
-
* Response of `POST /chat/:threadId/tools/:toolCallId/initiate
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* widget only; it never reaches the LLM.
|
|
260
|
+
* Response of `POST /chat/:threadId/tools/:toolCallId/initiate` for a pending
|
|
261
|
+
* call stamped with an embed. The payload is for the widget only; it never
|
|
262
|
+
* reaches the LLM.
|
|
239
263
|
*/
|
|
240
|
-
export type ClientToolInitiateResult =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
surface: ToolSurface;
|
|
246
|
-
embedOrigin: string;
|
|
247
|
-
};
|
|
264
|
+
export type ClientToolInitiateResult = {
|
|
265
|
+
embedUrl: string;
|
|
266
|
+
surface: ToolSurface;
|
|
267
|
+
embedOrigin: string;
|
|
268
|
+
};
|
|
248
269
|
|
|
249
270
|
export type ThreadCreatedEvent = {
|
|
250
271
|
type: "thread:created";
|
|
@@ -261,14 +282,22 @@ export type ThreadTitleUpdatedEvent = {
|
|
|
261
282
|
payload: { thread: ThreadSummary };
|
|
262
283
|
};
|
|
263
284
|
|
|
285
|
+
/** Run `runId` took the thread; the `thread:run-finished` naming the same run ends it. */
|
|
264
286
|
export type ThreadRunStartedEvent = {
|
|
265
287
|
type: "thread:run-started";
|
|
266
|
-
payload: { thread: ThreadSummary };
|
|
288
|
+
payload: { thread: ThreadSummary; runId: string };
|
|
267
289
|
};
|
|
268
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Run `runId` ended, committed or not, and left the thread free, so it takes
|
|
293
|
+
* a new message now; whatever turn it committed is in the transcript. Sent
|
|
294
|
+
* only for a run announced as started, and never by one whose thread another
|
|
295
|
+
* run had already taken, so a finish naming a run other than the one a
|
|
296
|
+
* client saw start last is stale.
|
|
297
|
+
*/
|
|
269
298
|
export type ThreadRunFinishedEvent = {
|
|
270
299
|
type: "thread:run-finished";
|
|
271
|
-
payload: { thread: ThreadSummary };
|
|
300
|
+
payload: { thread: ThreadSummary; runId: string };
|
|
272
301
|
};
|
|
273
302
|
|
|
274
303
|
export type ThreadMessagesUpdatedEvent = {
|
|
@@ -10,7 +10,7 @@ export type OpenApiOperation = {
|
|
|
10
10
|
operationId: string;
|
|
11
11
|
summary?: string;
|
|
12
12
|
tags?: string[];
|
|
13
|
-
/**
|
|
13
|
+
/** Cortex's own semantics, so nothing here is inferred from names or prose. */
|
|
14
14
|
"x-destructive"?: true;
|
|
15
15
|
"x-publish-procedure"?: string;
|
|
16
16
|
"x-live-fields"?: string[];
|
|
@@ -29,7 +29,8 @@ export type JsonSchema = {
|
|
|
29
29
|
properties?: Record<string, JsonSchema>;
|
|
30
30
|
required?: string[];
|
|
31
31
|
enum?: unknown[];
|
|
32
|
-
|
|
32
|
+
/** `false` on a tuple, whose positions `prefixItems` lists instead. */
|
|
33
|
+
items?: JsonSchema | boolean;
|
|
33
34
|
anyOf?: JsonSchema[];
|
|
34
35
|
default?: unknown;
|
|
35
36
|
description?: string;
|
|
@@ -45,7 +46,7 @@ export type OpenApiDocument = {
|
|
|
45
46
|
type Cached = { etag: string; document: OpenApiDocument };
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
|
-
* One cache entry per
|
|
49
|
+
* One cache entry per Cortex base URL (scheme, host, port, path),
|
|
49
50
|
* so an http and an https profile, or two deployments under one host, never
|
|
50
51
|
* read each other's document. The host keeps the file recognizable; a hash
|
|
51
52
|
* of the whole URL keeps two URLs from ever sharing a name.
|
|
@@ -85,7 +86,7 @@ const SCHEMA_FIELDS = {
|
|
|
85
86
|
properties: optional(recordOf(isJsonSchema)),
|
|
86
87
|
required: optional(isStrings),
|
|
87
88
|
enum: optional(Array.isArray),
|
|
88
|
-
items: optional(isJsonSchema),
|
|
89
|
+
items: optional((v) => typeof v === "boolean" || isJsonSchema(v)),
|
|
89
90
|
anyOf: optional(arrayOf(isJsonSchema)),
|
|
90
91
|
description: optional(isString),
|
|
91
92
|
format: optional(isString),
|
|
@@ -207,7 +208,7 @@ export function inputSchemaOf(operation: OpenApiOperation) {
|
|
|
207
208
|
* string, one parameter per field, with a scalar written plainly and
|
|
208
209
|
* anything else JSON-encoded, which is how the document declares it. A
|
|
209
210
|
* mutation posts its input as the JSON body. A refusal throws
|
|
210
|
-
* `ManagementError` with the error body's code and
|
|
211
|
+
* `ManagementError` with the error body's code, message and detail.
|
|
211
212
|
*/
|
|
212
213
|
export async function callManagement(
|
|
213
214
|
profile: { url: string; key: string },
|
|
@@ -227,8 +228,17 @@ export async function callManagement(
|
|
|
227
228
|
});
|
|
228
229
|
const body: unknown = await response.json().catch(() => null);
|
|
229
230
|
if (!response.ok) {
|
|
230
|
-
const { code, message } = (body ?? {}) as {
|
|
231
|
-
|
|
231
|
+
const { code, message, cause } = (body ?? {}) as {
|
|
232
|
+
code?: string;
|
|
233
|
+
message?: string;
|
|
234
|
+
cause?: unknown;
|
|
235
|
+
};
|
|
236
|
+
throw new ManagementError(
|
|
237
|
+
response.status,
|
|
238
|
+
code ?? "error",
|
|
239
|
+
message ?? response.statusText,
|
|
240
|
+
isRecord(cause) ? cause : undefined,
|
|
241
|
+
);
|
|
232
242
|
}
|
|
233
243
|
return body;
|
|
234
244
|
}
|
|
@@ -251,11 +261,13 @@ function queryString(input: Record<string, unknown>) {
|
|
|
251
261
|
return encoded === "" ? "" : `?${encoded}`;
|
|
252
262
|
}
|
|
253
263
|
|
|
264
|
+
/** A refusal; `detail` is what the refusal names, when it names something (an entry, the row holding a name). */
|
|
254
265
|
export class ManagementError extends Error {
|
|
255
266
|
public constructor(
|
|
256
267
|
public readonly status: number,
|
|
257
268
|
public readonly code: string,
|
|
258
269
|
message: string,
|
|
270
|
+
public readonly detail?: Record<string, unknown>,
|
|
259
271
|
) {
|
|
260
272
|
super(message);
|
|
261
273
|
this.name = "ManagementError";
|
|
@@ -44,7 +44,8 @@ function kindOf(property: JsonSchema, document: OpenApiDocument) {
|
|
|
44
44
|
if (type === "number" || type === "integer") return "number" as const;
|
|
45
45
|
if (type === "boolean") return "boolean" as const;
|
|
46
46
|
if (type === "array") {
|
|
47
|
-
const item =
|
|
47
|
+
const item =
|
|
48
|
+
typeof property.items === "object" ? resolve(property.items, document) : undefined;
|
|
48
49
|
const scalar = item && ["string", "number", "integer"].includes(typeOf(item) ?? "");
|
|
49
50
|
return scalar ? ("array" as const) : ("json" as const);
|
|
50
51
|
}
|
|
@@ -7,9 +7,9 @@ import { isRecord } from "@cortex/contracts/json";
|
|
|
7
7
|
type Profile = { url: string; key: string };
|
|
8
8
|
type Store = { current: string; profiles: Record<string, Profile> };
|
|
9
9
|
|
|
10
|
-
/** `~/.cortex/
|
|
10
|
+
/** `~/.cortex/profiles.json`; `CORTEX_HOME` relocates it (tests, CI). */
|
|
11
11
|
export function profilesPath() {
|
|
12
|
-
return join(process.env["CORTEX_HOME"] ?? join(homedir(), ".cortex"), "
|
|
12
|
+
return join(process.env["CORTEX_HOME"] ?? join(homedir(), ".cortex"), "profiles.json");
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** Thrown when the profile file exists but is not one this CLI wrote. */
|
|
@@ -87,13 +87,13 @@ export function removeProfile(name: string) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* The profile a
|
|
90
|
+
* The profile a management command runs against: `CORTEX_URL` + `CORTEX_KEY`
|
|
91
91
|
* when both are set (CI, containers), else the named or current entry from
|
|
92
92
|
* the file. `undefined` means nobody has logged in.
|
|
93
93
|
*/
|
|
94
94
|
export function resolveProfile(name?: string) {
|
|
95
|
-
const url = process.env["
|
|
96
|
-
const key = process.env["
|
|
95
|
+
const url = process.env["CORTEX_URL"];
|
|
96
|
+
const key = process.env["CORTEX_KEY"];
|
|
97
97
|
if (url && key) return { name: "env", url: url.replace(/\/+$/, ""), key };
|
|
98
98
|
const store = readStore();
|
|
99
99
|
const chosen = name ?? store.current;
|