@m6d/cortex-cli 1.7.0 → 1.9.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/package.json
CHANGED
|
@@ -1,47 +1,82 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The grammar and rendering pipeline for user-authored rich text that reaches
|
|
3
|
-
* the LLM (agent prompts, service descriptions). Both sides of
|
|
4
|
-
* it: the console edits and validates these strings, the
|
|
5
|
-
* pre-renders them (mentions + heading demotion), and
|
|
6
|
-
* applies per-request variable values.
|
|
3
|
+
* the LLM (agent prompts, service descriptions, skill bodies). Both sides of
|
|
4
|
+
* the wire share it: the console edits and validates these strings, the
|
|
5
|
+
* console's runtime API pre-renders them (mentions + heading demotion), and
|
|
6
|
+
* `@m6d/cortex-server` applies per-request variable values. Three token
|
|
7
|
+
* grammars live here:
|
|
7
8
|
*
|
|
8
|
-
* - Tool mentions `@[name](uuid)`
|
|
9
|
-
* is only a display cache refreshed
|
|
10
|
-
* resolved live when text is
|
|
9
|
+
* - Tool mentions `@[name](uuid)` and skill mentions `/[name](uuid)` — the
|
|
10
|
+
* uuid is authoritative; the inline name is only a display cache refreshed
|
|
11
|
+
* on save ({@link normalizeMentions}) and resolved live when text is
|
|
12
|
+
* rendered for the LLM ({@link renderMentions}).
|
|
11
13
|
* - Variables `{{name}}` — validated against a known list at authoring time
|
|
12
14
|
* ({@link analyzeVariables}) and substituted per request on the server
|
|
13
15
|
* ({@link substituteVariables}).
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const UUID = String.raw`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`;
|
|
19
|
+
|
|
20
|
+
/** One tool mention token, unanchored and flagless — the editor's tokenizer anchors it, the helpers below add `g`. */
|
|
21
|
+
export const MENTION_PATTERN_SOURCE = String.raw`@\[([^\]\n]*)\]\((${UUID})\)`;
|
|
22
|
+
/** One skill mention token; `/` is the slash-command convention skills already carry elsewhere. */
|
|
23
|
+
export const SKILL_MENTION_PATTERN_SOURCE = String.raw`/\[([^\]\n]*)\]\((${UUID})\)`;
|
|
18
24
|
|
|
19
25
|
const MENTION_PATTERN = new RegExp(MENTION_PATTERN_SOURCE, "g");
|
|
26
|
+
const SKILL_MENTION_PATTERN = new RegExp(SKILL_MENTION_PATTERN_SOURCE, "g");
|
|
20
27
|
|
|
21
28
|
const VARIABLE_PATTERN = /{{\s*([^{}]*?)\s*}}/g;
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
/** Entity names by id for the two mention grammars; a missing map leaves that grammar's cached names in place. */
|
|
31
|
+
export type MentionNames = {
|
|
32
|
+
tools?: ReadonlyMap<string, string>;
|
|
33
|
+
skills?: ReadonlyMap<string, string>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const NO_NAMES: ReadonlyMap<string, string> = new Map();
|
|
37
|
+
|
|
38
|
+
function extractIds(text: string, pattern: RegExp) {
|
|
24
39
|
const ids: string[] = [];
|
|
25
|
-
for (const match of text.matchAll(
|
|
40
|
+
for (const match of text.matchAll(pattern)) {
|
|
26
41
|
const id = match[2];
|
|
27
42
|
if (id && !ids.includes(id)) ids.push(id);
|
|
28
43
|
}
|
|
29
44
|
return ids;
|
|
30
45
|
}
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
export function extractMentionedToolIds(text: string) {
|
|
48
|
+
return extractIds(text, MENTION_PATTERN);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function extractMentionedSkillIds(text: string) {
|
|
52
|
+
return extractIds(text, SKILL_MENTION_PATTERN);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Replaces each mention token with the entity's current name (falling back to the cached inline name). */
|
|
56
|
+
export function renderMentions(text: string, names: MentionNames) {
|
|
57
|
+
const tools = names.tools ?? NO_NAMES;
|
|
58
|
+
const skills = names.skills ?? NO_NAMES;
|
|
59
|
+
return text
|
|
60
|
+
.replace(MENTION_PATTERN, (_, cached: string, id: string) => tools.get(id) ?? cached)
|
|
61
|
+
.replace(
|
|
62
|
+
SKILL_MENTION_PATTERN,
|
|
63
|
+
(_, cached: string, id: string) => skills.get(id) ?? cached,
|
|
64
|
+
);
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
/** Refreshes stale display names inside stored text, keeping the tokens intact. */
|
|
40
|
-
export function normalizeMentions(text: string,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
68
|
+
export function normalizeMentions(text: string, names: MentionNames) {
|
|
69
|
+
const tools = names.tools ?? NO_NAMES;
|
|
70
|
+
const skills = names.skills ?? NO_NAMES;
|
|
71
|
+
return text
|
|
72
|
+
.replace(MENTION_PATTERN, (token, _cached: string, id: string) => {
|
|
73
|
+
const name = tools.get(id);
|
|
74
|
+
return name === undefined ? token : `@[${name}](${id})`;
|
|
75
|
+
})
|
|
76
|
+
.replace(SKILL_MENTION_PATTERN, (token, _cached: string, id: string) => {
|
|
77
|
+
const name = skills.get(id);
|
|
78
|
+
return name === undefined ? token : `/[${name}](${id})`;
|
|
79
|
+
});
|
|
45
80
|
}
|
|
46
81
|
|
|
47
82
|
/** Splits the `{{variables}}` referenced in a text into known and unknown ones. */
|
|
@@ -65,7 +100,7 @@ export function analyzeVariables<Variable extends string>(
|
|
|
65
100
|
|
|
66
101
|
/** Fills `{{variables}}` with per-request values; unknown names stay verbatim. */
|
|
67
102
|
export function substituteVariables(text: string, values: Record<string, string>) {
|
|
68
|
-
return text.replace(
|
|
103
|
+
return text.replace(VARIABLE_PATTERN, (match, name: string) => values[name] ?? match);
|
|
69
104
|
}
|
|
70
105
|
|
|
71
106
|
/**
|
|
@@ -164,23 +199,21 @@ export function demoteHeadings(text: string) {
|
|
|
164
199
|
}
|
|
165
200
|
|
|
166
201
|
/** The one render step for LLM-bound rich text: resolve mentions, then demote headings. */
|
|
167
|
-
export function renderRichText(text: string,
|
|
168
|
-
return demoteHeadings(renderMentions(text,
|
|
202
|
+
export function renderRichText(text: string, names: MentionNames) {
|
|
203
|
+
return demoteHeadings(renderMentions(text, names));
|
|
169
204
|
}
|
|
170
205
|
|
|
171
206
|
/**
|
|
172
|
-
* Reduces rich text to plain prose for embeddings: mentions become
|
|
173
|
-
* (cached inline names when no map is given) and markdown syntax is
|
|
207
|
+
* Reduces rich text to plain prose for embeddings: mentions become entity
|
|
208
|
+
* names (cached inline names when no map is given) and markdown syntax is
|
|
209
|
+
* dropped.
|
|
174
210
|
* ponytail: a regex stripper, not a markdown parser — nested emphasis or exotic
|
|
175
211
|
* constructs may leave residue; swap in a real parser if retrieval quality
|
|
176
212
|
* ever shows it.
|
|
177
213
|
*/
|
|
178
|
-
export function stripToPlaintext(
|
|
179
|
-
text: string,
|
|
180
|
-
toolNameById: ReadonlyMap<string, string> = new Map(),
|
|
181
|
-
) {
|
|
214
|
+
export function stripToPlaintext(text: string, names: MentionNames = {}) {
|
|
182
215
|
const lineState = fenceTracker();
|
|
183
|
-
return renderMentions(text,
|
|
216
|
+
return renderMentions(text, names)
|
|
184
217
|
.split("\n")
|
|
185
218
|
.filter((line) => {
|
|
186
219
|
const state = lineState(line);
|
|
@@ -13,42 +13,22 @@ export const AGENT_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
|
13
13
|
/** Tool names become `tools.<name>()` sandbox bindings on the server. */
|
|
14
14
|
export const TOOL_NAME_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
15
15
|
|
|
16
|
-
export const PROMPT_VARIABLES = ["userName", "channel", "locale", "utcTime", "timezone"] as const;
|
|
17
|
-
|
|
18
16
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* key. Descriptions are plain English on purpose: variables are code-like
|
|
24
|
-
* identifiers shown in an LTR suggestion list, not localized UI copy.
|
|
17
|
+
* A variable the server fills per request: a session value, a request-context
|
|
18
|
+
* value, or one the SDK computes. Declared per agent so the console offers
|
|
19
|
+
* exactly what that agent's server provides, in prompts and as tool input
|
|
20
|
+
* sources alike.
|
|
25
21
|
*/
|
|
26
|
-
export const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
description: "Resolved locale of the session",
|
|
37
|
-
example: "ar",
|
|
38
|
-
},
|
|
39
|
-
// The server computes utcTime per request (a consumer-supplied value
|
|
40
|
-
// still wins); timezone has no server default — only the consumer knows
|
|
41
|
-
// the session's zone, so it must arrive via session/request context or
|
|
42
|
-
// the placeholder stays literal.
|
|
43
|
-
utcTime: {
|
|
44
|
-
description: "Current time in UTC (ISO 8601)",
|
|
45
|
-
example: "2026-08-16T09:30:00Z",
|
|
46
|
-
},
|
|
47
|
-
timezone: {
|
|
48
|
-
description: "IANA time zone of the session",
|
|
49
|
-
example: "Asia/Dubai",
|
|
50
|
-
},
|
|
51
|
-
} satisfies Record<(typeof PROMPT_VARIABLES)[number], { description: string; example: string }>;
|
|
22
|
+
export const variableDeclarationSchema = z.object({
|
|
23
|
+
name: z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(/^[A-Za-z_$][A-Za-z0-9_$]*$/)
|
|
26
|
+
.max(64),
|
|
27
|
+
description: z.string().max(500),
|
|
28
|
+
example: z.string().max(200),
|
|
29
|
+
/** Computed by the SDK per request (`utcTime`, `locale`); no session value can set it. */
|
|
30
|
+
computed: z.boolean().default(false),
|
|
31
|
+
});
|
|
52
32
|
|
|
53
33
|
export const RUNTIME_ERROR_KINDS = [
|
|
54
34
|
"timeout",
|
|
@@ -101,6 +81,13 @@ export const toolEmbedSchema = z.object({
|
|
|
101
81
|
embedOrigin: z.url(),
|
|
102
82
|
});
|
|
103
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Variables a tool's input reads (`x-source` of kind `variable` on an input
|
|
86
|
+
* property). The server fills those fields from the agent's declared
|
|
87
|
+
* variables at execute time and the model never sees them.
|
|
88
|
+
*/
|
|
89
|
+
const boundVariablesSchema = z.array(variableDeclarationSchema.shape.name);
|
|
90
|
+
|
|
104
91
|
export const toolSignatureSchema = z.object({
|
|
105
92
|
toolId: z.uuid(),
|
|
106
93
|
name: z.string().regex(TOOL_NAME_PATTERN),
|
|
@@ -110,6 +97,7 @@ export const toolSignatureSchema = z.object({
|
|
|
110
97
|
signature: z.string(),
|
|
111
98
|
score: z.number().nullable(),
|
|
112
99
|
pinned: z.boolean(),
|
|
100
|
+
variables: boundVariablesSchema,
|
|
113
101
|
embed: toolEmbedSchema.optional(),
|
|
114
102
|
/** Client tools only: the published input JSON Schema, verbatim.
|
|
115
103
|
* Client-executed declarations need a real schema — the rendered
|
|
@@ -117,19 +105,228 @@ export const toolSignatureSchema = z.object({
|
|
|
117
105
|
inputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
118
106
|
});
|
|
119
107
|
|
|
108
|
+
/** Consumer-facing slugs: service keys and skill names share it, the model types both. */
|
|
109
|
+
export const SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* One always-listed skill line. The description is the whole trigger: the
|
|
113
|
+
* model reads it every turn and calls `loadSkill` when it matches the task.
|
|
114
|
+
*/
|
|
115
|
+
export const skillLineSchema = z.object({
|
|
116
|
+
name: z.string().regex(SLUG_PATTERN).max(64),
|
|
117
|
+
description: z.string().max(500),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
120
|
export const serviceCardSchema = z.object({
|
|
121
121
|
serviceId: z.uuid(),
|
|
122
|
-
key: z
|
|
123
|
-
.string()
|
|
124
|
-
.regex(/^[a-z0-9]+(-[a-z0-9]+)*$/)
|
|
125
|
-
.max(64),
|
|
122
|
+
key: z.string().regex(SLUG_PATTERN).max(64),
|
|
126
123
|
name: z.string(),
|
|
127
124
|
card: z.string(),
|
|
128
125
|
score: z.number(),
|
|
126
|
+
/** Skills scoped to this service; listed only while the card is in context. Defaulted so an older Control Center still parses. */
|
|
127
|
+
skills: z.array(skillLineSchema).default([]),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A skill body served on `loadSkill`: mentions rendered to current names and
|
|
132
|
+
* headings demoted by the console, `{{variables}}` left for the server to
|
|
133
|
+
* fill per request. `mentionedTools` are the sandbox bindings the body
|
|
134
|
+
* refers to, so the turn can register them before the model acts on it.
|
|
135
|
+
*/
|
|
136
|
+
/**
|
|
137
|
+
* Opaque handle the Control Center assigns a resolved agent. Servers echo it
|
|
138
|
+
* on reports about that agent so they attach to it even if the slug or alias
|
|
139
|
+
* they used later moves.
|
|
140
|
+
*/
|
|
141
|
+
export const agentRefSchema = z.string().max(128);
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Response header carrying the resolved agent's ref on every agent-scoped
|
|
145
|
+
* runtime answer, errors included, so a server can attribute a request even
|
|
146
|
+
* when the body is an error envelope. Absent only when no agent resolved.
|
|
147
|
+
*/
|
|
148
|
+
export const AGENT_REF_HEADER = "X-CC-Agent-Ref";
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The playground operator's stand-in for a test thread's session: a
|
|
152
|
+
* URI-encoded JSON object of declared variable values, sent by cc's
|
|
153
|
+
* playground beside `X-Cortex-Playground` and honoured only when that header
|
|
154
|
+
* verifies. The server stores it as the thread's session on creation, so the
|
|
155
|
+
* customer's `loadSessionData` (which never sees cc-minted tokens) is not
|
|
156
|
+
* needed for prompts and tool sources to have values.
|
|
157
|
+
*/
|
|
158
|
+
export const PLAYGROUND_SESSION_HEADER = "X-Cortex-Playground-Session";
|
|
159
|
+
|
|
160
|
+
/** A sandbox binding a piece of rich text mentions, so the turn can register it before the model reads the text. */
|
|
161
|
+
export const mentionedToolSchema = z.object({
|
|
162
|
+
toolId: z.uuid(),
|
|
163
|
+
name: z.string().regex(TOOL_NAME_PATTERN),
|
|
164
|
+
readOnly: z.boolean(),
|
|
165
|
+
variables: boundVariablesSchema,
|
|
129
166
|
});
|
|
130
167
|
|
|
168
|
+
export const skillBodyResponseSchema = z.object({
|
|
169
|
+
name: z.string(),
|
|
170
|
+
body: z.string(),
|
|
171
|
+
mentionedTools: z.array(mentionedToolSchema),
|
|
172
|
+
catalogVersion: catalogVersionSchema,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* Triggers: named entry points on an agent. A firing is one invocation with a
|
|
177
|
+
* payload and starts at most one run. The end user's message is the built-in
|
|
178
|
+
* `message` trigger; every other trigger is fired by an external caller over
|
|
179
|
+
* `POST /agents/:agentId/triggers/:name` with the trigger's own secret.
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
export const MESSAGE_TRIGGER_NAME = "message";
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The first characters of a fire bearer say what it is: the secret callers
|
|
186
|
+
* hold, or a one-shot test token the console minted. A test token is a JWT
|
|
187
|
+
* the Control Center signed, so a server verifies it against the Control
|
|
188
|
+
* Center's JWKS before saying a word to it, and the Control Center then
|
|
189
|
+
* consumes it so it fires once.
|
|
190
|
+
*/
|
|
191
|
+
export const TRIGGER_SECRET_PREFIXES = { live: "tr_live_", test: "tr_test_" } as const;
|
|
192
|
+
export const TRIGGER_TEST_TOKEN_AUDIENCE = "cortex:trigger-test";
|
|
193
|
+
|
|
194
|
+
/** The claims a test token carries beside the standard ones; the server checks them against the route it arrived on. */
|
|
195
|
+
export const triggerTestTokenClaimsSchema = z.object({
|
|
196
|
+
agent: agentSlugSchema,
|
|
197
|
+
trigger: z.string().regex(SLUG_PATTERN).max(64),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const sha256HexSchema = z.string().regex(/^[0-9a-f]{64}$/);
|
|
201
|
+
|
|
202
|
+
export const TRIGGER_OUTCOMES = [
|
|
203
|
+
"started",
|
|
204
|
+
"completed",
|
|
205
|
+
"failed",
|
|
206
|
+
"rejected",
|
|
207
|
+
"dropped",
|
|
208
|
+
"disabled",
|
|
209
|
+
] as const;
|
|
210
|
+
|
|
211
|
+
/** Why a firing was dropped: the thread was busy, or the daily budget was spent. */
|
|
212
|
+
export const TRIGGER_DROP_REASONS = ["busy", "budget"] as const;
|
|
213
|
+
|
|
214
|
+
export const TRIGGER_SOURCES = ["code", "cc"] as const;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Which thread a firing lands in. `single`: one thread for the trigger.
|
|
218
|
+
* `per-firing`: a fresh thread every time. `keyed`: one thread per distinct
|
|
219
|
+
* value at a JSON pointer into the payload (e.g. `/pull_request/number`).
|
|
220
|
+
*/
|
|
221
|
+
export const triggerThreadPolicySchema = z.discriminatedUnion("policy", [
|
|
222
|
+
z.object({ policy: z.literal("single") }),
|
|
223
|
+
z.object({ policy: z.literal("per-firing") }),
|
|
224
|
+
z.object({ policy: z.literal("keyed"), key: z.string().regex(/^\//).max(256) }),
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
/** A trigger as both sides describe it. Secrets and credentials never cross this contract. */
|
|
228
|
+
export const triggerDeclarationSchema = z.object({
|
|
229
|
+
name: z.string().regex(SLUG_PATTERN).max(64),
|
|
230
|
+
description: z.string().max(500),
|
|
231
|
+
/** JSON Schema for the payload; cc triggers are validated from it on the server. */
|
|
232
|
+
input: z.record(z.string(), z.unknown()),
|
|
233
|
+
/**
|
|
234
|
+
* The prompt the run starts from, mentions already rendered to names by
|
|
235
|
+
* the console; `{{variables}}` and `{{/json/pointer}}` payload fields
|
|
236
|
+
* are the server's to fill per firing.
|
|
237
|
+
*/
|
|
238
|
+
instructions: z.string().max(20_000),
|
|
239
|
+
/** Tools the instructions mention, registered as sandbox bindings for the run. Defaulted so an older Control Center still parses. */
|
|
240
|
+
mentionedTools: z.array(mentionedToolSchema).default([]),
|
|
241
|
+
thread: triggerThreadPolicySchema.default({ policy: "single" }),
|
|
242
|
+
budgetPerDay: z.number().int().positive().nullable().default(null),
|
|
243
|
+
/** Consecutive failed or rejected firings before the trigger disables itself. */
|
|
244
|
+
disableAfterFailures: z.number().int().positive().default(5),
|
|
245
|
+
hasCredential: z.boolean().default(false),
|
|
246
|
+
/**
|
|
247
|
+
* sha256 of the fire secret, so a server checks a presented bearer in
|
|
248
|
+
* process; the Control Center hears only about console test secrets,
|
|
249
|
+
* which it alone can consume.
|
|
250
|
+
*/
|
|
251
|
+
secretHash: sha256HexSchema,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* What a server runs for one agent, sent whole at boot in `agent.declare`. An
|
|
256
|
+
* agent exists in the Control Center only through a declaration, and only
|
|
257
|
+
* its declaring server may serve it. Everything the console validates
|
|
258
|
+
* against (variables, code skills, code triggers) is here rather than
|
|
259
|
+
* assumed.
|
|
260
|
+
*/
|
|
261
|
+
export const agentDeclarationSchema = z.object({
|
|
262
|
+
slug: agentSlugSchema,
|
|
263
|
+
variables: z.array(variableDeclarationSchema),
|
|
264
|
+
skills: z.array(skillLineSchema),
|
|
265
|
+
triggers: z.array(
|
|
266
|
+
triggerDeclarationSchema.pick({ name: true, description: true, input: true, thread: true }),
|
|
267
|
+
),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
export const triggerFiringResponseSchema = z.discriminatedUnion("outcome", [
|
|
271
|
+
z.object({
|
|
272
|
+
firingId: z.uuid(),
|
|
273
|
+
outcome: z.literal("started"),
|
|
274
|
+
threadId: z.uuid(),
|
|
275
|
+
runId: z.string(),
|
|
276
|
+
}),
|
|
277
|
+
z.object({
|
|
278
|
+
firingId: z.uuid(),
|
|
279
|
+
outcome: z.literal("dropped"),
|
|
280
|
+
reason: z.enum(TRIGGER_DROP_REASONS),
|
|
281
|
+
threadId: z.uuid().nullable(),
|
|
282
|
+
}),
|
|
283
|
+
z.object({ firingId: z.uuid(), outcome: z.literal("disabled") }),
|
|
284
|
+
z.object({
|
|
285
|
+
firingId: z.uuid(),
|
|
286
|
+
outcome: z.literal("rejected"),
|
|
287
|
+
issues: z.array(z.object({ path: z.string(), message: z.string() })),
|
|
288
|
+
}),
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
export const triggerFiringSchema = z.object({
|
|
292
|
+
id: z.uuid(),
|
|
293
|
+
triggerName: z.string(),
|
|
294
|
+
threadId: z.uuid().nullable(),
|
|
295
|
+
runId: z.string().nullable(),
|
|
296
|
+
outcome: z.enum(TRIGGER_OUTCOMES),
|
|
297
|
+
reason: z.string().nullable(),
|
|
298
|
+
createdAt: z.iso.datetime(),
|
|
299
|
+
finishedAt: z.iso.datetime().nullable(),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
export const triggerFiringListResponseSchema = z.object({
|
|
303
|
+
firings: z.array(triggerFiringSchema),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* `POST /api/runtime/agents/:id/triggers/:name/auth`: a server holding a
|
|
308
|
+
* bearer for a cc trigger asks cc whether it is the trigger's secret. Only the
|
|
309
|
+
* hash travels; a positive answer carries the trigger's credential, if any.
|
|
310
|
+
*/
|
|
311
|
+
export const triggerAuthRequestSchema = z.object({
|
|
312
|
+
secretHash: sha256HexSchema,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
export const triggerAuthResponseSchema = z.discriminatedUnion("ok", [
|
|
316
|
+
z.object({
|
|
317
|
+
ok: z.literal(true),
|
|
318
|
+
credential: z.string().nullable(),
|
|
319
|
+
/** The bearer was a single-use test secret, now spent: the server must not cache this verdict. */
|
|
320
|
+
oneShot: z.boolean().optional(),
|
|
321
|
+
}),
|
|
322
|
+
z.object({ ok: z.literal(false) }),
|
|
323
|
+
]);
|
|
324
|
+
|
|
131
325
|
export const runtimeAgentConfigSchema = z.object({
|
|
326
|
+
/** The slug the request used: the agent's own or one of the server's aliases. */
|
|
132
327
|
agentId: agentSlugSchema,
|
|
328
|
+
/** The agent's own slug, the key of its `agents` entry in server code. Equals `agentId` unless an alias was used. */
|
|
329
|
+
slug: agentSlugSchema,
|
|
133
330
|
/**
|
|
134
331
|
* End-user JWT verification for this agent, managed by the agent's owner.
|
|
135
332
|
* Tokens must be asymmetrically signed (the JWKS URI serves the public
|
|
@@ -147,7 +344,8 @@ export const runtimeAgentConfigSchema = z.object({
|
|
|
147
344
|
})
|
|
148
345
|
.optional(),
|
|
149
346
|
systemPrompt: z.string(),
|
|
150
|
-
|
|
347
|
+
/** Every declared variable the published prompt, skills, or triggers reference. */
|
|
348
|
+
promptVariables: z.array(variableDeclarationSchema.shape.name),
|
|
151
349
|
defaultLocale: localeSchema,
|
|
152
350
|
/**
|
|
153
351
|
* Whether end users may upload files to this agent. Operational config
|
|
@@ -166,6 +364,14 @@ export const runtimeAgentConfigSchema = z.object({
|
|
|
166
364
|
executeTimeoutMs: z.number().int().positive(),
|
|
167
365
|
maxResponseBytes: z.number().int().positive(),
|
|
168
366
|
}),
|
|
367
|
+
/** Unscoped published skills; service-scoped ones travel on their card. Defaulted so an older Control Center still parses. */
|
|
368
|
+
skills: z.array(skillLineSchema).default([]),
|
|
369
|
+
/** Published triggers. Defaulted so an older Control Center still parses. */
|
|
370
|
+
triggers: z.array(triggerDeclarationSchema).default([]),
|
|
371
|
+
/** Whether end users may message this agent; false makes it headless. */
|
|
372
|
+
messageTrigger: z.boolean().default(true),
|
|
373
|
+
/** Absent from older Control Centers; reports then carry only the slug. */
|
|
374
|
+
agentRef: agentRefSchema.optional(),
|
|
169
375
|
catalogVersion: catalogVersionSchema,
|
|
170
376
|
publishedAt: z.iso.datetime(),
|
|
171
377
|
});
|
|
@@ -238,6 +444,8 @@ export const executeRequestSchema = z.object({
|
|
|
238
444
|
threadId: z.string().max(128).optional(),
|
|
239
445
|
userId: z.string().max(128).optional(),
|
|
240
446
|
locale: localeSchema.optional(),
|
|
447
|
+
/** Values of the variables the tool's input reads; only those names travel. */
|
|
448
|
+
variables: z.record(z.string(), z.unknown()).optional(),
|
|
241
449
|
})
|
|
242
450
|
.optional(),
|
|
243
451
|
});
|
|
@@ -299,7 +507,8 @@ export const tokenUsageSchema = z.object({
|
|
|
299
507
|
total: tokenCountSchema,
|
|
300
508
|
});
|
|
301
509
|
|
|
302
|
-
|
|
510
|
+
/** `trigger` rows hold a firing's rendered instructions; the model sees them as `user` turns. */
|
|
511
|
+
export const MESSAGE_ROLES = ["system", "user", "assistant", "tool", "trigger"] as const;
|
|
303
512
|
|
|
304
513
|
export const syncEventSchema = z.discriminatedUnion("type", [
|
|
305
514
|
z.object({
|
|
@@ -309,6 +518,9 @@ export const syncEventSchema = z.discriminatedUnion("type", [
|
|
|
309
518
|
userId: z.string().max(255),
|
|
310
519
|
title: z.string().max(256).nullable(),
|
|
311
520
|
isTest: z.boolean(),
|
|
521
|
+
/** Set on headless threads a trigger owns; `userId` is then a sentinel. */
|
|
522
|
+
triggerName: z.string().max(64).nullable().default(null),
|
|
523
|
+
triggerKey: z.string().max(256).nullable().default(null),
|
|
312
524
|
createdAt: z.iso.datetime(),
|
|
313
525
|
updatedAt: z.iso.datetime(),
|
|
314
526
|
}),
|
|
@@ -337,6 +549,48 @@ export const syncEventSchema = z.discriminatedUnion("type", [
|
|
|
337
549
|
// column, but the mirror needs one for time-bucketed usage stats.
|
|
338
550
|
createdAt: z.iso.datetime(),
|
|
339
551
|
}),
|
|
552
|
+
// One loadSkill call, hit or miss, from either skill source. Bounded by
|
|
553
|
+
// construction (no body travels), so it can never trip the size cap.
|
|
554
|
+
z.object({
|
|
555
|
+
type: z.literal("skill.load"),
|
|
556
|
+
id: z.uuid(),
|
|
557
|
+
threadId: z.uuid(),
|
|
558
|
+
agentSlug: agentSlugSchema,
|
|
559
|
+
/** The ref cc issued for the agent that answered; a code-only agent or a miss has none. */
|
|
560
|
+
agentRef: agentRefSchema.optional(),
|
|
561
|
+
/** As the model asked for it, even when nothing answered. */
|
|
562
|
+
skillName: z.string().max(64),
|
|
563
|
+
source: z.enum(["code", "cc"]).nullable(),
|
|
564
|
+
hit: z.boolean(),
|
|
565
|
+
/** The user message this turn answers; the mirror joins it for the drill-down. */
|
|
566
|
+
messageId: z.string().max(128),
|
|
567
|
+
createdAt: z.iso.datetime(),
|
|
568
|
+
}),
|
|
569
|
+
// One firing, upserted on start and again on its terminal outcome; the
|
|
570
|
+
// firing id is the event id so both land on one mirror row.
|
|
571
|
+
z.object({
|
|
572
|
+
type: z.literal("trigger.firing"),
|
|
573
|
+
id: z.uuid(),
|
|
574
|
+
agentSlug: agentSlugSchema,
|
|
575
|
+
agentRef: agentRefSchema.optional(),
|
|
576
|
+
triggerName: z.string().max(64),
|
|
577
|
+
source: z.enum(TRIGGER_SOURCES),
|
|
578
|
+
threadId: z.uuid().nullable(),
|
|
579
|
+
runId: z.string().max(64).nullable(),
|
|
580
|
+
outcome: z.enum(TRIGGER_OUTCOMES),
|
|
581
|
+
reason: z.string().max(64).nullable(),
|
|
582
|
+
createdAt: z.iso.datetime(),
|
|
583
|
+
finishedAt: z.iso.datetime().nullable(),
|
|
584
|
+
}),
|
|
585
|
+
// Everything the server runs, sent whole at boot. Replaces the server's
|
|
586
|
+
// previous declaration, so an agent removed from code leaves cc with its
|
|
587
|
+
// skills and triggers.
|
|
588
|
+
z.object({
|
|
589
|
+
type: z.literal("agent.declare"),
|
|
590
|
+
id: z.uuid(),
|
|
591
|
+
agents: z.array(agentDeclarationSchema),
|
|
592
|
+
createdAt: z.iso.datetime(),
|
|
593
|
+
}),
|
|
340
594
|
]);
|
|
341
595
|
|
|
342
596
|
/*
|
|
@@ -372,6 +626,20 @@ export type ToolEmbed = z.infer<typeof toolEmbedSchema>;
|
|
|
372
626
|
export type RuntimeAgentConfig = z.infer<typeof runtimeAgentConfigSchema>;
|
|
373
627
|
export type ResolveRequest = z.input<typeof resolveRequestSchema>;
|
|
374
628
|
export type ResolveResponse = z.infer<typeof resolveResponseSchema>;
|
|
629
|
+
export type SkillLine = z.infer<typeof skillLineSchema>;
|
|
630
|
+
export type SkillBodyResponse = z.infer<typeof skillBodyResponseSchema>;
|
|
631
|
+
export type TriggerDeclaration = z.infer<typeof triggerDeclarationSchema>;
|
|
632
|
+
export type AgentDeclaration = z.infer<typeof agentDeclarationSchema>;
|
|
633
|
+
export type VariableDeclaration = z.infer<typeof variableDeclarationSchema>;
|
|
634
|
+
export type TriggerThreadPolicy = z.infer<typeof triggerThreadPolicySchema>;
|
|
635
|
+
export type TriggerOutcome = (typeof TRIGGER_OUTCOMES)[number];
|
|
636
|
+
export type TriggerDropReason = (typeof TRIGGER_DROP_REASONS)[number];
|
|
637
|
+
export type TriggerSource = (typeof TRIGGER_SOURCES)[number];
|
|
638
|
+
export type TriggerFiringResponse = z.infer<typeof triggerFiringResponseSchema>;
|
|
639
|
+
export type TriggerFiring = z.infer<typeof triggerFiringSchema>;
|
|
640
|
+
export type TriggerFiringListResponse = z.infer<typeof triggerFiringListResponseSchema>;
|
|
641
|
+
export type TriggerAuthRequest = z.infer<typeof triggerAuthRequestSchema>;
|
|
642
|
+
export type TriggerAuthResponse = z.infer<typeof triggerAuthResponseSchema>;
|
|
375
643
|
export type SearchRequest = z.input<typeof searchRequestSchema>;
|
|
376
644
|
export type ExecuteRequest = z.input<typeof executeRequestSchema>;
|
|
377
645
|
export type SyncEvent = z.infer<typeof syncEventSchema>;
|
|
@@ -21,6 +21,9 @@ export const MAX_ATTACHMENTS_PER_MESSAGE = 5;
|
|
|
21
21
|
|
|
22
22
|
export const ATTACHMENT_STATUSES = ["pending", "described", "failed", "skipped"] as const;
|
|
23
23
|
|
|
24
|
+
/** Who put the file in the thread: the user through the composer, or a tool mid-turn. */
|
|
25
|
+
export const ATTACHMENT_ORIGINS = ["upload", "tool"] as const;
|
|
26
|
+
|
|
24
27
|
export function checkAttachmentPolicy(contentType: string, sizeBytes: number) {
|
|
25
28
|
if (!ATTACHMENT_MIME_TYPES.some((type) => type === contentType)) return "type";
|
|
26
29
|
if (sizeBytes > ATTACHMENT_MAX_BYTES) return "size";
|
|
@@ -29,12 +32,15 @@ export function checkAttachmentPolicy(contentType: string, sizeBytes: number) {
|
|
|
29
32
|
|
|
30
33
|
export type AttachmentStatus = (typeof ATTACHMENT_STATUSES)[number];
|
|
31
34
|
|
|
35
|
+
export type AttachmentOrigin = (typeof ATTACHMENT_ORIGINS)[number];
|
|
36
|
+
|
|
32
37
|
export type AttachmentSummary = {
|
|
33
38
|
id: string;
|
|
34
39
|
filename: string;
|
|
35
40
|
contentType: string;
|
|
36
41
|
sizeBytes: number;
|
|
37
42
|
status: AttachmentStatus;
|
|
43
|
+
origin: AttachmentOrigin;
|
|
38
44
|
description?: string;
|
|
39
45
|
};
|
|
40
46
|
|
|
@@ -44,6 +50,8 @@ export type ThreadSummary = {
|
|
|
44
50
|
createdAt: string;
|
|
45
51
|
updatedAt: string;
|
|
46
52
|
isRunning: boolean;
|
|
53
|
+
/** Present on a headless thread a trigger owns. Widgets never list these. */
|
|
54
|
+
trigger?: { name: string };
|
|
47
55
|
};
|
|
48
56
|
|
|
49
57
|
/**
|
|
@@ -79,6 +87,8 @@ export type ClientToolBinding = {
|
|
|
79
87
|
toolId: string;
|
|
80
88
|
surface: "inline" | "modal";
|
|
81
89
|
embedOrigin: string;
|
|
90
|
+
/** Variables the initiate call's input reads. */
|
|
91
|
+
variables: string[];
|
|
82
92
|
};
|
|
83
93
|
|
|
84
94
|
/**
|
|
@@ -87,6 +97,8 @@ export type ClientToolBinding = {
|
|
|
87
97
|
*/
|
|
88
98
|
export type AgentCapabilities = {
|
|
89
99
|
attachments: boolean;
|
|
100
|
+
/** False when the agent's `message` trigger is off: the widget hides its composer. */
|
|
101
|
+
messages: boolean;
|
|
90
102
|
};
|
|
91
103
|
|
|
92
104
|
export type MessageMetadata = {
|
|
@@ -96,6 +108,15 @@ export type MessageMetadata = {
|
|
|
96
108
|
tokenUsage?: TokenUsage;
|
|
97
109
|
attachments?: AttachmentSummary[];
|
|
98
110
|
clientTools?: Record<string, ClientToolBinding>;
|
|
111
|
+
/** Set on `trigger` messages: the firing that wrote them. */
|
|
112
|
+
trigger?: TriggerFiringSummary;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type TriggerFiringSummary = {
|
|
116
|
+
name: string;
|
|
117
|
+
firingId: string;
|
|
118
|
+
firedAt: string;
|
|
119
|
+
payload: unknown;
|
|
99
120
|
};
|
|
100
121
|
|
|
101
122
|
/**
|
|
@@ -108,7 +129,8 @@ export type MessageMetadata = {
|
|
|
108
129
|
*/
|
|
109
130
|
export type CortexMessage<TPart = unknown> = {
|
|
110
131
|
id: string;
|
|
111
|
-
|
|
132
|
+
/** `trigger` rows carry a firing's instructions; the UIs render them as an event, not a bubble. */
|
|
133
|
+
role: "system" | "user" | "assistant" | "trigger";
|
|
112
134
|
parts: TPart[];
|
|
113
135
|
metadata?: MessageMetadata;
|
|
114
136
|
};
|