@m6d/cortex-server 2.0.0 → 2.0.1
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/contracts/rich-text.ts +207 -0
- package/contracts/runtime.ts +36 -1
- package/dist/contracts/rich-text.d.ts +32 -0
- package/dist/contracts/runtime.d.ts +39 -7
- package/dist/src/lib/ai/cc-runtime.d.ts +3 -3
- package/dist/src/lib/ai/tools/search-common.d.ts +1 -1
- package/dist/src/lib/cc/client.d.ts +3 -3
- package/dist/src/lib/cc/config-cache.d.ts +2 -2
- package/package.json +1 -1
- package/src/lib/ai/index.ts +7 -0
- package/src/lib/cc/format.ts +3 -7
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The grammar and rendering pipeline for user-authored rich text that reaches
|
|
3
|
+
* the LLM (agent prompts, service descriptions). Both sides of the wire share
|
|
4
|
+
* it: the console edits and validates these strings, the console's runtime API
|
|
5
|
+
* pre-renders them (mentions + heading demotion), and `@m6d/cortex-server`
|
|
6
|
+
* applies per-request variable values. Two token grammars live here:
|
|
7
|
+
*
|
|
8
|
+
* - Tool mentions `@[name](uuid)` — the uuid is authoritative; the inline name
|
|
9
|
+
* is only a display cache refreshed on save ({@link normalizeMentions}) and
|
|
10
|
+
* resolved live when text is rendered for the LLM ({@link renderMentions}).
|
|
11
|
+
* - Variables `{{name}}` — validated against a known list at authoring time
|
|
12
|
+
* ({@link analyzeVariables}) and substituted per request on the server
|
|
13
|
+
* ({@link substituteVariables}).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** One mention token, unanchored and flagless — the editor's tokenizer anchors it, the helpers below add `g`. */
|
|
17
|
+
export const MENTION_PATTERN_SOURCE = String.raw`@\[([^\]\n]*)\]\(([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\)`;
|
|
18
|
+
|
|
19
|
+
const MENTION_PATTERN = new RegExp(MENTION_PATTERN_SOURCE, "g");
|
|
20
|
+
|
|
21
|
+
const VARIABLE_PATTERN = /{{\s*([^{}]*?)\s*}}/g;
|
|
22
|
+
|
|
23
|
+
export function extractMentionedToolIds(text: string) {
|
|
24
|
+
const ids: string[] = [];
|
|
25
|
+
for (const match of text.matchAll(MENTION_PATTERN)) {
|
|
26
|
+
const id = match[2];
|
|
27
|
+
if (id && !ids.includes(id)) ids.push(id);
|
|
28
|
+
}
|
|
29
|
+
return ids;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Replaces each mention token with the tool's current name (falling back to the cached inline name). */
|
|
33
|
+
export function renderMentions(text: string, toolNameById: ReadonlyMap<string, string>) {
|
|
34
|
+
return text.replace(MENTION_PATTERN, (_, cachedName: string, toolId: string) => {
|
|
35
|
+
return toolNameById.get(toolId) ?? cachedName;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Refreshes stale display names inside stored text, keeping the tokens intact. */
|
|
40
|
+
export function normalizeMentions(text: string, toolNameById: ReadonlyMap<string, string>) {
|
|
41
|
+
return text.replace(MENTION_PATTERN, (token, _cachedName: string, toolId: string) => {
|
|
42
|
+
const name = toolNameById.get(toolId);
|
|
43
|
+
return name === undefined ? token : `@[${name}](${toolId})`;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Splits the `{{variables}}` referenced in a text into known and unknown ones. */
|
|
48
|
+
export function analyzeVariables<Variable extends string>(
|
|
49
|
+
text: string,
|
|
50
|
+
known: ReadonlyArray<Variable>,
|
|
51
|
+
) {
|
|
52
|
+
const referenced = new Set(
|
|
53
|
+
Array.from(text.matchAll(VARIABLE_PATTERN), ([, name]) => name?.trim()),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
variables: known.filter((variable) => referenced.has(variable)),
|
|
58
|
+
unknownVariables: [...referenced].filter(
|
|
59
|
+
(variable): variable is string =>
|
|
60
|
+
variable !== undefined &&
|
|
61
|
+
!known.some((knownVariable) => knownVariable === variable),
|
|
62
|
+
),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Fills `{{variables}}` with per-request values; unknown names stay verbatim. */
|
|
67
|
+
export function substituteVariables(text: string, values: Record<string, string>) {
|
|
68
|
+
return text.replace(/\{\{(\w+)\}\}/g, (match, name: string) => values[name] ?? match);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Line-by-line fence state, following CommonMark's fence rules: a fence line
|
|
73
|
+
* is indented at most three spaces; an opening backtick fence's info string
|
|
74
|
+
* may not contain backticks; and a fence closes only on the same marker
|
|
75
|
+
* character, with at least the opening run's length, followed by nothing but
|
|
76
|
+
* whitespace — so a `~~~` line, a shorter same-marker run, or a ```` ```js ````
|
|
77
|
+
* info line inside a backtick fence all stay code content.
|
|
78
|
+
*/
|
|
79
|
+
function fenceTracker() {
|
|
80
|
+
let fence: { marker: string; length: number } | null = null;
|
|
81
|
+
return (line: string) => {
|
|
82
|
+
const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
83
|
+
const delimiter = match?.[1];
|
|
84
|
+
const rest = match?.[2] ?? "";
|
|
85
|
+
if (delimiter) {
|
|
86
|
+
const marker = delimiter.charAt(0);
|
|
87
|
+
if (fence) {
|
|
88
|
+
if (
|
|
89
|
+
marker === fence.marker &&
|
|
90
|
+
delimiter.length >= fence.length &&
|
|
91
|
+
rest.trim() === ""
|
|
92
|
+
) {
|
|
93
|
+
fence = null;
|
|
94
|
+
return "close";
|
|
95
|
+
}
|
|
96
|
+
return "inside";
|
|
97
|
+
}
|
|
98
|
+
if (marker === "`" && rest.includes("`")) return "outside";
|
|
99
|
+
fence = { marker, length: delimiter.length };
|
|
100
|
+
return "open";
|
|
101
|
+
}
|
|
102
|
+
return fence ? "inside" : "outside";
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** ATX heading, CommonMark-style: up to three leading spaces, `#` run closed by space or line end. */
|
|
107
|
+
const ATX_HEADING = /^( {0,3})(#{1,6})(?=[ \t]|$)/;
|
|
108
|
+
|
|
109
|
+
/** Setext underline: a run of `=` or `-` alone on its line, up to three leading spaces. */
|
|
110
|
+
const SETEXT_UNDERLINE = /^ {0,3}(=+|-+)[ \t]*$/;
|
|
111
|
+
|
|
112
|
+
/** The skeleton owns `##` and `###`, so user headings start three levels below it. */
|
|
113
|
+
function demotedHashes(level: number) {
|
|
114
|
+
return "#".repeat(Math.min(level + 3, 6));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether a line can be the text of a setext heading — i.e. it is an ordinary
|
|
119
|
+
* paragraph line, not a blank, a fence, another heading, or the start of a
|
|
120
|
+
* different block (quote/list), in which case the underline below it is a
|
|
121
|
+
* thematic break or list content rather than a heading marker.
|
|
122
|
+
*/
|
|
123
|
+
function isSetextText(line: string) {
|
|
124
|
+
return (
|
|
125
|
+
line.trim() !== "" &&
|
|
126
|
+
!ATX_HEADING.test(line) &&
|
|
127
|
+
!SETEXT_UNDERLINE.test(line) &&
|
|
128
|
+
!/^ {0,3}(`{3,}|~{3,})/.test(line) &&
|
|
129
|
+
!/^ {0,3}(>|[-*+][ \t]|\d+[.)][ \t])/.test(line)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Shifts user-authored headings down so they can never collide with the prompt
|
|
135
|
+
* skeleton, which owns `##` (sections) and `###` (card titles): `#` becomes
|
|
136
|
+
* `####`, deeper levels cap at `######`. Setext headings are rewritten as the
|
|
137
|
+
* equivalent demoted ATX heading, since two-level setext cannot express the
|
|
138
|
+
* shift. Fenced code is left alone.
|
|
139
|
+
*/
|
|
140
|
+
export function demoteHeadings(text: string) {
|
|
141
|
+
const lineState = fenceTracker();
|
|
142
|
+
const output: string[] = [];
|
|
143
|
+
for (const line of text.split("\n")) {
|
|
144
|
+
if (lineState(line) !== "outside") {
|
|
145
|
+
output.push(line);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const underline = SETEXT_UNDERLINE.exec(line)?.[1];
|
|
150
|
+
const previous = output[output.length - 1];
|
|
151
|
+
if (underline !== undefined && previous !== undefined && isSetextText(previous)) {
|
|
152
|
+
const level = underline.startsWith("=") ? 1 : 2;
|
|
153
|
+
output[output.length - 1] = `${demotedHashes(level)} ${previous.trim()}`;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
output.push(
|
|
158
|
+
line.replace(ATX_HEADING, (_, indent: string, hashes: string) => {
|
|
159
|
+
return `${indent}${demotedHashes(hashes.length)}`;
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return output.join("\n");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The one render step for LLM-bound rich text: resolve mentions, then demote headings. */
|
|
167
|
+
export function renderRichText(text: string, toolNameById: ReadonlyMap<string, string>) {
|
|
168
|
+
return demoteHeadings(renderMentions(text, toolNameById));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Reduces rich text to plain prose for embeddings: mentions become tool names
|
|
173
|
+
* (cached inline names when no map is given) and markdown syntax is dropped.
|
|
174
|
+
* ponytail: a regex stripper, not a markdown parser — nested emphasis or exotic
|
|
175
|
+
* constructs may leave residue; swap in a real parser if retrieval quality
|
|
176
|
+
* ever shows it.
|
|
177
|
+
*/
|
|
178
|
+
export function stripToPlaintext(
|
|
179
|
+
text: string,
|
|
180
|
+
toolNameById: ReadonlyMap<string, string> = new Map(),
|
|
181
|
+
) {
|
|
182
|
+
const lineState = fenceTracker();
|
|
183
|
+
return renderMentions(text, toolNameById)
|
|
184
|
+
.split("\n")
|
|
185
|
+
.filter((line) => {
|
|
186
|
+
const state = lineState(line);
|
|
187
|
+
return state !== "open" && state !== "close";
|
|
188
|
+
})
|
|
189
|
+
.map((line) =>
|
|
190
|
+
line
|
|
191
|
+
.replace(/^ {0,3}#{1,6}[ \t]+/, "")
|
|
192
|
+
.replace(SETEXT_UNDERLINE, "")
|
|
193
|
+
.replace(/^\s{0,3}(>\s?)+/, "")
|
|
194
|
+
.replace(/^(\s*)([-*+]|\d+[.)])\s+/, "$1")
|
|
195
|
+
.replace(/^\s*([-*_]\s*){3,}$/, ""),
|
|
196
|
+
)
|
|
197
|
+
.join("\n")
|
|
198
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
199
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
200
|
+
.replace(/(\*\*|__)([^*_]+)\1/g, "$2")
|
|
201
|
+
.replace(/(^|\W)[*_]([^*_]+)[*_](?=\W|$)/gm, "$1$2")
|
|
202
|
+
.replace(/~~([^~]+)~~/g, "$1")
|
|
203
|
+
.replace(/`([^`]*)`/g, "$1")
|
|
204
|
+
.replace(/\\([\\*_#[\]()>!`~+.-])/g, "$1")
|
|
205
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
206
|
+
.trim();
|
|
207
|
+
}
|
package/contracts/runtime.ts
CHANGED
|
@@ -13,7 +13,42 @@ export const AGENT_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
|
13
13
|
/** Runtime contract §5: tool names become `tools.<name>()` sandbox bindings. */
|
|
14
14
|
export const TOOL_NAME_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
15
15
|
|
|
16
|
-
export const PROMPT_VARIABLES = ["userName", "channel", "locale"] as const;
|
|
16
|
+
export const PROMPT_VARIABLES = ["userName", "channel", "locale", "utcTime", "timezone"] as const;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* One entry per prompt variable, driving editor autocomplete, validation, and
|
|
20
|
+
* runtime substitution alike. Adding a variable = extending the tuple above
|
|
21
|
+
* plus one entry here (the compiler enforces the pair); the consumer embedding
|
|
22
|
+
* the agent supplies the value in its session/request context under the same
|
|
23
|
+
* key. Descriptions are plain English on purpose: variables are code-like
|
|
24
|
+
* identifiers shown in an LTR suggestion list, not localized UI copy.
|
|
25
|
+
*/
|
|
26
|
+
export const PROMPT_VARIABLE_DEFINITIONS = {
|
|
27
|
+
userName: {
|
|
28
|
+
description: "Display name of the signed-in user",
|
|
29
|
+
example: "Sara",
|
|
30
|
+
},
|
|
31
|
+
channel: {
|
|
32
|
+
description: "Channel the conversation arrived on",
|
|
33
|
+
example: "web",
|
|
34
|
+
},
|
|
35
|
+
locale: {
|
|
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 }>;
|
|
17
52
|
|
|
18
53
|
export const RUNTIME_ERROR_KINDS = [
|
|
19
54
|
"timeout",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** One mention token, unanchored and flagless — the editor's tokenizer anchors it, the helpers below add `g`. */
|
|
2
|
+
export declare const MENTION_PATTERN_SOURCE: string;
|
|
3
|
+
export declare function extractMentionedToolIds(text: string): string[];
|
|
4
|
+
/** Replaces each mention token with the tool's current name (falling back to the cached inline name). */
|
|
5
|
+
export declare function renderMentions(text: string, toolNameById: ReadonlyMap<string, string>): string;
|
|
6
|
+
/** Refreshes stale display names inside stored text, keeping the tokens intact. */
|
|
7
|
+
export declare function normalizeMentions(text: string, toolNameById: ReadonlyMap<string, string>): string;
|
|
8
|
+
/** Splits the `{{variables}}` referenced in a text into known and unknown ones. */
|
|
9
|
+
export declare function analyzeVariables<Variable extends string>(text: string, known: ReadonlyArray<Variable>): {
|
|
10
|
+
variables: Variable[];
|
|
11
|
+
unknownVariables: string[];
|
|
12
|
+
};
|
|
13
|
+
/** Fills `{{variables}}` with per-request values; unknown names stay verbatim. */
|
|
14
|
+
export declare function substituteVariables(text: string, values: Record<string, string>): string;
|
|
15
|
+
/**
|
|
16
|
+
* Shifts user-authored headings down so they can never collide with the prompt
|
|
17
|
+
* skeleton, which owns `##` (sections) and `###` (card titles): `#` becomes
|
|
18
|
+
* `####`, deeper levels cap at `######`. Setext headings are rewritten as the
|
|
19
|
+
* equivalent demoted ATX heading, since two-level setext cannot express the
|
|
20
|
+
* shift. Fenced code is left alone.
|
|
21
|
+
*/
|
|
22
|
+
export declare function demoteHeadings(text: string): string;
|
|
23
|
+
/** The one render step for LLM-bound rich text: resolve mentions, then demote headings. */
|
|
24
|
+
export declare function renderRichText(text: string, toolNameById: ReadonlyMap<string, string>): string;
|
|
25
|
+
/**
|
|
26
|
+
* Reduces rich text to plain prose for embeddings: mentions become tool names
|
|
27
|
+
* (cached inline names when no map is given) and markdown syntax is dropped.
|
|
28
|
+
* ponytail: a regex stripper, not a markdown parser — nested emphasis or exotic
|
|
29
|
+
* constructs may leave residue; swap in a real parser if retrieval quality
|
|
30
|
+
* ever shows it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function stripToPlaintext(text: string, toolNameById?: ReadonlyMap<string, string>): string;
|
|
@@ -3,14 +3,44 @@ import { z } from "zod";
|
|
|
3
3
|
export declare const AGENT_SLUG_PATTERN: RegExp;
|
|
4
4
|
/** Runtime contract §5: tool names become `tools.<name>()` sandbox bindings. */
|
|
5
5
|
export declare const TOOL_NAME_PATTERN: RegExp;
|
|
6
|
-
export declare const PROMPT_VARIABLES: readonly ["userName", "channel", "locale"];
|
|
6
|
+
export declare const PROMPT_VARIABLES: readonly ["userName", "channel", "locale", "utcTime", "timezone"];
|
|
7
|
+
/**
|
|
8
|
+
* One entry per prompt variable, driving editor autocomplete, validation, and
|
|
9
|
+
* runtime substitution alike. Adding a variable = extending the tuple above
|
|
10
|
+
* plus one entry here (the compiler enforces the pair); the consumer embedding
|
|
11
|
+
* the agent supplies the value in its session/request context under the same
|
|
12
|
+
* key. Descriptions are plain English on purpose: variables are code-like
|
|
13
|
+
* identifiers shown in an LTR suggestion list, not localized UI copy.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PROMPT_VARIABLE_DEFINITIONS: {
|
|
16
|
+
userName: {
|
|
17
|
+
description: string;
|
|
18
|
+
example: string;
|
|
19
|
+
};
|
|
20
|
+
channel: {
|
|
21
|
+
description: string;
|
|
22
|
+
example: string;
|
|
23
|
+
};
|
|
24
|
+
locale: {
|
|
25
|
+
description: string;
|
|
26
|
+
example: string;
|
|
27
|
+
};
|
|
28
|
+
utcTime: {
|
|
29
|
+
description: string;
|
|
30
|
+
example: string;
|
|
31
|
+
};
|
|
32
|
+
timezone: {
|
|
33
|
+
description: string;
|
|
34
|
+
example: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
7
37
|
export declare const RUNTIME_ERROR_KINDS: readonly ["timeout", "upstream_4xx", "upstream_5xx", "schema_mismatch", "rate_limited", "not_found", "unauthorized", "invalid_request", "egress_blocked", "internal"];
|
|
8
38
|
/** The ways an execute output may disagree with the tool's declared schema. */
|
|
9
39
|
export declare const JSON_SCHEMA_ISSUE_KINDS: readonly ["missing", "unexpected", "type_mismatch"];
|
|
10
40
|
export declare const agentSlugSchema: z.ZodString;
|
|
11
41
|
export declare const localeSchema: z.ZodEnum<{
|
|
12
|
-
en: "en";
|
|
13
42
|
ar: "ar";
|
|
43
|
+
en: "en";
|
|
14
44
|
}>;
|
|
15
45
|
export declare const catalogVersionSchema: z.ZodString;
|
|
16
46
|
export declare const sharedShapeSchema: z.ZodObject<{
|
|
@@ -54,11 +84,13 @@ export declare const runtimeAgentConfigSchema: z.ZodObject<{
|
|
|
54
84
|
userName: "userName";
|
|
55
85
|
channel: "channel";
|
|
56
86
|
locale: "locale";
|
|
87
|
+
utcTime: "utcTime";
|
|
88
|
+
timezone: "timezone";
|
|
57
89
|
}>>;
|
|
58
90
|
catalogBlurb: z.ZodString;
|
|
59
91
|
defaultLocale: z.ZodEnum<{
|
|
60
|
-
en: "en";
|
|
61
92
|
ar: "ar";
|
|
93
|
+
en: "en";
|
|
62
94
|
}>;
|
|
63
95
|
metaTools: z.ZodObject<{
|
|
64
96
|
searchKnowledge: z.ZodBoolean;
|
|
@@ -76,8 +108,8 @@ export declare const runtimeAgentConfigSchema: z.ZodObject<{
|
|
|
76
108
|
export declare const resolveRequestSchema: z.ZodObject<{
|
|
77
109
|
query: z.ZodString;
|
|
78
110
|
locale: z.ZodOptional<z.ZodEnum<{
|
|
79
|
-
en: "en";
|
|
80
111
|
ar: "ar";
|
|
112
|
+
en: "en";
|
|
81
113
|
}>>;
|
|
82
114
|
hints: z.ZodOptional<z.ZodObject<{
|
|
83
115
|
threadId: z.ZodOptional<z.ZodString>;
|
|
@@ -95,8 +127,8 @@ export declare const resolveResponseSchema: z.ZodObject<{
|
|
|
95
127
|
resolveId: z.ZodUUID;
|
|
96
128
|
catalogVersion: z.ZodString;
|
|
97
129
|
locale: z.ZodEnum<{
|
|
98
|
-
en: "en";
|
|
99
130
|
ar: "ar";
|
|
131
|
+
en: "en";
|
|
100
132
|
}>;
|
|
101
133
|
knowledge: z.ZodArray<z.ZodObject<{
|
|
102
134
|
chunkId: z.ZodUUID;
|
|
@@ -142,8 +174,8 @@ export declare const searchRequestSchema: z.ZodObject<{
|
|
|
142
174
|
query: z.ZodString;
|
|
143
175
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
144
176
|
locale: z.ZodOptional<z.ZodEnum<{
|
|
145
|
-
en: "en";
|
|
146
177
|
ar: "ar";
|
|
178
|
+
en: "en";
|
|
147
179
|
}>>;
|
|
148
180
|
threadId: z.ZodOptional<z.ZodString>;
|
|
149
181
|
service: z.ZodOptional<z.ZodString>;
|
|
@@ -211,8 +243,8 @@ export declare const executeRequestSchema: z.ZodObject<{
|
|
|
211
243
|
threadId: z.ZodOptional<z.ZodString>;
|
|
212
244
|
userId: z.ZodOptional<z.ZodString>;
|
|
213
245
|
locale: z.ZodOptional<z.ZodEnum<{
|
|
214
|
-
en: "en";
|
|
215
246
|
ar: "ar";
|
|
247
|
+
en: "en";
|
|
216
248
|
}>>;
|
|
217
249
|
}, z.core.$strip>>;
|
|
218
250
|
}, z.core.$strip>;
|
|
@@ -31,9 +31,9 @@ export declare function createCcRuntime(options: CcRuntimeOptions): {
|
|
|
31
31
|
config: {
|
|
32
32
|
agentId: string;
|
|
33
33
|
systemPrompt: string;
|
|
34
|
-
promptVariables: ("userName" | "channel" | "locale")[];
|
|
34
|
+
promptVariables: ("userName" | "channel" | "locale" | "utcTime" | "timezone")[];
|
|
35
35
|
catalogBlurb: string;
|
|
36
|
-
defaultLocale: "
|
|
36
|
+
defaultLocale: "ar" | "en";
|
|
37
37
|
metaTools: {
|
|
38
38
|
searchKnowledge: boolean;
|
|
39
39
|
searchTools: boolean;
|
|
@@ -51,7 +51,7 @@ export declare function createCcRuntime(options: CcRuntimeOptions): {
|
|
|
51
51
|
threadId: string;
|
|
52
52
|
turnKey: string;
|
|
53
53
|
userId: string;
|
|
54
|
-
locale: "
|
|
54
|
+
locale: "ar" | "en";
|
|
55
55
|
endUserToken: string;
|
|
56
56
|
abortSignal: AbortSignal;
|
|
57
57
|
getStepIndex: () => number;
|
|
@@ -30,9 +30,9 @@ export declare class ControlCenterClient {
|
|
|
30
30
|
readonly config: {
|
|
31
31
|
agentId: string;
|
|
32
32
|
systemPrompt: string;
|
|
33
|
-
promptVariables: ("userName" | "channel" | "locale")[];
|
|
33
|
+
promptVariables: ("userName" | "channel" | "locale" | "utcTime" | "timezone")[];
|
|
34
34
|
catalogBlurb: string;
|
|
35
|
-
defaultLocale: "
|
|
35
|
+
defaultLocale: "ar" | "en";
|
|
36
36
|
metaTools: {
|
|
37
37
|
searchKnowledge: boolean;
|
|
38
38
|
searchTools: boolean;
|
|
@@ -51,7 +51,7 @@ export declare class ControlCenterClient {
|
|
|
51
51
|
resolve(agentId: string, request: ResolveRequest, abortSignal?: AbortSignal): Promise<{
|
|
52
52
|
resolveId: string;
|
|
53
53
|
catalogVersion: string;
|
|
54
|
-
locale: "
|
|
54
|
+
locale: "ar" | "en";
|
|
55
55
|
knowledge: {
|
|
56
56
|
chunkId: string;
|
|
57
57
|
text: string;
|
|
@@ -2,9 +2,9 @@ import type { ControlCenterClient } from "./client";
|
|
|
2
2
|
export declare function getControlCenterConfig(cc: ControlCenterClient, agentId: string, abortSignal?: AbortSignal): Promise<{
|
|
3
3
|
agentId: string;
|
|
4
4
|
systemPrompt: string;
|
|
5
|
-
promptVariables: ("userName" | "channel" | "locale")[];
|
|
5
|
+
promptVariables: ("userName" | "channel" | "locale" | "utcTime" | "timezone")[];
|
|
6
6
|
catalogBlurb: string;
|
|
7
|
-
defaultLocale: "
|
|
7
|
+
defaultLocale: "ar" | "en";
|
|
8
8
|
metaTools: {
|
|
9
9
|
searchKnowledge: boolean;
|
|
10
10
|
searchTools: boolean;
|
package/package.json
CHANGED
package/src/lib/ai/index.ts
CHANGED
|
@@ -190,6 +190,13 @@ export async function startTurn(
|
|
|
190
190
|
config: cc.config,
|
|
191
191
|
resolved: ccResolved,
|
|
192
192
|
variables: buildPromptVariables(cc.config.promptVariables, {
|
|
193
|
+
// Computed per request; consumer-supplied context
|
|
194
|
+
// with the same key wins via the spreads below. No
|
|
195
|
+
// such default for `timezone`: the server's zone is
|
|
196
|
+
// not the session's, so guessing would feed the
|
|
197
|
+
// model wrong user context — only the consumer can
|
|
198
|
+
// supply it.
|
|
199
|
+
utcTime: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
193
200
|
...session,
|
|
194
201
|
...requestContext,
|
|
195
202
|
locale: cc.locale,
|
package/src/lib/cc/format.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { substituteVariables } from "@cortex/contracts/rich-text";
|
|
2
|
+
|
|
1
3
|
import type { ResolveResponse, RuntimeAgentConfig } from "./types";
|
|
2
4
|
|
|
3
5
|
type SharedShape = ResolveResponse["sharedShapes"][number];
|
|
@@ -5,12 +7,6 @@ type ToolSignature = ResolveResponse["tools"][number];
|
|
|
5
7
|
type ServiceCard = ResolveResponse["services"][number];
|
|
6
8
|
type KnowledgeChunk = ResolveResponse["knowledge"][number];
|
|
7
9
|
|
|
8
|
-
function substitutePromptVariables(template: string, variables: Record<string, string>) {
|
|
9
|
-
return template.replace(/\{\{(\w+)\}\}/g, function replaceVariable(match, name: string) {
|
|
10
|
-
return variables[name] ?? match;
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
10
|
/** Values for the published prompt's `{{variable}}` placeholders, read from session/request context. */
|
|
15
11
|
export function buildPromptVariables(
|
|
16
12
|
promptVariables: RuntimeAgentConfig["promptVariables"],
|
|
@@ -67,7 +63,7 @@ export type CcPromptInput = {
|
|
|
67
63
|
export function buildCcSection(cc: CcPromptInput) {
|
|
68
64
|
const parts: string[] = [];
|
|
69
65
|
|
|
70
|
-
const prompt =
|
|
66
|
+
const prompt = substituteVariables(cc.config.systemPrompt, cc.variables).trim();
|
|
71
67
|
if (prompt) parts.push(prompt);
|
|
72
68
|
|
|
73
69
|
if (cc.config.catalogBlurb) {
|