@f5-sales-demo/xcsh 22.1.2 → 22.2.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 +8 -8
- package/src/internal-urls/api-catalog-discovery.ts +56 -1
- package/src/internal-urls/api-catalog-preflight.ts +35 -63
- package/src/internal-urls/api-catalog-qmd-index.generated.ts +2 -2
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/knowledge-classifier.ts +209 -0
- package/src/internal-urls/knowledge-search.ts +90 -0
- package/src/prompts/internal-urls/about.md +1 -1
- package/src/prompts/rules/llms-search.md +10 -1
- package/src/prompts/system/system-prompt.md +15 -1
- package/src/prompts/tools/display-media.md +4 -0
- package/src/services/xcsh-knowledge.ts +6 -6
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "22.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "22.2.0",
|
|
21
|
+
"commit": "5d9f4f3ce9b3fc191846cfe6fa37d60afab62421",
|
|
22
|
+
"shortCommit": "5d9f4f3",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v22.
|
|
25
|
-
"commitDate": "2026-09-
|
|
26
|
-
"buildDate": "2026-09-
|
|
24
|
+
"tag": "v22.2.0",
|
|
25
|
+
"commitDate": "2026-09-28T01:49:00Z",
|
|
26
|
+
"buildDate": "2026-09-28T02:25:11.763Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v22.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/5d9f4f3ce9b3fc191846cfe6fa37d60afab62421",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v22.2.0"
|
|
33
33
|
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
export type KnowledgeRoute = "api" | "documentation" | "direct-uri" | "none";
|
|
2
|
+
export type DocumentationKnowledgeSource = "docs-cloud-f5-com" | "my-f5-com";
|
|
3
|
+
|
|
4
|
+
export interface KnowledgeClassifierResource {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly aliases: readonly string[];
|
|
7
|
+
readonly domain: string;
|
|
8
|
+
readonly categories: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface KnowledgeSearchPlan {
|
|
12
|
+
readonly intent: string;
|
|
13
|
+
readonly lex: readonly string[];
|
|
14
|
+
readonly vec: readonly string[];
|
|
15
|
+
readonly hyde: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface KnowledgeConstraints {
|
|
19
|
+
readonly domain?: string;
|
|
20
|
+
readonly source?: DocumentationKnowledgeSource;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface KnowledgeClassification {
|
|
24
|
+
readonly route: KnowledgeRoute;
|
|
25
|
+
readonly confidence: number;
|
|
26
|
+
readonly query: KnowledgeSearchPlan;
|
|
27
|
+
readonly constraints: KnowledgeConstraints;
|
|
28
|
+
readonly resource?: string;
|
|
29
|
+
readonly domain?: string;
|
|
30
|
+
readonly source?: DocumentationKnowledgeSource;
|
|
31
|
+
readonly documentPath?: string;
|
|
32
|
+
readonly directUri?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface KnowledgeClassifierOptions {
|
|
36
|
+
readonly toolsEnabled: boolean;
|
|
37
|
+
readonly resources?: readonly KnowledgeClassifierResource[];
|
|
38
|
+
readonly documentationSource?: DocumentationKnowledgeSource;
|
|
39
|
+
readonly previous?: KnowledgeClassification | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const EMPTY_QUERY: KnowledgeSearchPlan = { intent: "", lex: [], vec: [], hyde: [] };
|
|
43
|
+
const THIRD_PARTY_SCOPE = /\b(?:aws|amazon|azure|gcp|google cloud|kubernetes)\b/i;
|
|
44
|
+
const F5_SCOPE = /\b(?:f5(?:\s+distributed\s+cloud)?|xc)\b/i;
|
|
45
|
+
const ANAPHORIC = /\b(?:its|it|that|this|those|them|the same)\b/i;
|
|
46
|
+
const API_METADATA =
|
|
47
|
+
/\b(?:api|endpoint|operation\s*id|api\s+path|http\s+method|method|payload|request\s+body|required\s+fields?|enum|allowed\s+values?|constraints?|limits?|maximum|max(?:imum)?|minimum|min(?:imum)?|how\s+many|create|creates|creating|get|gets|list|lists|update|updates|replace|replaces|delete|deletes|clone|import)\b/i;
|
|
48
|
+
const DOCUMENTATION_INTENT =
|
|
49
|
+
/\b(?:documentation|docs?|guide|tutorial|how\s+(?:do|can|to)|configure|configuration|set\s*up|troubleshoot|procedure|instructions?)\b/i;
|
|
50
|
+
const EXCLUDED_API_INTENT = /\b(?:pricing|price|quote|licen[cs]ing|sales)\b/i;
|
|
51
|
+
const DIRECT_URI = /\bxcsh:\/\/(?:api-catalog|api-spec|documentation)\/[^\s)>\]}]*/i;
|
|
52
|
+
|
|
53
|
+
function normalizePhrase(value: string): string {
|
|
54
|
+
return value
|
|
55
|
+
.toLocaleLowerCase("en-US")
|
|
56
|
+
.replace(/[_-]+/g, " ")
|
|
57
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
58
|
+
.trim()
|
|
59
|
+
.replace(/\s+/g, " ");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function action(value: string): { base?: string; gerund?: string } {
|
|
63
|
+
if (/\b(?:create|creates|creating)\b/i.test(value)) return { base: "create", gerund: "creating" };
|
|
64
|
+
if (/\b(?:configure|configuration|configuring)\b/i.test(value)) return { base: "configure", gerund: "configuring" };
|
|
65
|
+
if (/\b(?:set\s*up|setting\s*up)\b/i.test(value)) return { base: "set up", gerund: "setting up" };
|
|
66
|
+
if (/\b(?:get|gets|read|fetch)\b/i.test(value)) return { base: "get", gerund: "getting" };
|
|
67
|
+
if (/\b(?:list|lists|listing)\b/i.test(value)) return { base: "list", gerund: "listing" };
|
|
68
|
+
if (/\b(?:update|updates|updating)\b/i.test(value)) return { base: "update", gerund: "updating" };
|
|
69
|
+
if (/\b(?:replace|replaces|replacing)\b/i.test(value)) return { base: "replace", gerund: "replacing" };
|
|
70
|
+
if (/\b(?:delete|deletes|remove|removing)\b/i.test(value)) return { base: "delete", gerund: "deleting" };
|
|
71
|
+
if (/\bclone\b/i.test(value)) return { base: "clone", gerund: "cloning" };
|
|
72
|
+
if (/\bimport\b/i.test(value)) return { base: "import", gerund: "importing" };
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function documentationSubject(prompt: string): string {
|
|
77
|
+
const normalized = normalizePhrase(prompt);
|
|
78
|
+
const withoutPreamble = normalized
|
|
79
|
+
.replace(/^(?:please )?(?:use|read|find|show me|show) /, "")
|
|
80
|
+
.replace(/^(?:the )?(?:official |pinned )?f5 (?:distributed cloud )?(?:documentation|docs?) (?:to |for )?/, "")
|
|
81
|
+
.replace(/^(?:the )?(?:official |pinned )?(?:documentation|docs?) (?:to |for )?/, "");
|
|
82
|
+
return (withoutPreamble || normalized)
|
|
83
|
+
.replace(/\b(?:a|an|the)\b/g, " ")
|
|
84
|
+
.replace(/\s+/g, " ")
|
|
85
|
+
.trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildKnowledgeSearchPlan(route: "api" | "documentation", subject: string): KnowledgeSearchPlan {
|
|
89
|
+
const normalized = normalizePhrase(subject);
|
|
90
|
+
const verb = action(normalized);
|
|
91
|
+
if (route === "api") {
|
|
92
|
+
const resource = verb.base
|
|
93
|
+
? normalizePhrase(normalized.replace(new RegExp(`\\b${verb.base}\\b`), ""))
|
|
94
|
+
: normalized;
|
|
95
|
+
const lex = [...new Set([resource, verb.base ? `${verb.base} ${resource}` : resource].filter(Boolean))];
|
|
96
|
+
const phrase = verb.gerund ? `${verb.gerund} ${resource}` : resource;
|
|
97
|
+
return {
|
|
98
|
+
intent: `Find the authoritative F5 Distributed Cloud API operation for ${phrase}.`,
|
|
99
|
+
lex,
|
|
100
|
+
vec: [`${verb.base ? `${verb.base} ` : ""}${resource} F5 Distributed Cloud API`],
|
|
101
|
+
hyde: [
|
|
102
|
+
`An authoritative F5 Distributed Cloud API operation for ${phrase}, including method, path, operation ID, request fields, and constraints.`,
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const phrase = verb.gerund ? normalized.replace(new RegExp(`^${verb.base}`), verb.gerund) : normalized;
|
|
107
|
+
return {
|
|
108
|
+
intent: `Find the authoritative F5 documentation page for ${phrase}.`,
|
|
109
|
+
lex: [normalized],
|
|
110
|
+
vec: [`${subject.trim()} F5 documentation`],
|
|
111
|
+
hyde: [
|
|
112
|
+
`An authoritative F5 documentation page explains how to ${subject.trim()} with prerequisites, ordered steps, verification, and limitations.`,
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function none(): KnowledgeClassification {
|
|
118
|
+
return { route: "none", confidence: 0, query: EMPTY_QUERY, constraints: {} };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function direct(prompt: string): KnowledgeClassification | null {
|
|
122
|
+
const directUri = DIRECT_URI.exec(prompt)?.[0];
|
|
123
|
+
if (!directUri) return null;
|
|
124
|
+
try {
|
|
125
|
+
const parsed = new URL(directUri);
|
|
126
|
+
const constraints: KnowledgeConstraints = {};
|
|
127
|
+
if (parsed.hostname === "api-spec") {
|
|
128
|
+
const domain = parsed.pathname.split("/").filter(Boolean)[0];
|
|
129
|
+
if (domain) Object.assign(constraints, { domain });
|
|
130
|
+
}
|
|
131
|
+
if (parsed.hostname === "documentation") {
|
|
132
|
+
const source = parsed.pathname.split("/").filter(Boolean)[0];
|
|
133
|
+
if (source === "docs-cloud-f5-com" || source === "my-f5-com") Object.assign(constraints, { source });
|
|
134
|
+
}
|
|
135
|
+
return { route: "direct-uri", confidence: 1, directUri, constraints, query: EMPTY_QUERY };
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function selectResource(
|
|
142
|
+
prompt: string,
|
|
143
|
+
resources: readonly KnowledgeClassifierResource[],
|
|
144
|
+
): KnowledgeClassifierResource | undefined {
|
|
145
|
+
const normalizedPrompt = ` ${normalizePhrase(prompt)} `;
|
|
146
|
+
return resources
|
|
147
|
+
.flatMap(resource =>
|
|
148
|
+
resource.aliases
|
|
149
|
+
.map(alias => normalizePhrase(alias))
|
|
150
|
+
.filter(alias => alias && normalizedPrompt.includes(` ${alias} `))
|
|
151
|
+
.map(alias => ({ resource, alias })),
|
|
152
|
+
)
|
|
153
|
+
.sort(
|
|
154
|
+
(left, right) =>
|
|
155
|
+
right.alias.length - left.alias.length ||
|
|
156
|
+
right.resource.categories.length - left.resource.categories.length ||
|
|
157
|
+
left.resource.name.localeCompare(right.resource.name) ||
|
|
158
|
+
left.resource.domain.localeCompare(right.resource.domain),
|
|
159
|
+
)[0]?.resource;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function classifyKnowledgeRequest(prompt: string, options: KnowledgeClassifierOptions): KnowledgeClassification {
|
|
163
|
+
if (!options.toolsEnabled) return none();
|
|
164
|
+
const directResult = direct(prompt);
|
|
165
|
+
if (directResult) return directResult;
|
|
166
|
+
|
|
167
|
+
const resources = options.resources ?? [];
|
|
168
|
+
const selected = selectResource(prompt, resources);
|
|
169
|
+
const thirdPartyOnly = THIRD_PARTY_SCOPE.test(prompt) && !F5_SCOPE.test(prompt);
|
|
170
|
+
if (!thirdPartyOnly && !EXCLUDED_API_INTENT.test(prompt) && API_METADATA.test(prompt)) {
|
|
171
|
+
const contextual =
|
|
172
|
+
!selected && ANAPHORIC.test(prompt) && options.previous?.route === "api" ? options.previous : null;
|
|
173
|
+
const resource = selected?.name ?? contextual?.resource;
|
|
174
|
+
const domain = selected?.domain ?? contextual?.domain;
|
|
175
|
+
if (resource && domain) {
|
|
176
|
+
const alias = normalizePhrase(selected?.aliases[0] ?? contextual?.query.lex[0] ?? resource);
|
|
177
|
+
const verb = action(prompt).base;
|
|
178
|
+
const subject = verb ? `${verb} ${alias}` : alias;
|
|
179
|
+
return {
|
|
180
|
+
route: "api",
|
|
181
|
+
confidence: F5_SCOPE.test(prompt) || contextual ? 0.99 : 0.97,
|
|
182
|
+
resource,
|
|
183
|
+
domain,
|
|
184
|
+
constraints: { domain },
|
|
185
|
+
query: buildKnowledgeSearchPlan("api", subject),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const previousDocumentation = options.previous?.route === "documentation" ? options.previous : null;
|
|
191
|
+
if (
|
|
192
|
+
!thirdPartyOnly &&
|
|
193
|
+
(DOCUMENTATION_INTENT.test(prompt) || (ANAPHORIC.test(prompt) && previousDocumentation)) &&
|
|
194
|
+
(F5_SCOPE.test(prompt) || /\b(?:documentation|docs?)\b/i.test(prompt) || previousDocumentation)
|
|
195
|
+
) {
|
|
196
|
+
const source = options.documentationSource ?? previousDocumentation?.source;
|
|
197
|
+
const subject = documentationSubject(prompt);
|
|
198
|
+
return {
|
|
199
|
+
route: "documentation",
|
|
200
|
+
confidence: /\b(?:documentation|docs?)\b/i.test(prompt) ? 0.99 : 0.96,
|
|
201
|
+
source,
|
|
202
|
+
documentPath: previousDocumentation?.documentPath,
|
|
203
|
+
constraints: source ? { source } : {},
|
|
204
|
+
query: buildKnowledgeSearchPlan("documentation", subject),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return none();
|
|
209
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { KnowledgeSearchPlan } from "./knowledge-classifier";
|
|
2
|
+
|
|
3
|
+
export type KnowledgeSearchMode = "bm25" | "hybrid";
|
|
4
|
+
export type KnowledgeSearchCorpus = "api" | "documentation";
|
|
5
|
+
|
|
6
|
+
export interface KnowledgeSearchResult {
|
|
7
|
+
readonly displayPath: string;
|
|
8
|
+
readonly score: number;
|
|
9
|
+
readonly [key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface KnowledgeSearchStore<T extends KnowledgeSearchResult = KnowledgeSearchResult> {
|
|
13
|
+
search(options: Record<string, unknown>): Promise<readonly T[]>;
|
|
14
|
+
searchLex(query: string, options?: { limit?: number; collection?: string | string[] }): Promise<readonly T[]>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface KnowledgeSearchOptions {
|
|
18
|
+
readonly mode: KnowledgeSearchMode;
|
|
19
|
+
readonly corpus: KnowledgeSearchCorpus;
|
|
20
|
+
readonly collections?: readonly string[];
|
|
21
|
+
readonly limit: number;
|
|
22
|
+
readonly candidateLimit?: number;
|
|
23
|
+
readonly rerank?: boolean;
|
|
24
|
+
readonly explain?: boolean;
|
|
25
|
+
readonly minScore?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const KNOWLEDGE_SCORE_THRESHOLDS = {
|
|
29
|
+
api: 0.2,
|
|
30
|
+
documentation: 0.25,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
function interleave<T extends KnowledgeSearchResult>(lists: readonly (readonly T[])[], limit: number): T[] {
|
|
34
|
+
const results: T[] = [];
|
|
35
|
+
const seen = new Set<string>();
|
|
36
|
+
for (let position = 0; results.length < limit; position++) {
|
|
37
|
+
let found = false;
|
|
38
|
+
for (const list of lists) {
|
|
39
|
+
const result = list[position];
|
|
40
|
+
if (!result) continue;
|
|
41
|
+
found = true;
|
|
42
|
+
if (!seen.has(result.displayPath)) {
|
|
43
|
+
seen.add(result.displayPath);
|
|
44
|
+
results.push(result);
|
|
45
|
+
if (results.length === limit) break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!found) break;
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Runs a deterministic structured query. The BM25 mode never invokes model
|
|
55
|
+
* APIs; hybrid mode supplies typed queries so QMD never loads its expansion
|
|
56
|
+
* model. Native/model errors deliberately propagate to the caller.
|
|
57
|
+
*/
|
|
58
|
+
export async function searchKnowledgeStore<T extends KnowledgeSearchResult>(
|
|
59
|
+
store: KnowledgeSearchStore<T>,
|
|
60
|
+
plan: KnowledgeSearchPlan,
|
|
61
|
+
options: KnowledgeSearchOptions,
|
|
62
|
+
): Promise<readonly T[]> {
|
|
63
|
+
if (options.mode === "bm25") {
|
|
64
|
+
const lists: Array<readonly T[]> = [];
|
|
65
|
+
for (const query of plan.lex) {
|
|
66
|
+
lists.push(
|
|
67
|
+
await store.searchLex(query, {
|
|
68
|
+
limit: options.limit,
|
|
69
|
+
collection: options.collections ? [...options.collections] : undefined,
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return interleave(lists, options.limit);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return store.search({
|
|
77
|
+
queries: [
|
|
78
|
+
...plan.lex.map(query => ({ type: "lex" as const, query })),
|
|
79
|
+
...plan.vec.map(query => ({ type: "vec" as const, query })),
|
|
80
|
+
...plan.hyde.map(query => ({ type: "hyde" as const, query })),
|
|
81
|
+
],
|
|
82
|
+
intent: plan.intent,
|
|
83
|
+
collections: options.collections ? [...options.collections] : undefined,
|
|
84
|
+
limit: options.limit,
|
|
85
|
+
candidateLimit: options.candidateLimit ?? 30,
|
|
86
|
+
minScore: options.minScore ?? KNOWLEDGE_SCORE_THRESHOLDS[options.corpus],
|
|
87
|
+
explain: options.explain ?? true,
|
|
88
|
+
rerank: options.rerank ?? false,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -29,7 +29,7 @@ This document is the authoritative answer when the user asks about xcsh itself.
|
|
|
29
29
|
|
|
30
30
|
xcsh serves F5 Distributed Cloud sales engineers. Product documentation is
|
|
31
31
|
federated across the f5-sales-demo GitHub organization. Entry point:
|
|
32
|
-
https://f5-sales-demo.github.io/
|
|
32
|
+
https://f5-sales-demo.github.io/llms.txt
|
|
33
33
|
|
|
34
34
|
Each product repo publishes: llms.txt (index with sidebar nav), custom sets
|
|
35
35
|
at /_llms-txt/{topic}.txt, per-page content at /{slug}.md, plus
|
|
@@ -5,6 +5,15 @@ Select the best result and read its exact follow-up URL,
|
|
|
5
5
|
`xcsh://documentation/<source>/<stable-path>/index.md`, before answering. Use the source filter when the question
|
|
6
6
|
clearly targets `docs-cloud-f5-com` or `my-f5-com`.
|
|
7
7
|
|
|
8
|
+
Render documentation images only when the user explicitly asks to see them. Extract all unique Markdown image references
|
|
9
|
+
from the exact document, resolve relative references against its exact `xcsh://documentation/` URI, and
|
|
10
|
+
read each asset one at a time in document order. Static screenshot collections are not timelines or slideshows.
|
|
11
|
+
Pair every returned image with its official Markdown caption verbatim and one conservative explanation grounded in
|
|
12
|
+
the image, its caption, and nearby prose. If pixels are unavailable, use only the caption and nearby prose without
|
|
13
|
+
inventing visual details. Do not announce or estimate the image count before all reads finish. Afterward, verify that
|
|
14
|
+
the reported image count matches the number of successful asset reads. Documentation assets use `read`, even when
|
|
15
|
+
generic image-inspection guidance prefers `inspect_image`.
|
|
16
|
+
|
|
8
17
|
For API paths, methods, payload fields, required status, enums, and constraints, use the deterministic
|
|
9
18
|
`api-catalog-preflight` result followed by exact `xcsh://api-catalog/` and `xcsh://api-spec/` reads first.
|
|
10
19
|
Do not route API metadata through the documentation QMD index.
|
|
@@ -13,7 +22,7 @@ Use the live cascade below only when the pinned documentation is stale or missin
|
|
|
13
22
|
the QMD lookup fails. You **MUST** disclose why the pinned snapshot is stale, missing, or unavailable before the
|
|
14
23
|
first live lookup:
|
|
15
24
|
|
|
16
|
-
1. **Federation index** — Read `https://f5-sales-demo.github.io/
|
|
25
|
+
1. **Federation index** — Read `https://f5-sales-demo.github.io/llms.txt` and select the relevant categorized site.
|
|
17
26
|
2. **Site index** — Read that site's `llms.txt`; use its Documentation Sets, Sections, and Translations links as published.
|
|
18
27
|
3. **Locale index** — Prefer the user's published locale. The default locale's Section normally points to `/_llms-txt/en.txt`; locale-aware tiered paths use `/_llms-txt/{locale}/…`. If a localized endpoint is absent or contains only a system marker, fall back to English and disclose that fallback.
|
|
19
28
|
4. **Focused content** — Follow `## Contents` links recursively until the narrowest leaf `.txt` answers the question. Generated `/_llms-txt/` links are canonical; do not rewrite them. A same-locale page endpoint such as `/{locale}/{slug}.md` is an equivalent leaf when a page URL is already known.
|
|
@@ -495,10 +495,23 @@ search `xcsh://documentation/?search=<query>` first, then read the exact
|
|
|
495
495
|
`xcsh://documentation/<source>/<stable-path>/index.md` result before answering. The search result
|
|
496
496
|
is discovery; the exact Markdown document is the evidence.
|
|
497
497
|
|
|
498
|
+
### Documentation images
|
|
499
|
+
|
|
500
|
+
Render documentation images only when the user explicitly asks to see them. After that request, extract every
|
|
501
|
+
unique Markdown image reference from the exact document, resolve each relative reference against that document's
|
|
502
|
+
`xcsh://documentation/` URI, and preserve document order. Read each exact asset URI sequentially, one at a time, so
|
|
503
|
+
the transcript order is stable. Do not use `display_media` for documentation assets, and do not convert a static
|
|
504
|
+
screenshot collection into a timeline or slideshow.
|
|
505
|
+
|
|
506
|
+
Inspect each returned image conservatively. Present its official Markdown caption verbatim with one grounded explanation.
|
|
507
|
+
If image pixels are unavailable to the active model, quietly rely only on the official caption and nearby document
|
|
508
|
+
prose; never invent visual details. Do not announce or estimate the image count before all reads finish. Afterward,
|
|
509
|
+
verify that the reported image count matches the number of successful asset reads.
|
|
510
|
+
|
|
498
511
|
If the pinned snapshot is stale, missing, or the QMD lookup fails, disclose that condition before using the live
|
|
499
512
|
fallback knowledge index:
|
|
500
513
|
|
|
501
|
-
`https://f5-sales-demo.github.io/
|
|
514
|
+
`https://f5-sales-demo.github.io/llms.txt`
|
|
502
515
|
|
|
503
516
|
Follow links from there to the relevant site's own `llms.txt`, then fetch only the
|
|
504
517
|
narrowest published content that answers the question. Do not silently replace or supplement a successful
|
|
@@ -714,6 +727,7 @@ Don't open a file hoping. Hope is not a strategy.
|
|
|
714
727
|
- For image understanding tasks: **MUST** use `inspect_image` over `read` to avoid overloading main session context.
|
|
715
728
|
- Write a specific `question` for `inspect_image`: what to inspect, constraints (for example verbatim OCR), and desired output format.
|
|
716
729
|
- If you encounter `[Image content detected but current model does not support vision]` in a message, use `inspect_image` with the image file path to analyze it. Do not ask the user to describe the image — analyze it yourself via the tool.
|
|
730
|
+
- Documentation images are the exception: use the exact `xcsh://documentation/` asset URI with `read`; do not pass internal documentation URIs to `inspect_image`.
|
|
717
731
|
{{/if}}
|
|
718
732
|
{{#ifAll (includes tools "inspect_image") (includes tools "generate_image")}}
|
|
719
733
|
### Image generation and analysis
|
|
@@ -6,3 +6,7 @@ Pass exactly one of:
|
|
|
6
6
|
- `frames`: ordered text frames or raster-frame sources with per-frame `durationMs`.
|
|
7
7
|
|
|
8
8
|
Playback is always muted. Autoplay defaults to once, looping is opt-in, and playback is capped at 12 FPS unless a lower or higher cap (up to 60) is requested. Unsupported terminals and missing FFmpeg receive a static poster or text fallback.
|
|
9
|
+
|
|
10
|
+
Do not use this tool for documentation assets. Resolve each Markdown image to its exact
|
|
11
|
+
`xcsh://documentation/<source>/<stable-path>/assets/<filename>` URI and use `read` sequentially. Static screenshot
|
|
12
|
+
collections are not timelines or slideshows.
|
|
@@ -21,7 +21,6 @@ export interface LlmsIndex {
|
|
|
21
21
|
|
|
22
22
|
const ENTRY_PATTERN = /^- \[([^\]]+)\]\(([^)]+)\):\s*(.+)$/;
|
|
23
23
|
const ROOT_LLMS_FILE = "llms.txt";
|
|
24
|
-
const PORTAL_SLUG = "docs";
|
|
25
24
|
|
|
26
25
|
function extractRootLlmsSlug(url: string): string | null {
|
|
27
26
|
try {
|
|
@@ -64,8 +63,9 @@ function isValidIndex(value: unknown): value is LlmsIndex {
|
|
|
64
63
|
*
|
|
65
64
|
* Federation category headings are intentionally open-ended. A list entry is a
|
|
66
65
|
* topic when it links to a sibling site's root /{slug}/llms.txt endpoint; this
|
|
67
|
-
* excludes documentation sets
|
|
68
|
-
*
|
|
66
|
+
* excludes documentation sets and tiered content. The root portal now has
|
|
67
|
+
* locale feeds at /{locale}/llms.txt, so translation entries are excluded
|
|
68
|
+
* by their heading without restricting future federation categories.
|
|
69
69
|
*/
|
|
70
70
|
export function parseLlmsTxt(content: string, now?: Date): LlmsIndex {
|
|
71
71
|
const lines = content.split("\n");
|
|
@@ -94,11 +94,11 @@ export function parseLlmsTxt(content: string, now?: Date): LlmsIndex {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
const match = ENTRY_PATTERN.exec(trimmed);
|
|
97
|
-
if (!match || !category) continue;
|
|
97
|
+
if (!match || !category || category === "Translations") continue;
|
|
98
98
|
|
|
99
99
|
const [, name, url, topicDescription] = match;
|
|
100
100
|
const slug = extractRootLlmsSlug(url);
|
|
101
|
-
if (!slug ||
|
|
101
|
+
if (!slug || seenUrls.has(url)) continue;
|
|
102
102
|
|
|
103
103
|
seenUrls.add(url);
|
|
104
104
|
topics.push({ name, description: topicDescription, url, category });
|
|
@@ -113,7 +113,7 @@ export function parseLlmsTxt(content: string, now?: Date): LlmsIndex {
|
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
const ROOT_LLMS_URL = "https://f5-sales-demo.github.io/
|
|
116
|
+
const ROOT_LLMS_URL = "https://f5-sales-demo.github.io/llms.txt";
|
|
117
117
|
const DEFAULT_TTL_MS = 3_600_000;
|
|
118
118
|
|
|
119
119
|
export class KnowledgeService {
|