@farming-labs/docs 0.2.60 → 0.2.62
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 +1 -1
- package/package.json +11 -1
- package/dist/agent-BFqyqEnC.mjs +0 -4547
- package/dist/agent-DlxriaTs.mjs +0 -624
- package/dist/agent-evals-kJs2Y9xR.mjs +0 -2144
- package/dist/agent-export-BgUaiW8f.mjs +0 -869
- package/dist/agent-scope-CCaIY1aK.mjs +0 -283
- package/dist/agents-Djh-HXih.mjs +0 -219
- package/dist/analytics-Bx44lg6d.mjs +0 -177
- package/dist/cli/index.d.mts +0 -15
- package/dist/cli/index.mjs +0 -452
- package/dist/client/react.d.mts +0 -45
- package/dist/client/react.mjs +0 -223
- package/dist/cloud-BH_sHX64.mjs +0 -1615
- package/dist/cloud-analytics-CSyFE6SS.mjs +0 -132
- package/dist/cloud-ask-ai-B2WnG4fF.d.mts +0 -23
- package/dist/cloud-ask-ai-hnJfj8-X.mjs +0 -382
- package/dist/code-blocks-qe0T8-xe.mjs +0 -871
- package/dist/codeblocks-Bq67u32v.mjs +0 -250
- package/dist/config-DASewQ0x.mjs +0 -363
- package/dist/dev-DgY5xGl9.mjs +0 -1333
- package/dist/docs-cloud-server.d.mts +0 -70
- package/dist/docs-cloud-server.mjs +0 -310
- package/dist/doctor-CO1VMcF_.mjs +0 -1906
- package/dist/downgrade-BZs86NVr.mjs +0 -184
- package/dist/errors-CVqZ3kOO.mjs +0 -20
- package/dist/golden-evaluations-BN9u2wxw.mjs +0 -1483
- package/dist/i18n-CAlj1ADU.mjs +0 -40
- package/dist/index.d.mts +0 -1099
- package/dist/index.mjs +0 -9
- package/dist/init-Bd_k06bR.mjs +0 -1233
- package/dist/mcp-B_yXL5G5.mjs +0 -137
- package/dist/mcp.d.mts +0 -287
- package/dist/mcp.mjs +0 -4135
- package/dist/metadata-BDuewuzq.mjs +0 -237
- package/dist/package-version-qik_4J6C.mjs +0 -128
- package/dist/reading-time-BkEft6SD.mjs +0 -741
- package/dist/review-NC-sOdXn.mjs +0 -665
- package/dist/robots-DskPvGPw.mjs +0 -178
- package/dist/robots-ltltiLJF.mjs +0 -197
- package/dist/search-C1JitPwi.d.mts +0 -397
- package/dist/search-D57JXQLj.mjs +0 -1758
- package/dist/search-o4Ud6OXv.mjs +0 -102
- package/dist/server.d.mts +0 -341
- package/dist/server.mjs +0 -11
- package/dist/sitemap-Cq-Yj_iA.mjs +0 -247
- package/dist/sitemap-server-C1ibVKOy.mjs +0 -1137
- package/dist/templates-DNw15P-x.mjs +0 -2373
- package/dist/types-XHABMh_f.d.mts +0 -3248
- package/dist/upgrade-BCJTCW3O.mjs +0 -56
- package/dist/utils-6UCLxv4B.mjs +0 -225
package/dist/agent-BFqyqEnC.mjs
DELETED
|
@@ -1,4547 +0,0 @@
|
|
|
1
|
-
import { createProcessor } from "@mdx-js/mdx";
|
|
2
|
-
|
|
3
|
-
//#region src/agent-contract.ts
|
|
4
|
-
const PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS = [
|
|
5
|
-
"task",
|
|
6
|
-
"outcome",
|
|
7
|
-
"appliesTo",
|
|
8
|
-
"prerequisites",
|
|
9
|
-
"files",
|
|
10
|
-
"commands",
|
|
11
|
-
"sideEffects",
|
|
12
|
-
"verification",
|
|
13
|
-
"rollback",
|
|
14
|
-
"failureModes"
|
|
15
|
-
];
|
|
16
|
-
const PAGE_AGENT_CONTRACT_FIELDS = ["tokenBudget", ...PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS];
|
|
17
|
-
const PAGE_AGENT_CONTRACT_FIELD_SCHEMA = {
|
|
18
|
-
tokenBudget: "number",
|
|
19
|
-
task: "string",
|
|
20
|
-
outcome: "string",
|
|
21
|
-
appliesTo: {
|
|
22
|
-
framework: "string|string[]",
|
|
23
|
-
version: "string|string[]",
|
|
24
|
-
package: "string|string[]"
|
|
25
|
-
},
|
|
26
|
-
prerequisites: "string[]",
|
|
27
|
-
files: "string[]",
|
|
28
|
-
commands: "Array<string|{run,cwd?,description?}>",
|
|
29
|
-
sideEffects: "string[]",
|
|
30
|
-
verification: "Array<string|{description?,run?,expect?}>",
|
|
31
|
-
rollback: "string[]",
|
|
32
|
-
failureModes: "Array<string|{symptom,resolution?}>"
|
|
33
|
-
};
|
|
34
|
-
const APPLIES_TO_FIELDS = [
|
|
35
|
-
"framework",
|
|
36
|
-
"version",
|
|
37
|
-
"package"
|
|
38
|
-
];
|
|
39
|
-
const COMMAND_FIELDS = [
|
|
40
|
-
"run",
|
|
41
|
-
"cwd",
|
|
42
|
-
"description"
|
|
43
|
-
];
|
|
44
|
-
const VERIFICATION_FIELDS = [
|
|
45
|
-
"description",
|
|
46
|
-
"run",
|
|
47
|
-
"expect"
|
|
48
|
-
];
|
|
49
|
-
const FAILURE_MODE_FIELDS = ["symptom", "resolution"];
|
|
50
|
-
const PAGE_AGENT_CONTRACT_START_MARKER = "<!-- farming-labs:agent-contract:start -->";
|
|
51
|
-
const PAGE_AGENT_CONTRACT_END_MARKER = "<!-- farming-labs:agent-contract:end -->";
|
|
52
|
-
function isRecord(value) {
|
|
53
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
54
|
-
}
|
|
55
|
-
function normalizeString(value) {
|
|
56
|
-
if (typeof value !== "string") return void 0;
|
|
57
|
-
return value.trim() || void 0;
|
|
58
|
-
}
|
|
59
|
-
function normalizeStringList(value) {
|
|
60
|
-
if (!Array.isArray(value)) return void 0;
|
|
61
|
-
const seen = /* @__PURE__ */ new Set();
|
|
62
|
-
const normalized = [];
|
|
63
|
-
for (const item of value) {
|
|
64
|
-
const text = normalizeString(item);
|
|
65
|
-
if (!text || seen.has(text)) continue;
|
|
66
|
-
seen.add(text);
|
|
67
|
-
normalized.push(text);
|
|
68
|
-
}
|
|
69
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
70
|
-
}
|
|
71
|
-
function normalizeStringOrList(value) {
|
|
72
|
-
const single = normalizeString(value);
|
|
73
|
-
if (single) return [single];
|
|
74
|
-
return normalizeStringList(value);
|
|
75
|
-
}
|
|
76
|
-
function normalizeAppliesTo(value) {
|
|
77
|
-
if (!isRecord(value)) return void 0;
|
|
78
|
-
const framework = normalizeStringOrList(value.framework);
|
|
79
|
-
const version = normalizeStringOrList(value.version);
|
|
80
|
-
const packageNames = normalizeStringOrList(value.package);
|
|
81
|
-
if (!framework && !version && !packageNames) return void 0;
|
|
82
|
-
return {
|
|
83
|
-
...framework ? { framework } : {},
|
|
84
|
-
...version ? { version } : {},
|
|
85
|
-
...packageNames ? { package: packageNames } : {}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function normalizeCommands(value) {
|
|
89
|
-
if (!Array.isArray(value)) return void 0;
|
|
90
|
-
const commands = [];
|
|
91
|
-
for (const item of value) {
|
|
92
|
-
const shorthand = normalizeString(item);
|
|
93
|
-
if (shorthand) {
|
|
94
|
-
commands.push(shorthand);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (!isRecord(item)) continue;
|
|
98
|
-
const run = normalizeString(item.run);
|
|
99
|
-
if (!run) continue;
|
|
100
|
-
const cwd = normalizeString(item.cwd);
|
|
101
|
-
const description = normalizeString(item.description);
|
|
102
|
-
commands.push({
|
|
103
|
-
run,
|
|
104
|
-
...cwd ? { cwd } : {},
|
|
105
|
-
...description ? { description } : {}
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
return commands.length > 0 ? commands : void 0;
|
|
109
|
-
}
|
|
110
|
-
function normalizeVerification(value) {
|
|
111
|
-
if (!Array.isArray(value)) return void 0;
|
|
112
|
-
const verification = [];
|
|
113
|
-
for (const item of value) {
|
|
114
|
-
const shorthand = normalizeString(item);
|
|
115
|
-
if (shorthand) {
|
|
116
|
-
verification.push(shorthand);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (!isRecord(item)) continue;
|
|
120
|
-
const description = normalizeString(item.description);
|
|
121
|
-
const run = normalizeString(item.run);
|
|
122
|
-
const expect = normalizeString(item.expect);
|
|
123
|
-
if (!description && !run && !expect) continue;
|
|
124
|
-
verification.push({
|
|
125
|
-
...description ? { description } : {},
|
|
126
|
-
...run ? { run } : {},
|
|
127
|
-
...expect ? { expect } : {}
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
return verification.length > 0 ? verification : void 0;
|
|
131
|
-
}
|
|
132
|
-
function normalizeFailureModes(value) {
|
|
133
|
-
if (!Array.isArray(value)) return void 0;
|
|
134
|
-
const failureModes = [];
|
|
135
|
-
for (const item of value) {
|
|
136
|
-
const shorthand = normalizeString(item);
|
|
137
|
-
if (shorthand) {
|
|
138
|
-
failureModes.push(shorthand);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (!isRecord(item)) continue;
|
|
142
|
-
const symptom = normalizeString(item.symptom);
|
|
143
|
-
if (!symptom) continue;
|
|
144
|
-
const resolution = normalizeString(item.resolution);
|
|
145
|
-
failureModes.push({
|
|
146
|
-
symptom,
|
|
147
|
-
...resolution ? { resolution } : {}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
return failureModes.length > 0 ? failureModes : void 0;
|
|
151
|
-
}
|
|
152
|
-
function normalizeTokenBudget(value) {
|
|
153
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
154
|
-
return Math.ceil(value);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Normalize untrusted page frontmatter into the stable agent contract shape.
|
|
158
|
-
* Invalid fields are omitted so malformed author input cannot break page delivery.
|
|
159
|
-
*/
|
|
160
|
-
function normalizePageAgentFrontmatter(value) {
|
|
161
|
-
if (!isRecord(value)) return void 0;
|
|
162
|
-
const tokenBudget = normalizeTokenBudget(value.tokenBudget);
|
|
163
|
-
const task = normalizeString(value.task);
|
|
164
|
-
const outcome = normalizeString(value.outcome);
|
|
165
|
-
const appliesTo = normalizeAppliesTo(value.appliesTo);
|
|
166
|
-
const prerequisites = normalizeStringList(value.prerequisites);
|
|
167
|
-
const files = normalizeStringList(value.files);
|
|
168
|
-
const commands = normalizeCommands(value.commands);
|
|
169
|
-
const sideEffects = normalizeStringList(value.sideEffects);
|
|
170
|
-
const verification = normalizeVerification(value.verification);
|
|
171
|
-
const rollback = normalizeStringList(value.rollback);
|
|
172
|
-
const failureModes = normalizeFailureModes(value.failureModes);
|
|
173
|
-
const normalized = {
|
|
174
|
-
...tokenBudget !== void 0 ? { tokenBudget } : {},
|
|
175
|
-
...task ? { task } : {},
|
|
176
|
-
...outcome ? { outcome } : {},
|
|
177
|
-
...appliesTo ? { appliesTo } : {},
|
|
178
|
-
...prerequisites ? { prerequisites } : {},
|
|
179
|
-
...files ? { files } : {},
|
|
180
|
-
...commands ? { commands } : {},
|
|
181
|
-
...sideEffects ? { sideEffects } : {},
|
|
182
|
-
...verification ? { verification } : {},
|
|
183
|
-
...rollback ? { rollback } : {},
|
|
184
|
-
...failureModes ? { failureModes } : {}
|
|
185
|
-
};
|
|
186
|
-
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
187
|
-
}
|
|
188
|
-
function hasStructuredPageAgentContract(value) {
|
|
189
|
-
const normalized = normalizePageAgentFrontmatter(value);
|
|
190
|
-
if (!normalized) return false;
|
|
191
|
-
return PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS.some((field) => normalized[field] !== void 0);
|
|
192
|
-
}
|
|
193
|
-
function addStringIssue(issues, object, field) {
|
|
194
|
-
if (!(field in object)) return;
|
|
195
|
-
if (!normalizeString(object[field])) issues.push({
|
|
196
|
-
path: `agent.${field}`,
|
|
197
|
-
message: "must be a non-empty string"
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
function addStringListIssues(issues, object, field) {
|
|
201
|
-
if (!(field in object)) return;
|
|
202
|
-
const value = object[field];
|
|
203
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
204
|
-
issues.push({
|
|
205
|
-
path: `agent.${field}`,
|
|
206
|
-
message: "must be a non-empty string array"
|
|
207
|
-
});
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
value.forEach((item, index) => {
|
|
211
|
-
if (!normalizeString(item)) issues.push({
|
|
212
|
-
path: `agent.${field}[${index}]`,
|
|
213
|
-
message: "must be a non-empty string"
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
function editDistance(left, right) {
|
|
218
|
-
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
219
|
-
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
220
|
-
const current = [leftIndex];
|
|
221
|
-
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (previous[rightIndex] ?? 0) + 1, (previous[rightIndex - 1] ?? 0) + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
|
|
222
|
-
previous.splice(0, previous.length, ...current);
|
|
223
|
-
}
|
|
224
|
-
return previous[right.length] ?? 0;
|
|
225
|
-
}
|
|
226
|
-
function closestKnownField(field, allowed) {
|
|
227
|
-
const closest = allowed.map((candidate) => ({
|
|
228
|
-
candidate,
|
|
229
|
-
distance: editDistance(field, candidate)
|
|
230
|
-
})).sort((left, right) => left.distance - right.distance || left.candidate.localeCompare(right.candidate))[0];
|
|
231
|
-
if (!closest) return void 0;
|
|
232
|
-
const threshold = Math.max(1, Math.min(3, Math.floor(closest.candidate.length / 3)));
|
|
233
|
-
return closest.distance <= threshold ? closest.candidate : void 0;
|
|
234
|
-
}
|
|
235
|
-
function addUnknownKeyIssues(issues, object, allowed, path) {
|
|
236
|
-
for (const field of Object.keys(object)) {
|
|
237
|
-
if (allowed.includes(field)) continue;
|
|
238
|
-
const suggestion = closestKnownField(field, allowed);
|
|
239
|
-
issues.push({
|
|
240
|
-
path: `${path}.${field}`,
|
|
241
|
-
message: suggestion ? `is not recognized; did you mean "${path}.${suggestion}"?` : "is not recognized"
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
/** Return author-facing validation issues without throwing. */
|
|
246
|
-
function getPageAgentFrontmatterIssues(value) {
|
|
247
|
-
if (value === void 0) return [];
|
|
248
|
-
if (!isRecord(value)) return [{
|
|
249
|
-
path: "agent",
|
|
250
|
-
message: "must be an object"
|
|
251
|
-
}];
|
|
252
|
-
const issues = [];
|
|
253
|
-
addUnknownKeyIssues(issues, value, PAGE_AGENT_CONTRACT_FIELDS, "agent");
|
|
254
|
-
if ("tokenBudget" in value && (typeof value.tokenBudget !== "number" || !Number.isFinite(value.tokenBudget) || value.tokenBudget <= 0)) issues.push({
|
|
255
|
-
path: "agent.tokenBudget",
|
|
256
|
-
message: "must be a positive finite number"
|
|
257
|
-
});
|
|
258
|
-
addStringIssue(issues, value, "task");
|
|
259
|
-
addStringIssue(issues, value, "outcome");
|
|
260
|
-
if ("appliesTo" in value) if (!isRecord(value.appliesTo)) issues.push({
|
|
261
|
-
path: "agent.appliesTo",
|
|
262
|
-
message: "must be an object"
|
|
263
|
-
});
|
|
264
|
-
else {
|
|
265
|
-
addUnknownKeyIssues(issues, value.appliesTo, APPLIES_TO_FIELDS, "agent.appliesTo");
|
|
266
|
-
for (const field of [
|
|
267
|
-
"framework",
|
|
268
|
-
"version",
|
|
269
|
-
"package"
|
|
270
|
-
]) {
|
|
271
|
-
if (!(field in value.appliesTo)) continue;
|
|
272
|
-
const fieldValue = value.appliesTo[field];
|
|
273
|
-
if (!normalizeStringOrList(fieldValue)) {
|
|
274
|
-
issues.push({
|
|
275
|
-
path: `agent.appliesTo.${field}`,
|
|
276
|
-
message: "must be a non-empty string or string array"
|
|
277
|
-
});
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (Array.isArray(fieldValue)) fieldValue.forEach((item, index) => {
|
|
281
|
-
if (!normalizeString(item)) issues.push({
|
|
282
|
-
path: `agent.appliesTo.${field}[${index}]`,
|
|
283
|
-
message: "must be a non-empty string"
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
if (!normalizeAppliesTo(value.appliesTo)) issues.push({
|
|
288
|
-
path: "agent.appliesTo",
|
|
289
|
-
message: "must include framework, version, or package"
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
for (const field of [
|
|
293
|
-
"prerequisites",
|
|
294
|
-
"files",
|
|
295
|
-
"sideEffects",
|
|
296
|
-
"rollback"
|
|
297
|
-
]) addStringListIssues(issues, value, field);
|
|
298
|
-
if ("commands" in value) if (!Array.isArray(value.commands) || value.commands.length === 0) issues.push({
|
|
299
|
-
path: "agent.commands",
|
|
300
|
-
message: "must be a non-empty array"
|
|
301
|
-
});
|
|
302
|
-
else value.commands.forEach((command, index) => {
|
|
303
|
-
if (normalizeString(command)) return;
|
|
304
|
-
if (!isRecord(command)) {
|
|
305
|
-
issues.push({
|
|
306
|
-
path: `agent.commands[${index}]`,
|
|
307
|
-
message: "must be a command string or an object with a non-empty run field"
|
|
308
|
-
});
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
addUnknownKeyIssues(issues, command, COMMAND_FIELDS, `agent.commands[${index}]`);
|
|
312
|
-
if (!normalizeString(command.run)) {
|
|
313
|
-
issues.push({
|
|
314
|
-
path: `agent.commands[${index}]`,
|
|
315
|
-
message: "must be a command string or an object with a non-empty run field"
|
|
316
|
-
});
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
for (const field of ["cwd", "description"]) if (field in command && !normalizeString(command[field])) issues.push({
|
|
320
|
-
path: `agent.commands[${index}].${field}`,
|
|
321
|
-
message: "must be a non-empty string"
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
if ("verification" in value) if (!Array.isArray(value.verification) || value.verification.length === 0) issues.push({
|
|
325
|
-
path: "agent.verification",
|
|
326
|
-
message: "must be a non-empty array"
|
|
327
|
-
});
|
|
328
|
-
else value.verification.forEach((step, index) => {
|
|
329
|
-
if (normalizeString(step)) return;
|
|
330
|
-
if (!isRecord(step)) {
|
|
331
|
-
issues.push({
|
|
332
|
-
path: `agent.verification[${index}]`,
|
|
333
|
-
message: "must be a string or an object with description, run, or expect"
|
|
334
|
-
});
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
addUnknownKeyIssues(issues, step, VERIFICATION_FIELDS, `agent.verification[${index}]`);
|
|
338
|
-
if (!normalizeVerification([step])) {
|
|
339
|
-
issues.push({
|
|
340
|
-
path: `agent.verification[${index}]`,
|
|
341
|
-
message: "must be a string or an object with description, run, or expect"
|
|
342
|
-
});
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
for (const field of [
|
|
346
|
-
"description",
|
|
347
|
-
"run",
|
|
348
|
-
"expect"
|
|
349
|
-
]) if (field in step && !normalizeString(step[field])) issues.push({
|
|
350
|
-
path: `agent.verification[${index}].${field}`,
|
|
351
|
-
message: "must be a non-empty string"
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
if ("failureModes" in value) if (!Array.isArray(value.failureModes) || value.failureModes.length === 0) issues.push({
|
|
355
|
-
path: "agent.failureModes",
|
|
356
|
-
message: "must be a non-empty array"
|
|
357
|
-
});
|
|
358
|
-
else value.failureModes.forEach((mode, index) => {
|
|
359
|
-
if (normalizeString(mode)) return;
|
|
360
|
-
if (!isRecord(mode)) {
|
|
361
|
-
issues.push({
|
|
362
|
-
path: `agent.failureModes[${index}]`,
|
|
363
|
-
message: "must be a string or an object with a non-empty symptom field"
|
|
364
|
-
});
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
addUnknownKeyIssues(issues, mode, FAILURE_MODE_FIELDS, `agent.failureModes[${index}]`);
|
|
368
|
-
if (!normalizeString(mode.symptom)) {
|
|
369
|
-
issues.push({
|
|
370
|
-
path: `agent.failureModes[${index}]`,
|
|
371
|
-
message: "must be a string or an object with a non-empty symptom field"
|
|
372
|
-
});
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
if ("resolution" in mode && !normalizeString(mode.resolution)) issues.push({
|
|
376
|
-
path: `agent.failureModes[${index}].resolution`,
|
|
377
|
-
message: "must be a non-empty string"
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
return issues;
|
|
381
|
-
}
|
|
382
|
-
function yamlString(value) {
|
|
383
|
-
return JSON.stringify(value);
|
|
384
|
-
}
|
|
385
|
-
function renderYamlStringList(lines, indent, key, values) {
|
|
386
|
-
lines.push(`${indent}${key}:`);
|
|
387
|
-
for (const value of values) lines.push(`${indent} - ${yamlString(value)}`);
|
|
388
|
-
}
|
|
389
|
-
function renderYamlObjectList(lines, indent, key, values, fields) {
|
|
390
|
-
lines.push(`${indent}${key}:`);
|
|
391
|
-
for (const value of values) {
|
|
392
|
-
if (typeof value === "string") {
|
|
393
|
-
lines.push(`${indent} - ${yamlString(value)}`);
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
const record = value;
|
|
397
|
-
const firstField = fields.find((field) => normalizeString(record[field]));
|
|
398
|
-
if (!firstField) continue;
|
|
399
|
-
lines.push(`${indent} - ${firstField}: ${yamlString(normalizeString(record[firstField]))}`);
|
|
400
|
-
for (const field of fields) {
|
|
401
|
-
const fieldValue = normalizeString(record[field]);
|
|
402
|
-
if (field === firstField || !fieldValue) continue;
|
|
403
|
-
lines.push(`${indent} ${field}: ${yamlString(fieldValue)}`);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
/** Render normalized `agent` YAML in a stable field order. */
|
|
408
|
-
function renderPageAgentFrontmatterYamlLines(value, indentation = 0) {
|
|
409
|
-
const agent = normalizePageAgentFrontmatter(value);
|
|
410
|
-
if (!agent) return [];
|
|
411
|
-
const indent = " ".repeat(Math.max(0, indentation));
|
|
412
|
-
const child = `${indent} `;
|
|
413
|
-
const lines = [`${indent}agent:`];
|
|
414
|
-
if (agent.tokenBudget !== void 0) lines.push(`${child}tokenBudget: ${agent.tokenBudget}`);
|
|
415
|
-
if (agent.task) lines.push(`${child}task: ${yamlString(agent.task)}`);
|
|
416
|
-
if (agent.outcome) lines.push(`${child}outcome: ${yamlString(agent.outcome)}`);
|
|
417
|
-
if (agent.appliesTo) {
|
|
418
|
-
lines.push(`${child}appliesTo:`);
|
|
419
|
-
const appliesIndent = `${child} `;
|
|
420
|
-
for (const field of [
|
|
421
|
-
"framework",
|
|
422
|
-
"version",
|
|
423
|
-
"package"
|
|
424
|
-
]) {
|
|
425
|
-
const values = normalizeStringOrList(agent.appliesTo[field]);
|
|
426
|
-
if (values) renderYamlStringList(lines, appliesIndent, field, values);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (agent.prerequisites) renderYamlStringList(lines, child, "prerequisites", agent.prerequisites);
|
|
430
|
-
if (agent.files) renderYamlStringList(lines, child, "files", agent.files);
|
|
431
|
-
if (agent.commands) renderYamlObjectList(lines, child, "commands", agent.commands, [
|
|
432
|
-
"run",
|
|
433
|
-
"cwd",
|
|
434
|
-
"description"
|
|
435
|
-
]);
|
|
436
|
-
if (agent.sideEffects) renderYamlStringList(lines, child, "sideEffects", agent.sideEffects);
|
|
437
|
-
if (agent.verification) renderYamlObjectList(lines, child, "verification", agent.verification, [
|
|
438
|
-
"description",
|
|
439
|
-
"run",
|
|
440
|
-
"expect"
|
|
441
|
-
]);
|
|
442
|
-
if (agent.rollback) renderYamlStringList(lines, child, "rollback", agent.rollback);
|
|
443
|
-
if (agent.failureModes) renderYamlObjectList(lines, child, "failureModes", agent.failureModes, ["symptom", "resolution"]);
|
|
444
|
-
return lines;
|
|
445
|
-
}
|
|
446
|
-
function inlineCode(value) {
|
|
447
|
-
const longestRun = Math.max(0, ...[...value.matchAll(/`+/g)].map((match) => match[0].length));
|
|
448
|
-
const fence = "`".repeat(longestRun + 1);
|
|
449
|
-
const padding = value.startsWith("`") || value.endsWith("`") ? " " : "";
|
|
450
|
-
return `${fence}${padding}${value}${padding}${fence}`;
|
|
451
|
-
}
|
|
452
|
-
function renderTextList(lines, title, values, code = false) {
|
|
453
|
-
lines.push("", `### ${title}`, "");
|
|
454
|
-
for (const value of values) lines.push(`- ${code ? inlineCode(value) : value}`);
|
|
455
|
-
}
|
|
456
|
-
/** Render the structured contract as deterministic, compact machine-readable Markdown. */
|
|
457
|
-
function renderPageAgentContractMarkdown(value) {
|
|
458
|
-
const agent = normalizePageAgentFrontmatter(value);
|
|
459
|
-
if (!agent || !hasStructuredPageAgentContract(agent)) return "";
|
|
460
|
-
const lines = [PAGE_AGENT_CONTRACT_START_MARKER, "## Agent Contract"];
|
|
461
|
-
if (agent.task) lines.push("", `Task: ${agent.task}`);
|
|
462
|
-
if (agent.outcome) lines.push(`Outcome: ${agent.outcome}`);
|
|
463
|
-
if (agent.appliesTo) {
|
|
464
|
-
lines.push("", "### Applies To", "");
|
|
465
|
-
for (const [label, field] of [
|
|
466
|
-
["Framework", "framework"],
|
|
467
|
-
["Version", "version"],
|
|
468
|
-
["Package", "package"]
|
|
469
|
-
]) {
|
|
470
|
-
const values = normalizeStringOrList(agent.appliesTo[field]);
|
|
471
|
-
if (values) lines.push(`- ${label}: ${values.map(inlineCode).join(", ")}`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (agent.prerequisites) renderTextList(lines, "Prerequisites", agent.prerequisites);
|
|
475
|
-
if (agent.files) renderTextList(lines, "Files", agent.files, true);
|
|
476
|
-
if (agent.commands) {
|
|
477
|
-
lines.push("", "### Commands", "");
|
|
478
|
-
for (const command of agent.commands) {
|
|
479
|
-
if (typeof command === "string") {
|
|
480
|
-
lines.push(`- ${inlineCode(command)}`);
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
const details = [command.cwd ? `cwd ${inlineCode(command.cwd)}` : void 0, command.description].filter(Boolean);
|
|
484
|
-
lines.push(`- ${inlineCode(command.run)}${details.length ? ` — ${details.join("; ")}` : ""}`);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
if (agent.sideEffects) renderTextList(lines, "Side Effects", agent.sideEffects);
|
|
488
|
-
if (agent.verification) {
|
|
489
|
-
lines.push("", "### Verification", "");
|
|
490
|
-
for (const step of agent.verification) {
|
|
491
|
-
if (typeof step === "string") {
|
|
492
|
-
lines.push(`- ${step}`);
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
const summary = step.description ?? (step.run ? `Run ${inlineCode(step.run)}` : step.expect ?? "Verification step");
|
|
496
|
-
lines.push(`- ${summary}`);
|
|
497
|
-
if (step.run && step.description) lines.push(` - Run: ${inlineCode(step.run)}`);
|
|
498
|
-
if (step.expect && step.expect !== summary) lines.push(` - Expected: ${step.expect}`);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
if (agent.rollback) renderTextList(lines, "Rollback", agent.rollback);
|
|
502
|
-
if (agent.failureModes) {
|
|
503
|
-
lines.push("", "### Failure Modes", "");
|
|
504
|
-
for (const mode of agent.failureModes) {
|
|
505
|
-
if (typeof mode === "string") {
|
|
506
|
-
lines.push(`- ${mode}`);
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
lines.push(`- ${mode.symptom}${mode.resolution ? ` — Recovery: ${mode.resolution}` : ""}`);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
lines.push(PAGE_AGENT_CONTRACT_END_MARKER);
|
|
513
|
-
return lines.join("\n");
|
|
514
|
-
}
|
|
515
|
-
/** Remove generated contract blocks while preserving handwritten guidance. */
|
|
516
|
-
function stripGeneratedPageAgentContractMarkdown(markdown) {
|
|
517
|
-
const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
|
|
518
|
-
const output = [];
|
|
519
|
-
let fence;
|
|
520
|
-
let pendingBlock;
|
|
521
|
-
let skipEmptySeparator = false;
|
|
522
|
-
for (const line of markdown.split(/\r?\n/)) {
|
|
523
|
-
if (pendingBlock) {
|
|
524
|
-
pendingBlock.push(line);
|
|
525
|
-
if (isGeneratedAgentContractMarker(line, PAGE_AGENT_CONTRACT_END_MARKER)) {
|
|
526
|
-
pendingBlock = void 0;
|
|
527
|
-
skipEmptySeparator = true;
|
|
528
|
-
}
|
|
529
|
-
continue;
|
|
530
|
-
}
|
|
531
|
-
if (!fence && isGeneratedAgentContractMarker(line, PAGE_AGENT_CONTRACT_START_MARKER)) {
|
|
532
|
-
pendingBlock = [line];
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
535
|
-
if (skipEmptySeparator && line.length === 0) continue;
|
|
536
|
-
skipEmptySeparator = false;
|
|
537
|
-
output.push(line);
|
|
538
|
-
fence = advanceMarkdownFence(line, fence);
|
|
539
|
-
}
|
|
540
|
-
if (pendingBlock) output.push(...pendingBlock);
|
|
541
|
-
while (output[0] === "") output.shift();
|
|
542
|
-
return output.join(newline);
|
|
543
|
-
}
|
|
544
|
-
function isGeneratedAgentContractMarker(line, marker) {
|
|
545
|
-
if (line.trim() !== marker) return false;
|
|
546
|
-
const markerIndex = line.indexOf(marker);
|
|
547
|
-
return markerIndex <= 3 && /^ *$/.test(line.slice(0, markerIndex));
|
|
548
|
-
}
|
|
549
|
-
function advanceMarkdownFence(line, fence) {
|
|
550
|
-
if (fence) {
|
|
551
|
-
const closing = /^ {0,3}(`+|~+)[\t ]*$/.exec(line)?.[1];
|
|
552
|
-
if (closing?.[0] === fence.character && closing.length >= fence.length) return;
|
|
553
|
-
return fence;
|
|
554
|
-
}
|
|
555
|
-
const opening = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
556
|
-
if (!opening?.[1]) return void 0;
|
|
557
|
-
if (opening[1][0] === "`" && opening[2]?.includes("`")) return void 0;
|
|
558
|
-
return {
|
|
559
|
-
character: opening[1][0],
|
|
560
|
-
length: opening[1].length
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
function isAgentContractHeadingText(value) {
|
|
564
|
-
return value.trim().toLowerCase() === "agent contract";
|
|
565
|
-
}
|
|
566
|
-
function hasPageAgentContractHeading(markdown) {
|
|
567
|
-
let fence;
|
|
568
|
-
let previousLine;
|
|
569
|
-
for (const line of markdown.split(/\r?\n/)) {
|
|
570
|
-
const nextFence = advanceMarkdownFence(line, fence);
|
|
571
|
-
if (fence || nextFence) {
|
|
572
|
-
fence = nextFence;
|
|
573
|
-
previousLine = void 0;
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
const atxText = /^ {0,3}#{1,6}(?:[\t ]+|$)(.*)$/.exec(line)?.[1]?.replace(/[\t ]+#+[\t ]*$/, "").trim();
|
|
577
|
-
if (atxText && isAgentContractHeadingText(atxText)) return true;
|
|
578
|
-
if (previousLine && /^ {0,3}(?:=+|-+)[\t ]*$/.test(line) && isAgentContractHeadingText(previousLine)) return true;
|
|
579
|
-
previousLine = line.trim().length > 0 && !/^ {4}/.test(line) && !/^ {0,3}\t/.test(line) ? line : void 0;
|
|
580
|
-
}
|
|
581
|
-
return false;
|
|
582
|
-
}
|
|
583
|
-
/** Insert one generated contract unless the document already has handwritten contract guidance. */
|
|
584
|
-
function upsertPageAgentContractMarkdown(markdown, value) {
|
|
585
|
-
const cleaned = stripGeneratedPageAgentContractMarkdown(markdown);
|
|
586
|
-
const contract = renderPageAgentContractMarkdown(value);
|
|
587
|
-
if (!contract || hasPageAgentContractHeading(cleaned)) return cleaned;
|
|
588
|
-
return cleaned ? `${contract}\n\n${cleaned.replace(/^\r?\n+/, "")}` : contract;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
//#endregion
|
|
592
|
-
//#region src/related.ts
|
|
593
|
-
function normalizeDocsRelated(value) {
|
|
594
|
-
if (!Array.isArray(value)) return [];
|
|
595
|
-
const seen = /* @__PURE__ */ new Set();
|
|
596
|
-
const links = [];
|
|
597
|
-
for (const item of value) {
|
|
598
|
-
const parsed = parseRelatedInput(item);
|
|
599
|
-
if (!parsed) continue;
|
|
600
|
-
const dedupeKey = normalizeRelatedLookupPath(parsed.href) ?? parsed.href;
|
|
601
|
-
if (seen.has(dedupeKey)) continue;
|
|
602
|
-
seen.add(dedupeKey);
|
|
603
|
-
links.push({ href: parsed.href });
|
|
604
|
-
}
|
|
605
|
-
return links;
|
|
606
|
-
}
|
|
607
|
-
function renderDocsRelatedMarkdownLines(related) {
|
|
608
|
-
if (!related?.length) return [];
|
|
609
|
-
return [`Related: ${related.map((link) => normalizeInlineText(link.href)).join(", ")}`];
|
|
610
|
-
}
|
|
611
|
-
function parseRelatedInput(value) {
|
|
612
|
-
if (typeof value === "string") {
|
|
613
|
-
const href = cleanString(value);
|
|
614
|
-
return href ? { href } : null;
|
|
615
|
-
}
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
618
|
-
function cleanString(value) {
|
|
619
|
-
if (typeof value !== "string") return void 0;
|
|
620
|
-
return value.trim() || void 0;
|
|
621
|
-
}
|
|
622
|
-
function normalizeRelatedLookupPath(value) {
|
|
623
|
-
const trimmed = value.trim();
|
|
624
|
-
if (!trimmed) return null;
|
|
625
|
-
let pathname = trimmed;
|
|
626
|
-
try {
|
|
627
|
-
pathname = new URL(trimmed, "https://docs.local").pathname;
|
|
628
|
-
} catch {
|
|
629
|
-
pathname = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
|
|
630
|
-
}
|
|
631
|
-
pathname = pathname.replace(/\/+/g, "/").replace(/\.md$/i, "");
|
|
632
|
-
if (!pathname.startsWith("/")) pathname = `/${pathname}`;
|
|
633
|
-
if (pathname !== "/") pathname = pathname.replace(/\/+$/, "");
|
|
634
|
-
return pathname;
|
|
635
|
-
}
|
|
636
|
-
function normalizeInlineText(value) {
|
|
637
|
-
return value.replace(/\s+/g, " ").trim();
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
//#endregion
|
|
641
|
-
//#region src/standards-discovery.ts
|
|
642
|
-
const DEFAULT_API_CATALOG_ROUTE = "/.well-known/api-catalog";
|
|
643
|
-
const DEFAULT_API_CATALOG_FORMAT = "api-catalog";
|
|
644
|
-
const API_CATALOG_PROFILE_URI = "https://www.rfc-editor.org/info/rfc9727";
|
|
645
|
-
const API_CATALOG_MEDIA_TYPE = "application/linkset+json";
|
|
646
|
-
const DEFAULT_AGENT_SKILLS_INDEX_ROUTE = "/.well-known/agent-skills/index.json";
|
|
647
|
-
const DEFAULT_AGENT_SKILLS_ROUTE_PREFIX = "/.well-known/agent-skills";
|
|
648
|
-
const DEFAULT_AGENT_SKILLS_ROUTE_PATTERN = `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/{name}/SKILL.md`;
|
|
649
|
-
const DEFAULT_AGENT_SKILLS_INDEX_FORMAT = "agent-skills";
|
|
650
|
-
const DEFAULT_AGENT_SKILL_FORMAT = "agent-skill";
|
|
651
|
-
const AGENT_SKILLS_DISCOVERY_SCHEMA_URI = "https://schemas.agentskills.io/discovery/0.2.0/schema.json";
|
|
652
|
-
const DEFAULT_DOCS_API_ROUTE$1 = "/api/docs";
|
|
653
|
-
const DEFAULT_AGENT_MANIFEST_ROUTE = "/.well-known/agent.json";
|
|
654
|
-
const AGENT_SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
655
|
-
const AGENT_SKILL_NAME_MAX_LENGTH = 64;
|
|
656
|
-
const AGENT_SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
|
657
|
-
const DISCOVERY_CACHE_CONTROL = "public, max-age=0, s-maxage=3600";
|
|
658
|
-
function normalizeDocsRoute(value) {
|
|
659
|
-
const normalized = `/${value.trim()}`.replace(/\/{2,}/g, "/");
|
|
660
|
-
return normalized === "/" ? normalized : normalized.replace(/\/+$/, "");
|
|
661
|
-
}
|
|
662
|
-
/** Resolve the same-origin Docs API pathname used by query-form discovery routes. */
|
|
663
|
-
function resolveDocsDiscoveryApiRoute(apiRoute) {
|
|
664
|
-
const candidate = apiRoute?.trim().split(/[?#]/, 1)[0];
|
|
665
|
-
return normalizeDocsRoute(candidate || DEFAULT_DOCS_API_ROUTE$1);
|
|
666
|
-
}
|
|
667
|
-
function resolveHttpUrl(value, origin) {
|
|
668
|
-
if (!value) return null;
|
|
669
|
-
try {
|
|
670
|
-
const base = new URL(origin);
|
|
671
|
-
const resolved = new URL(value, `${base.origin}/`);
|
|
672
|
-
if (resolved.protocol !== "http:" && resolved.protocol !== "https:") return null;
|
|
673
|
-
return resolved.toString();
|
|
674
|
-
} catch {
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
function compactUniqueTargets(targets) {
|
|
679
|
-
const seen = /* @__PURE__ */ new Set();
|
|
680
|
-
return targets.filter((target) => {
|
|
681
|
-
if (!target) return false;
|
|
682
|
-
const key = `${target.href}\u0000${target.type ?? ""}\u0000${target.title ?? ""}`;
|
|
683
|
-
if (seen.has(key)) return false;
|
|
684
|
-
seen.add(key);
|
|
685
|
-
return true;
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
function toTarget(route, origin, type, title) {
|
|
689
|
-
const href = resolveHttpUrl(route, origin);
|
|
690
|
-
return href ? {
|
|
691
|
-
href,
|
|
692
|
-
...type ? { type } : {},
|
|
693
|
-
...title ? { title } : {}
|
|
694
|
-
} : null;
|
|
695
|
-
}
|
|
696
|
-
/** Build an RFC 9727 API Catalog using the RFC 9264 JSON Linkset representation. */
|
|
697
|
-
function buildDocsApiCatalog(options) {
|
|
698
|
-
const catalogUrl = resolveHttpUrl(DEFAULT_API_CATALOG_ROUTE, options.origin);
|
|
699
|
-
if (!catalogUrl) throw new Error(`Invalid HTTP(S) docs origin for API catalog: ${options.origin}`);
|
|
700
|
-
const apiRoute = options.apiRoute === null ? null : resolveDocsDiscoveryApiRoute(options.apiRoute);
|
|
701
|
-
const agentManifestRoute = options.agentManifestRoute === void 0 ? DEFAULT_AGENT_MANIFEST_ROUTE : options.agentManifestRoute;
|
|
702
|
-
const agentSkillsIndexRoute = options.agentSkillsIndexRoute === void 0 ? DEFAULT_AGENT_SKILLS_INDEX_ROUTE : options.agentSkillsIndexRoute;
|
|
703
|
-
const apiTargets = compactUniqueTargets([
|
|
704
|
-
toTarget(apiRoute, options.origin, "application/json", "Documentation API"),
|
|
705
|
-
...(options.apiRoutes ?? []).map((target) => toTarget(target.route, options.origin, target.type, target.title)),
|
|
706
|
-
toTarget(options.mcpRoute, options.origin, "application/json", "Documentation MCP endpoint"),
|
|
707
|
-
...(options.feedbackRoutes ?? []).map((route, index) => toTarget(route, options.origin, index === 0 ? "application/json" : "application/schema+json", index === 0 ? "Agent feedback endpoint" : "Agent feedback schema"))
|
|
708
|
-
]);
|
|
709
|
-
const serviceDocs = compactUniqueTargets([
|
|
710
|
-
toTarget(options.docsRoute ?? "/docs", options.origin, "text/html", "Documentation"),
|
|
711
|
-
toTarget(options.markdownRootRoute, options.origin, "text/markdown", "Documentation Markdown"),
|
|
712
|
-
toTarget(options.agentsRoute, options.origin, "text/markdown", "Agent instructions"),
|
|
713
|
-
toTarget(options.skillRoute, options.origin, "text/markdown", "Site skill"),
|
|
714
|
-
...(options.llmsRoutes ?? []).map((route) => toTarget(route, options.origin, "text/plain", "LLM documentation index")),
|
|
715
|
-
...(options.sitemapRoutes ?? []).map((route) => toTarget(route, options.origin, route.endsWith(".xml") ? "application/xml" : "text/markdown", "Documentation sitemap")),
|
|
716
|
-
toTarget(options.apiReferenceRoute, options.origin, "text/html", "API reference")
|
|
717
|
-
]);
|
|
718
|
-
const serviceMetadata = compactUniqueTargets([
|
|
719
|
-
toTarget(agentManifestRoute, options.origin, "application/json", "Agent discovery manifest"),
|
|
720
|
-
toTarget(agentSkillsIndexRoute, options.origin, "application/json", "Agent Skills discovery index"),
|
|
721
|
-
toTarget(options.configRoute, options.origin, "application/json", "Docs configuration map"),
|
|
722
|
-
toTarget(options.diagnosticsRoute, options.origin, "application/json", "Docs diagnostics"),
|
|
723
|
-
toTarget(options.robotsRoute, options.origin, "text/plain", "Robots policy")
|
|
724
|
-
]);
|
|
725
|
-
const serviceDescriptions = compactUniqueTargets([toTarget(options.openapiRoute, options.origin, "application/vnd.oai.openapi+json;version=3.1", "OpenAPI schema")]);
|
|
726
|
-
const catalogContext = {
|
|
727
|
-
anchor: catalogUrl,
|
|
728
|
-
"api-catalog": [{
|
|
729
|
-
href: catalogUrl,
|
|
730
|
-
type: API_CATALOG_MEDIA_TYPE,
|
|
731
|
-
title: "API catalog"
|
|
732
|
-
}]
|
|
733
|
-
};
|
|
734
|
-
if (apiTargets.length > 0) catalogContext.item = apiTargets;
|
|
735
|
-
if (serviceDocs.length > 0) catalogContext["service-doc"] = serviceDocs;
|
|
736
|
-
if (serviceMetadata.length > 0) catalogContext["service-meta"] = serviceMetadata;
|
|
737
|
-
if (serviceDescriptions.length > 0) catalogContext["service-desc"] = serviceDescriptions;
|
|
738
|
-
const linkset = [catalogContext];
|
|
739
|
-
for (const target of apiTargets) {
|
|
740
|
-
const context = { anchor: target.href };
|
|
741
|
-
if (serviceDocs.length > 0) context["service-doc"] = serviceDocs;
|
|
742
|
-
if (serviceMetadata.length > 0) context["service-meta"] = serviceMetadata;
|
|
743
|
-
if (serviceDescriptions.length > 0 && target.href === resolveHttpUrl(apiRoute, options.origin)) context["service-desc"] = serviceDescriptions;
|
|
744
|
-
linkset.push(context);
|
|
745
|
-
}
|
|
746
|
-
return { linkset };
|
|
747
|
-
}
|
|
748
|
-
function unquoteFrontmatterScalar(value) {
|
|
749
|
-
const trimmed = value.trim();
|
|
750
|
-
if (!trimmed || trimmed === ">" || trimmed === "|" || trimmed.startsWith("[") || trimmed.startsWith("{")) return null;
|
|
751
|
-
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) try {
|
|
752
|
-
const parsed = JSON.parse(trimmed);
|
|
753
|
-
return typeof parsed === "string" ? parsed.trim() : null;
|
|
754
|
-
} catch {
|
|
755
|
-
return null;
|
|
756
|
-
}
|
|
757
|
-
if (trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1).replace(/''/g, "'").trim();
|
|
758
|
-
return trimmed.replace(/\s+#.*$/, "").trim();
|
|
759
|
-
}
|
|
760
|
-
function readAgentSkillFrontmatter(document) {
|
|
761
|
-
const match = (document.startsWith("") ? document.slice(1) : document).match(/^---[\t ]*\r?\n([\s\S]*?)\r?\n---(?:[\t ]*\r?\n|$)/);
|
|
762
|
-
if (!match) return null;
|
|
763
|
-
const values = /* @__PURE__ */ new Map();
|
|
764
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
765
|
-
const field = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*?)\s*$/);
|
|
766
|
-
if (!field) continue;
|
|
767
|
-
const value = unquoteFrontmatterScalar(field[2]);
|
|
768
|
-
if (value !== null) values.set(field[1], value);
|
|
769
|
-
}
|
|
770
|
-
const name = values.get("name") ?? "";
|
|
771
|
-
const description = values.get("description") ?? "";
|
|
772
|
-
if (!name || name.length > AGENT_SKILL_NAME_MAX_LENGTH || !AGENT_SKILL_NAME_PATTERN.test(name) || !description || description.length > AGENT_SKILL_DESCRIPTION_MAX_LENGTH) return null;
|
|
773
|
-
return {
|
|
774
|
-
name,
|
|
775
|
-
description
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
async function sha256DocsDiscoveryContent(content) {
|
|
779
|
-
const subtle = globalThis.crypto?.subtle;
|
|
780
|
-
if (!subtle) throw new Error("Web Crypto SHA-256 support is required for Agent Skills discovery.");
|
|
781
|
-
const digest = await subtle.digest("SHA-256", new TextEncoder().encode(content));
|
|
782
|
-
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
783
|
-
}
|
|
784
|
-
/** Select a valid public skill and hash the exact bytes returned by its standards route. */
|
|
785
|
-
async function resolveDocsPublishedAgentSkill({ preferredDocument, fallbackDocument }) {
|
|
786
|
-
const preferredMetadata = preferredDocument ? readAgentSkillFrontmatter(preferredDocument) : null;
|
|
787
|
-
const fallbackMetadata = readAgentSkillFrontmatter(fallbackDocument);
|
|
788
|
-
const content = preferredMetadata ? preferredDocument : fallbackDocument;
|
|
789
|
-
const metadata = preferredMetadata ?? fallbackMetadata;
|
|
790
|
-
if (!metadata) throw new Error("The generated Agent Skills fallback must contain valid name and description frontmatter.");
|
|
791
|
-
const sha256 = await sha256DocsDiscoveryContent(content);
|
|
792
|
-
return {
|
|
793
|
-
name: metadata.name,
|
|
794
|
-
type: "skill-md",
|
|
795
|
-
description: metadata.description,
|
|
796
|
-
url: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${metadata.name}/SKILL.md`,
|
|
797
|
-
digest: `sha256:${sha256}`,
|
|
798
|
-
content,
|
|
799
|
-
sha256
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
function buildDocsAgentSkillsIndex(skills) {
|
|
803
|
-
return {
|
|
804
|
-
$schema: AGENT_SKILLS_DISCOVERY_SCHEMA_URI,
|
|
805
|
-
skills: (Array.isArray(skills) ? skills : [skills]).map(({ name, type, description, url, digest }) => ({
|
|
806
|
-
name,
|
|
807
|
-
type,
|
|
808
|
-
description,
|
|
809
|
-
url,
|
|
810
|
-
digest
|
|
811
|
-
})).sort((left, right) => left.name.localeCompare(right.name))
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
function resolveDocsStandardsDiscoveryRequest(url, options = {}) {
|
|
815
|
-
const pathname = normalizeDocsRoute(url.pathname);
|
|
816
|
-
if (pathname === DEFAULT_API_CATALOG_ROUTE) return { kind: "api-catalog" };
|
|
817
|
-
if (pathname === DEFAULT_AGENT_SKILLS_INDEX_ROUTE) return { kind: "agent-skills-index" };
|
|
818
|
-
const artifactMatch = pathname.match(/^\/\.well-known\/agent-skills\/([^/]+)\/SKILL\.md$/);
|
|
819
|
-
if (artifactMatch) try {
|
|
820
|
-
return {
|
|
821
|
-
kind: "agent-skill",
|
|
822
|
-
name: decodeURIComponent(artifactMatch[1])
|
|
823
|
-
};
|
|
824
|
-
} catch {
|
|
825
|
-
return {
|
|
826
|
-
kind: "agent-skill",
|
|
827
|
-
name: ""
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
if (pathname !== resolveDocsDiscoveryApiRoute(options.apiRoute)) return null;
|
|
831
|
-
const format = url.searchParams.get("format")?.trim();
|
|
832
|
-
if (format === DEFAULT_API_CATALOG_FORMAT) return { kind: "api-catalog" };
|
|
833
|
-
if (format === DEFAULT_AGENT_SKILLS_INDEX_FORMAT) return { kind: "agent-skills-index" };
|
|
834
|
-
if (format === DEFAULT_AGENT_SKILL_FORMAT) return {
|
|
835
|
-
kind: "agent-skill",
|
|
836
|
-
name: url.searchParams.get("name")?.trim() ?? ""
|
|
837
|
-
};
|
|
838
|
-
return null;
|
|
839
|
-
}
|
|
840
|
-
function isDocsStandardsDiscoveryRequest(url, options = {}) {
|
|
841
|
-
return resolveDocsStandardsDiscoveryRequest(url, options) !== null;
|
|
842
|
-
}
|
|
843
|
-
function formatLinkTarget(href, relation, options = {}) {
|
|
844
|
-
return [
|
|
845
|
-
`<${href}>`,
|
|
846
|
-
`rel="${relation}"`,
|
|
847
|
-
...options.type ? [`type="${options.type}"`] : [],
|
|
848
|
-
...options.profile ? [`profile="${options.profile}"`] : []
|
|
849
|
-
].join("; ");
|
|
850
|
-
}
|
|
851
|
-
/** Cross-link the standards endpoints without replacing an existing canonical Link value. */
|
|
852
|
-
function getDocsDiscoveryLinkHeader(options = {}) {
|
|
853
|
-
return [
|
|
854
|
-
...options.includeApiCatalog === false ? [] : [formatLinkTarget(DEFAULT_API_CATALOG_ROUTE, "api-catalog", {
|
|
855
|
-
type: API_CATALOG_MEDIA_TYPE,
|
|
856
|
-
profile: API_CATALOG_PROFILE_URI
|
|
857
|
-
})],
|
|
858
|
-
...options.includeManifest === false ? [] : [formatLinkTarget(DEFAULT_AGENT_MANIFEST_ROUTE, "service-meta", { type: "application/json" })],
|
|
859
|
-
...options.includeSkills === false ? [] : [formatLinkTarget(DEFAULT_AGENT_SKILLS_INDEX_ROUTE, "service-meta", { type: "application/json" })]
|
|
860
|
-
].join(", ");
|
|
861
|
-
}
|
|
862
|
-
function appendDocsDiscoveryLinkHeader(headers, value) {
|
|
863
|
-
const next = new Headers(headers);
|
|
864
|
-
const discoveryLinks = value ?? getDocsDiscoveryLinkHeader();
|
|
865
|
-
const existing = next.get("Link");
|
|
866
|
-
next.set("Link", existing ? `${existing}, ${discoveryLinks}` : discoveryLinks);
|
|
867
|
-
return next;
|
|
868
|
-
}
|
|
869
|
-
function requestMatchesEtag(request, etag) {
|
|
870
|
-
const value = request.headers.get("if-none-match");
|
|
871
|
-
if (!value) return false;
|
|
872
|
-
if (value.trim() === "*") return true;
|
|
873
|
-
return value.split(",").some((candidate) => candidate.trim().replace(/^W\//i, "") === etag);
|
|
874
|
-
}
|
|
875
|
-
async function createHashedDiscoveryResponse(options) {
|
|
876
|
-
const etag = `"${options.sha256 ?? await sha256DocsDiscoveryContent(options.content)}"`;
|
|
877
|
-
const headers = {
|
|
878
|
-
"Access-Control-Allow-Origin": "*",
|
|
879
|
-
"Access-Control-Expose-Headers": "ETag, Link",
|
|
880
|
-
Allow: "GET, HEAD",
|
|
881
|
-
"Cache-Control": DISCOVERY_CACHE_CONTROL,
|
|
882
|
-
"Content-Type": options.contentType,
|
|
883
|
-
ETag: etag,
|
|
884
|
-
Link: options.linkHeader,
|
|
885
|
-
"X-Robots-Tag": "noindex"
|
|
886
|
-
};
|
|
887
|
-
if (requestMatchesEtag(options.request, etag)) {
|
|
888
|
-
const { "Content-Type": _contentType, ...notModifiedHeaders } = headers;
|
|
889
|
-
return new Response(null, {
|
|
890
|
-
status: 304,
|
|
891
|
-
headers: notModifiedHeaders
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
return new Response(options.request.method.toUpperCase() === "HEAD" ? null : options.content, { headers });
|
|
895
|
-
}
|
|
896
|
-
function standardsNotFoundResponse(request, linkHeader) {
|
|
897
|
-
return new Response(request.method.toUpperCase() === "HEAD" ? null : "Not Found", {
|
|
898
|
-
status: 404,
|
|
899
|
-
headers: {
|
|
900
|
-
"Access-Control-Allow-Origin": "*",
|
|
901
|
-
"Access-Control-Expose-Headers": "Link",
|
|
902
|
-
"Cache-Control": "no-store",
|
|
903
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
904
|
-
Link: linkHeader,
|
|
905
|
-
"X-Robots-Tag": "noindex"
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
function standardsMethodNotAllowedResponse(linkHeader) {
|
|
910
|
-
return new Response("Method Not Allowed", {
|
|
911
|
-
status: 405,
|
|
912
|
-
headers: {
|
|
913
|
-
"Access-Control-Allow-Origin": "*",
|
|
914
|
-
"Access-Control-Expose-Headers": "Link",
|
|
915
|
-
Allow: "GET, HEAD",
|
|
916
|
-
"Cache-Control": "no-store",
|
|
917
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
918
|
-
Link: linkHeader,
|
|
919
|
-
"X-Robots-Tag": "noindex"
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
/** Return one RFC 9727 or Agent Skills discovery response, or null for unrelated requests. */
|
|
924
|
-
async function createDocsStandardsResponse({ request, apiCatalog, apiCatalogEnabled = true, apiRoute, preferredSkillDocument, fallbackSkillDocument }) {
|
|
925
|
-
const resolved = resolveDocsStandardsDiscoveryRequest(new URL(request.url), { apiRoute });
|
|
926
|
-
if (!resolved) return null;
|
|
927
|
-
const linkHeader = getDocsDiscoveryLinkHeader({ includeApiCatalog: apiCatalogEnabled });
|
|
928
|
-
if (resolved.kind === "api-catalog" && (!apiCatalogEnabled || !apiCatalog)) return standardsNotFoundResponse(request, linkHeader);
|
|
929
|
-
const method = request.method.toUpperCase();
|
|
930
|
-
if (method !== "GET" && method !== "HEAD") return standardsMethodNotAllowedResponse(linkHeader);
|
|
931
|
-
if (resolved.kind === "api-catalog") return createHashedDiscoveryResponse({
|
|
932
|
-
request,
|
|
933
|
-
content: `${JSON.stringify(apiCatalog, null, 2)}\n`,
|
|
934
|
-
contentType: `${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"; charset=utf-8`,
|
|
935
|
-
linkHeader
|
|
936
|
-
});
|
|
937
|
-
const skill = await resolveDocsPublishedAgentSkill({
|
|
938
|
-
preferredDocument: preferredSkillDocument,
|
|
939
|
-
fallbackDocument: fallbackSkillDocument
|
|
940
|
-
});
|
|
941
|
-
if (resolved.kind === "agent-skill") {
|
|
942
|
-
if (resolved.name !== skill.name) return standardsNotFoundResponse(request, linkHeader);
|
|
943
|
-
return createHashedDiscoveryResponse({
|
|
944
|
-
request,
|
|
945
|
-
content: skill.content,
|
|
946
|
-
contentType: "text/markdown; charset=utf-8",
|
|
947
|
-
sha256: skill.sha256,
|
|
948
|
-
linkHeader: [linkHeader, formatLinkTarget(DEFAULT_AGENT_SKILLS_INDEX_ROUTE, "collection", { type: "application/json" })].join(", ")
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
return createHashedDiscoveryResponse({
|
|
952
|
-
request,
|
|
953
|
-
content: `${JSON.stringify(buildDocsAgentSkillsIndex(skill), null, 2)}\n`,
|
|
954
|
-
contentType: "application/json; charset=utf-8",
|
|
955
|
-
linkHeader: [linkHeader, formatLinkTarget(skill.url, "item", { type: "text/markdown" })].join(", ")
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
//#endregion
|
|
960
|
-
//#region src/sitemap.ts
|
|
961
|
-
const DEFAULT_SITEMAP_XML_ROUTE = "/sitemap.xml";
|
|
962
|
-
const DEFAULT_SITEMAP_MD_ROUTE = "/sitemap.md";
|
|
963
|
-
const DEFAULT_SITEMAP_MD_DOCS_ROUTE = "/docs/sitemap.md";
|
|
964
|
-
const DEFAULT_SITEMAP_MD_WELL_KNOWN_ROUTE = "/.well-known/sitemap.md";
|
|
965
|
-
const DEFAULT_SITEMAP_MANIFEST_PATH = ".farming-labs/sitemap-manifest.json";
|
|
966
|
-
function normalizeUrlPath(value) {
|
|
967
|
-
const normalized = value.replace(/\/+/g, "/");
|
|
968
|
-
if (normalized === "/") return normalized;
|
|
969
|
-
return normalized.replace(/\/+$/, "");
|
|
970
|
-
}
|
|
971
|
-
function normalizeRoutePrefix(value) {
|
|
972
|
-
if (!value) return "";
|
|
973
|
-
const normalized = normalizeUrlPath(`/${value.replace(/^\/+|\/+$/g, "")}`);
|
|
974
|
-
return normalized === "/" ? "" : normalized;
|
|
975
|
-
}
|
|
976
|
-
function joinRoute(prefix, route) {
|
|
977
|
-
return normalizeUrlPath(`${prefix}/${route.replace(/^\/+/, "")}`);
|
|
978
|
-
}
|
|
979
|
-
function normalizeDateOnly(value) {
|
|
980
|
-
if (!value) return void 0;
|
|
981
|
-
const trimmed = value.trim();
|
|
982
|
-
if (!trimmed) return void 0;
|
|
983
|
-
const dateOnly = trimmed.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
984
|
-
if (dateOnly) return dateOnly[1];
|
|
985
|
-
const parsed = new Date(trimmed);
|
|
986
|
-
if (Number.isNaN(parsed.getTime())) return void 0;
|
|
987
|
-
return parsed.toISOString().slice(0, 10);
|
|
988
|
-
}
|
|
989
|
-
function normalizeBaseUrl(value) {
|
|
990
|
-
if (!value) return void 0;
|
|
991
|
-
return value.trim().replace(/\/+$/, "") || void 0;
|
|
992
|
-
}
|
|
993
|
-
function isEnabledObject(config) {
|
|
994
|
-
if (config === false) return false;
|
|
995
|
-
if (config === void 0) return true;
|
|
996
|
-
if (config === true) return true;
|
|
997
|
-
return config.enabled !== false;
|
|
998
|
-
}
|
|
999
|
-
function resolveDocsSitemapConfig(sitemap, defaults = {}) {
|
|
1000
|
-
const objectConfig = sitemap && typeof sitemap === "object" ? sitemap : {};
|
|
1001
|
-
const routePrefix = normalizeRoutePrefix(objectConfig.routePrefix);
|
|
1002
|
-
const xmlConfig = objectConfig.xml;
|
|
1003
|
-
const markdownConfig = objectConfig.markdown;
|
|
1004
|
-
const xmlEnabled = xmlConfig === false ? false : typeof xmlConfig === "object" ? xmlConfig.enabled ?? true : true;
|
|
1005
|
-
const markdownEnabled = markdownConfig === false ? false : typeof markdownConfig === "object" ? markdownConfig.enabled ?? true : true;
|
|
1006
|
-
return {
|
|
1007
|
-
enabled: isEnabledObject(sitemap),
|
|
1008
|
-
routePrefix,
|
|
1009
|
-
baseUrl: normalizeBaseUrl(objectConfig.baseUrl ?? defaults.baseUrl),
|
|
1010
|
-
manifestPath: objectConfig.manifestPath ?? DEFAULT_SITEMAP_MANIFEST_PATH,
|
|
1011
|
-
xml: {
|
|
1012
|
-
enabled: xmlEnabled,
|
|
1013
|
-
includeLastmod: typeof xmlConfig === "object" ? xmlConfig.includeLastmod ?? true : true,
|
|
1014
|
-
route: joinRoute(routePrefix, DEFAULT_SITEMAP_XML_ROUTE)
|
|
1015
|
-
},
|
|
1016
|
-
markdown: {
|
|
1017
|
-
enabled: markdownEnabled,
|
|
1018
|
-
includeDescriptions: typeof markdownConfig === "object" ? markdownConfig.includeDescriptions ?? true : true,
|
|
1019
|
-
includeLastmod: typeof markdownConfig === "object" ? markdownConfig.includeLastmod ?? true : true,
|
|
1020
|
-
linkTarget: typeof markdownConfig === "object" ? markdownConfig.linkTarget ?? "both" : "both",
|
|
1021
|
-
route: joinRoute(routePrefix, DEFAULT_SITEMAP_MD_ROUTE),
|
|
1022
|
-
docsRoute: routePrefix ? void 0 : DEFAULT_SITEMAP_MD_DOCS_ROUTE,
|
|
1023
|
-
wellKnownRoute: joinRoute(routePrefix, DEFAULT_SITEMAP_MD_WELL_KNOWN_ROUTE)
|
|
1024
|
-
}
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
function resolveDocsSitemapRequest(url, sitemap, options = {}) {
|
|
1028
|
-
const pathname = normalizeUrlPath(url.pathname);
|
|
1029
|
-
const format = url.searchParams.get("format")?.trim();
|
|
1030
|
-
if (pathname === resolveDocsDiscoveryApiRoute(options.apiRoute)) {
|
|
1031
|
-
if (format === "sitemap-xml") return "xml";
|
|
1032
|
-
if (format === "sitemap-md" || format === "sitemap-markdown") return "markdown";
|
|
1033
|
-
}
|
|
1034
|
-
const resolved = resolveDocsSitemapConfig(sitemap);
|
|
1035
|
-
if (!resolved.enabled) return null;
|
|
1036
|
-
if (resolved.xml.enabled && pathname === resolved.xml.route) return "xml";
|
|
1037
|
-
if (resolved.markdown.enabled && (pathname === resolved.markdown.route || pathname === resolved.markdown.docsRoute || pathname === resolved.markdown.wellKnownRoute)) return "markdown";
|
|
1038
|
-
return null;
|
|
1039
|
-
}
|
|
1040
|
-
function toDocsSitemapMarkdownUrl(url) {
|
|
1041
|
-
const normalized = normalizeUrlPath(url);
|
|
1042
|
-
return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
1043
|
-
}
|
|
1044
|
-
function absolutizeUrl(baseUrl, url) {
|
|
1045
|
-
if (/^https?:\/\//i.test(url)) return url;
|
|
1046
|
-
const base = normalizeBaseUrl(baseUrl);
|
|
1047
|
-
return base ? `${base}${url.startsWith("/") ? url : `/${url}`}` : url;
|
|
1048
|
-
}
|
|
1049
|
-
function escapeXml(value) {
|
|
1050
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1051
|
-
}
|
|
1052
|
-
function hashString(value) {
|
|
1053
|
-
let hash = 14695981039346656037n;
|
|
1054
|
-
const bytes = new TextEncoder().encode(value);
|
|
1055
|
-
for (const byte of bytes) {
|
|
1056
|
-
hash ^= BigInt(byte);
|
|
1057
|
-
hash = BigInt.asUintN(64, hash * 1099511628211n);
|
|
1058
|
-
}
|
|
1059
|
-
return `"fnv1a64-${hash.toString(16).padStart(16, "0")}"`;
|
|
1060
|
-
}
|
|
1061
|
-
function newestLastmod(pages) {
|
|
1062
|
-
let newest;
|
|
1063
|
-
for (const page of pages) {
|
|
1064
|
-
const date = normalizeDateOnly(page.lastmod);
|
|
1065
|
-
if (!date) continue;
|
|
1066
|
-
if (!newest || date > newest) newest = date;
|
|
1067
|
-
}
|
|
1068
|
-
return newest;
|
|
1069
|
-
}
|
|
1070
|
-
function titleize(value) {
|
|
1071
|
-
return value.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1072
|
-
}
|
|
1073
|
-
function sitemapSectionName(entry, pageUrl) {
|
|
1074
|
-
const normalizedEntry = `/${entry.replace(/^\/+|\/+$/g, "") || "docs"}`;
|
|
1075
|
-
const normalizedUrl = normalizeUrlPath(pageUrl);
|
|
1076
|
-
if (normalizedUrl === normalizedEntry) return "Overview";
|
|
1077
|
-
const firstSegment = (normalizedUrl.startsWith(`${normalizedEntry}/`) ? normalizedUrl.slice(normalizedEntry.length + 1) : normalizedUrl.replace(/^\/+/, "")).split("/").filter(Boolean)[0];
|
|
1078
|
-
return firstSegment ? titleize(firstSegment) : "Pages";
|
|
1079
|
-
}
|
|
1080
|
-
function buildDocsSitemapManifest(options) {
|
|
1081
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
1082
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1083
|
-
const pages = [];
|
|
1084
|
-
for (const page of options.pages) {
|
|
1085
|
-
const url = normalizeUrlPath(page.url);
|
|
1086
|
-
if (seen.has(url)) continue;
|
|
1087
|
-
seen.add(url);
|
|
1088
|
-
const resolvedLastmod = options.resolveLastmod?.(page);
|
|
1089
|
-
const fallbackLastmod = normalizeDateOnly(page.lastmod ?? page.lastModified);
|
|
1090
|
-
const lastmod = normalizeDateOnly(resolvedLastmod?.lastmod) ?? fallbackLastmod;
|
|
1091
|
-
pages.push({
|
|
1092
|
-
url,
|
|
1093
|
-
absoluteUrl: absolutizeUrl(baseUrl, url),
|
|
1094
|
-
markdownUrl: page.markdownUrl ?? toDocsSitemapMarkdownUrl(url),
|
|
1095
|
-
title: page.title,
|
|
1096
|
-
description: page.description,
|
|
1097
|
-
sourcePath: page.sourcePath,
|
|
1098
|
-
lastmod,
|
|
1099
|
-
lastmodSource: resolvedLastmod?.lastmodSource ?? (lastmod ? "unknown" : void 0),
|
|
1100
|
-
related: page.related?.map((link) => link.href)
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
pages.sort((left, right) => left.url.localeCompare(right.url));
|
|
1104
|
-
return {
|
|
1105
|
-
version: 1,
|
|
1106
|
-
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1107
|
-
baseUrl,
|
|
1108
|
-
entry: options.entry ?? "docs",
|
|
1109
|
-
siteTitle: options.siteTitle,
|
|
1110
|
-
pages
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
function renderDocsSitemapXml(manifest, options = {}) {
|
|
1114
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl ?? manifest.baseUrl);
|
|
1115
|
-
const includeLastmod = options.includeLastmod ?? true;
|
|
1116
|
-
const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"];
|
|
1117
|
-
for (const page of manifest.pages) {
|
|
1118
|
-
lines.push(" <url>");
|
|
1119
|
-
lines.push(` <loc>${escapeXml(page.absoluteUrl ?? absolutizeUrl(baseUrl, page.url))}</loc>`);
|
|
1120
|
-
const lastmod = normalizeDateOnly(page.lastmod);
|
|
1121
|
-
if (includeLastmod && lastmod) lines.push(` <lastmod>${lastmod}</lastmod>`);
|
|
1122
|
-
lines.push(" </url>");
|
|
1123
|
-
}
|
|
1124
|
-
lines.push("</urlset>");
|
|
1125
|
-
return `${lines.join("\n")}\n`;
|
|
1126
|
-
}
|
|
1127
|
-
function renderDocsSitemapMarkdown(manifest, options = {}) {
|
|
1128
|
-
const siteTitle = manifest.siteTitle ?? "Documentation";
|
|
1129
|
-
const includeDescriptions = options.includeDescriptions ?? true;
|
|
1130
|
-
const includeLastmod = options.includeLastmod ?? true;
|
|
1131
|
-
const linkTarget = options.linkTarget ?? "both";
|
|
1132
|
-
const lines = [`# ${siteTitle} Sitemap`, ""];
|
|
1133
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl ?? manifest.baseUrl);
|
|
1134
|
-
if (baseUrl) lines.push(`Base URL: ${baseUrl}`);
|
|
1135
|
-
lines.push(`Docs entry: /${manifest.entry.replace(/^\/+|\/+$/g, "") || "docs"}`);
|
|
1136
|
-
lines.push(`Generated: ${normalizeDateOnly(manifest.generatedAt) ?? manifest.generatedAt}`, "");
|
|
1137
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1138
|
-
for (const page of manifest.pages) {
|
|
1139
|
-
const sectionName = sitemapSectionName(manifest.entry, page.url);
|
|
1140
|
-
groups.set(sectionName, [...groups.get(sectionName) ?? [], page]);
|
|
1141
|
-
}
|
|
1142
|
-
for (const [sectionName, pages] of groups) {
|
|
1143
|
-
lines.push(`## ${sectionName}`, "");
|
|
1144
|
-
for (const page of pages) {
|
|
1145
|
-
const primaryHref = linkTarget === "markdown" ? page.markdownUrl : page.url;
|
|
1146
|
-
lines.push(`- [${page.title}](${primaryHref})`);
|
|
1147
|
-
if (linkTarget === "both" || linkTarget === "markdown") lines.push(` Markdown: ${page.markdownUrl}`);
|
|
1148
|
-
if (includeDescriptions && page.description) lines.push(` Description: ${page.description}`);
|
|
1149
|
-
const lastmod = normalizeDateOnly(page.lastmod);
|
|
1150
|
-
if (includeLastmod && lastmod) lines.push(` Last updated: ${lastmod}`);
|
|
1151
|
-
if (page.related && page.related.length > 0) lines.push(` Related: ${page.related.join(", ")}`);
|
|
1152
|
-
lines.push("");
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
return lines.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
1156
|
-
}
|
|
1157
|
-
function readDocsSitemapManifestFromContentMap(contentMap) {
|
|
1158
|
-
if (!contentMap) return null;
|
|
1159
|
-
const raw = contentMap[`/${DEFAULT_SITEMAP_MANIFEST_PATH}`] ?? contentMap[DEFAULT_SITEMAP_MANIFEST_PATH];
|
|
1160
|
-
if (!raw) return null;
|
|
1161
|
-
try {
|
|
1162
|
-
return JSON.parse(raw);
|
|
1163
|
-
} catch {
|
|
1164
|
-
return null;
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
function resolveDocsSitemapPageLastmod(manifest, url) {
|
|
1168
|
-
if (!manifest) return void 0;
|
|
1169
|
-
const normalizedUrl = normalizeUrlPath(url);
|
|
1170
|
-
return normalizeDateOnly(manifest.pages.find((entry) => normalizeUrlPath(entry.url) === normalizedUrl)?.lastmod);
|
|
1171
|
-
}
|
|
1172
|
-
function createDocsSitemapResponse({ request, apiRoute, sitemap, entry = "docs", siteTitle, baseUrl, pages, manifest }) {
|
|
1173
|
-
const url = new URL(request.url);
|
|
1174
|
-
const format = resolveDocsSitemapRequest(url, sitemap, { apiRoute });
|
|
1175
|
-
if (!format) return null;
|
|
1176
|
-
const resolved = resolveDocsSitemapConfig(sitemap, { baseUrl });
|
|
1177
|
-
if (!resolved.enabled) return null;
|
|
1178
|
-
if (format === "xml" && !resolved.xml.enabled) return null;
|
|
1179
|
-
if (format === "markdown" && !resolved.markdown.enabled) return null;
|
|
1180
|
-
const nextManifest = manifest ?? buildDocsSitemapManifest({
|
|
1181
|
-
pages,
|
|
1182
|
-
entry,
|
|
1183
|
-
siteTitle,
|
|
1184
|
-
baseUrl: resolved.baseUrl ?? url.origin
|
|
1185
|
-
});
|
|
1186
|
-
const responseBaseUrl = resolved.baseUrl ?? nextManifest.baseUrl ?? url.origin;
|
|
1187
|
-
const body = format === "xml" ? renderDocsSitemapXml(nextManifest, {
|
|
1188
|
-
baseUrl: responseBaseUrl,
|
|
1189
|
-
includeLastmod: resolved.xml.includeLastmod
|
|
1190
|
-
}) : renderDocsSitemapMarkdown(nextManifest, {
|
|
1191
|
-
baseUrl: responseBaseUrl,
|
|
1192
|
-
includeDescriptions: resolved.markdown.includeDescriptions,
|
|
1193
|
-
includeLastmod: resolved.markdown.includeLastmod,
|
|
1194
|
-
linkTarget: resolved.markdown.linkTarget
|
|
1195
|
-
});
|
|
1196
|
-
const headers = new Headers({
|
|
1197
|
-
"Content-Type": format === "xml" ? "application/xml; charset=utf-8" : "text/markdown; charset=utf-8",
|
|
1198
|
-
"Cache-Control": "public, max-age=0, must-revalidate",
|
|
1199
|
-
ETag: hashString(body)
|
|
1200
|
-
});
|
|
1201
|
-
const lastModified = newestLastmod(nextManifest.pages);
|
|
1202
|
-
if (lastModified) headers.set("Last-Modified", (/* @__PURE__ */ new Date(`${lastModified}T00:00:00.000Z`)).toUTCString());
|
|
1203
|
-
if (request.headers.get("if-none-match") === headers.get("ETag")) return new Response(null, {
|
|
1204
|
-
status: 304,
|
|
1205
|
-
headers
|
|
1206
|
-
});
|
|
1207
|
-
return new Response(body, { headers });
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
//#endregion
|
|
1211
|
-
//#region src/audience.ts
|
|
1212
|
-
/** Resolve whether content with an optional audience restriction is visible. */
|
|
1213
|
-
function resolveDocsAudienceExposure(only, audience) {
|
|
1214
|
-
return only !== "human" && only !== "agent" || only === audience;
|
|
1215
|
-
}
|
|
1216
|
-
const DOCS_AUDIENCE_TAG_NAMES = [
|
|
1217
|
-
"Audience",
|
|
1218
|
-
"Agent",
|
|
1219
|
-
"Human"
|
|
1220
|
-
];
|
|
1221
|
-
const DOCS_AUDIENCE_TAG_CANDIDATE_PATTERN = /<\/?(?:Audience|Agent|Human)(?=[\s/>])/;
|
|
1222
|
-
function parseAudienceOnly(attributes) {
|
|
1223
|
-
if (/\{\s*\.\.\./.test(attributes)) return void 0;
|
|
1224
|
-
const value = attributes.match(/(?:^|\s)only\s*=\s*(?:"(human|agent)"|'(human|agent)'|\{\s*"(human|agent)"\s*\}|\{\s*'(human|agent)'\s*\})/)?.slice(1).find(Boolean);
|
|
1225
|
-
return value === "human" || value === "agent" ? value : void 0;
|
|
1226
|
-
}
|
|
1227
|
-
function isEscapedAt(content, index) {
|
|
1228
|
-
let backslashes = 0;
|
|
1229
|
-
for (let cursor = index - 1; cursor >= 0 && content[cursor] === "\\"; cursor -= 1) backslashes += 1;
|
|
1230
|
-
return backslashes % 2 === 1;
|
|
1231
|
-
}
|
|
1232
|
-
const JAVASCRIPT_REGEX_PREFIX_KEYWORDS = new Set([
|
|
1233
|
-
"await",
|
|
1234
|
-
"case",
|
|
1235
|
-
"delete",
|
|
1236
|
-
"do",
|
|
1237
|
-
"else",
|
|
1238
|
-
"each",
|
|
1239
|
-
"if",
|
|
1240
|
-
"in",
|
|
1241
|
-
"instanceof",
|
|
1242
|
-
"key",
|
|
1243
|
-
"new",
|
|
1244
|
-
"of",
|
|
1245
|
-
"return",
|
|
1246
|
-
"throw",
|
|
1247
|
-
"catch",
|
|
1248
|
-
"const",
|
|
1249
|
-
"debug",
|
|
1250
|
-
"html",
|
|
1251
|
-
"render",
|
|
1252
|
-
"snippet",
|
|
1253
|
-
"then",
|
|
1254
|
-
"typeof",
|
|
1255
|
-
"void",
|
|
1256
|
-
"yield"
|
|
1257
|
-
]);
|
|
1258
|
-
function canStartJavascriptRegex(content, cursor, boundary) {
|
|
1259
|
-
let previous = cursor - 1;
|
|
1260
|
-
while (previous > boundary && /\s/.test(content[previous] ?? "")) previous -= 1;
|
|
1261
|
-
if (previous <= boundary || /[([{=,:;!?&|>]/.test(content[previous] ?? "")) return true;
|
|
1262
|
-
const precedingWord = content.slice(boundary + 1, cursor).match(/([A-Za-z_$][\w$]*)\s*$/)?.[1];
|
|
1263
|
-
return JAVASCRIPT_REGEX_PREFIX_KEYWORDS.has(precedingWord ?? "");
|
|
1264
|
-
}
|
|
1265
|
-
function readAudienceTagAt(content, index) {
|
|
1266
|
-
let cursor = index + 1;
|
|
1267
|
-
const closing = content[cursor] === "/";
|
|
1268
|
-
if (closing) cursor += 1;
|
|
1269
|
-
const name = DOCS_AUDIENCE_TAG_NAMES.find((candidate) => content.startsWith(candidate, cursor) && /[\s/>]/.test(content[cursor + candidate.length] ?? ""));
|
|
1270
|
-
if (!name) return void 0;
|
|
1271
|
-
cursor += name.length;
|
|
1272
|
-
const attributesStart = cursor;
|
|
1273
|
-
let braceDepth = 0;
|
|
1274
|
-
let quote;
|
|
1275
|
-
let blockComment = false;
|
|
1276
|
-
let lineComment = false;
|
|
1277
|
-
while (cursor < content.length) {
|
|
1278
|
-
const character = content[cursor];
|
|
1279
|
-
const next = content[cursor + 1];
|
|
1280
|
-
if (lineComment) {
|
|
1281
|
-
if (character === "\n") lineComment = false;
|
|
1282
|
-
cursor += 1;
|
|
1283
|
-
continue;
|
|
1284
|
-
}
|
|
1285
|
-
if (blockComment) {
|
|
1286
|
-
if (character === "*" && next === "/") {
|
|
1287
|
-
blockComment = false;
|
|
1288
|
-
cursor += 2;
|
|
1289
|
-
} else cursor += 1;
|
|
1290
|
-
continue;
|
|
1291
|
-
}
|
|
1292
|
-
if (quote) {
|
|
1293
|
-
if (character === "\\") {
|
|
1294
|
-
cursor += 2;
|
|
1295
|
-
continue;
|
|
1296
|
-
}
|
|
1297
|
-
if (character === quote) quote = void 0;
|
|
1298
|
-
cursor += 1;
|
|
1299
|
-
continue;
|
|
1300
|
-
}
|
|
1301
|
-
if (braceDepth > 0 && character === "/" && next === "*") {
|
|
1302
|
-
blockComment = true;
|
|
1303
|
-
cursor += 2;
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
if (braceDepth > 0 && character === "/" && next === "/") {
|
|
1307
|
-
lineComment = true;
|
|
1308
|
-
cursor += 2;
|
|
1309
|
-
continue;
|
|
1310
|
-
}
|
|
1311
|
-
if (character === "\"" || character === "'" || character === "`") quote = character;
|
|
1312
|
-
else if (character === "{") braceDepth += 1;
|
|
1313
|
-
else if (character === "}" && braceDepth > 0) braceDepth -= 1;
|
|
1314
|
-
else if (character === ">" && braceDepth === 0) {
|
|
1315
|
-
let attributesEnd = cursor;
|
|
1316
|
-
let slashIndex = cursor - 1;
|
|
1317
|
-
while (slashIndex >= attributesStart && /\s/.test(content[slashIndex] ?? "")) slashIndex -= 1;
|
|
1318
|
-
const selfClosing = content[slashIndex] === "/";
|
|
1319
|
-
if (selfClosing) attributesEnd = slashIndex;
|
|
1320
|
-
const attributes = content.slice(attributesStart, attributesEnd);
|
|
1321
|
-
return {
|
|
1322
|
-
index,
|
|
1323
|
-
end: cursor + 1,
|
|
1324
|
-
name,
|
|
1325
|
-
closing,
|
|
1326
|
-
selfClosing,
|
|
1327
|
-
attributes,
|
|
1328
|
-
only: name === "Agent" ? "agent" : name === "Human" ? "human" : parseAudienceOnly(attributes)
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
cursor += 1;
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
function findAudienceTags(content, protectedRanges) {
|
|
1335
|
-
const tags = [];
|
|
1336
|
-
let cursor = 0;
|
|
1337
|
-
while (cursor < content.length) {
|
|
1338
|
-
const index = content.indexOf("<", cursor);
|
|
1339
|
-
if (index === -1) break;
|
|
1340
|
-
if (isEscapedAt(content, index) || isInsideRange(index, protectedRanges)) {
|
|
1341
|
-
cursor = index + 1;
|
|
1342
|
-
continue;
|
|
1343
|
-
}
|
|
1344
|
-
const tag = readAudienceTagAt(content, index);
|
|
1345
|
-
if (tag) {
|
|
1346
|
-
tags.push(tag);
|
|
1347
|
-
cursor = tag.end;
|
|
1348
|
-
} else cursor = index + 1;
|
|
1349
|
-
}
|
|
1350
|
-
return tags;
|
|
1351
|
-
}
|
|
1352
|
-
const docsAudienceMdxProcessor = createProcessor({ format: "mdx" });
|
|
1353
|
-
const docsAudienceMarkdownProcessor = createProcessor({ format: "md" });
|
|
1354
|
-
function getOffset(position, edge) {
|
|
1355
|
-
const value = position?.[edge]?.offset;
|
|
1356
|
-
return typeof value === "number" ? value : void 0;
|
|
1357
|
-
}
|
|
1358
|
-
function maskRangesForMdxParser(content, ranges) {
|
|
1359
|
-
if (ranges.length === 0) return content;
|
|
1360
|
-
const characters = content.split("");
|
|
1361
|
-
for (const range of ranges) for (let index = range.start; index < range.end; index += 1) if (characters[index] !== "\n" && characters[index] !== "\r") characters[index] = " ";
|
|
1362
|
-
return characters.join("");
|
|
1363
|
-
}
|
|
1364
|
-
function findLeadingFrontmatterRange(content) {
|
|
1365
|
-
const bomLength = content.startsWith("") ? 1 : 0;
|
|
1366
|
-
const opening = content.slice(bomLength).match(/^(---|\+\+\+)[\t ]*(?:\r?\n|$)/);
|
|
1367
|
-
const marker = opening?.[1];
|
|
1368
|
-
if (!opening || !marker) return void 0;
|
|
1369
|
-
let offset = bomLength + opening[0].length;
|
|
1370
|
-
for (const line of content.slice(offset).split(/(?<=\n)/)) {
|
|
1371
|
-
offset += line.length;
|
|
1372
|
-
const value = line.replace(/\r?\n$/, "");
|
|
1373
|
-
if (marker === "---" ? /^(?:---|\.\.\.)[\t ]*$/.test(value) : /^\+\+\+[\t ]*$/.test(value)) return {
|
|
1374
|
-
start: 0,
|
|
1375
|
-
end: offset
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
function findHtmlLiteralRanges(content, excludedRanges = []) {
|
|
1380
|
-
const ranges = [];
|
|
1381
|
-
const rawPattern = /<(script|style)(?=[\s/>])/g;
|
|
1382
|
-
let cursor = 0;
|
|
1383
|
-
while (cursor < content.length) {
|
|
1384
|
-
rawPattern.lastIndex = cursor;
|
|
1385
|
-
const rawMatch = rawPattern.exec(content);
|
|
1386
|
-
const commentStart = content.indexOf("<!--", cursor);
|
|
1387
|
-
const rawStart = rawMatch?.index ?? -1;
|
|
1388
|
-
const useComment = commentStart !== -1 && (rawStart === -1 || commentStart < rawStart);
|
|
1389
|
-
const start = useComment ? commentStart : rawStart;
|
|
1390
|
-
if (start === -1) break;
|
|
1391
|
-
if (isEscapedAt(content, start) || isInsideRange(start, excludedRanges)) {
|
|
1392
|
-
cursor = start + 1;
|
|
1393
|
-
continue;
|
|
1394
|
-
}
|
|
1395
|
-
if (useComment) {
|
|
1396
|
-
const closing = content.indexOf("-->", start + 4);
|
|
1397
|
-
const end = closing === -1 ? content.length : closing + 3;
|
|
1398
|
-
ranges.push({
|
|
1399
|
-
start,
|
|
1400
|
-
end
|
|
1401
|
-
});
|
|
1402
|
-
cursor = end;
|
|
1403
|
-
continue;
|
|
1404
|
-
}
|
|
1405
|
-
const name = rawMatch?.[1];
|
|
1406
|
-
if (!name) {
|
|
1407
|
-
cursor = start + 1;
|
|
1408
|
-
continue;
|
|
1409
|
-
}
|
|
1410
|
-
const opening = readGenericMdxJsxTag(content, start);
|
|
1411
|
-
if (!opening) {
|
|
1412
|
-
cursor = start + 1;
|
|
1413
|
-
continue;
|
|
1414
|
-
}
|
|
1415
|
-
if (opening.selfClosing) {
|
|
1416
|
-
cursor = opening.end;
|
|
1417
|
-
continue;
|
|
1418
|
-
}
|
|
1419
|
-
const closingPattern = new RegExp(`</${name}\\s*>`, "g");
|
|
1420
|
-
closingPattern.lastIndex = opening.end;
|
|
1421
|
-
if (!closingPattern.exec(content)) {
|
|
1422
|
-
ranges.push({
|
|
1423
|
-
start,
|
|
1424
|
-
end: content.length
|
|
1425
|
-
});
|
|
1426
|
-
break;
|
|
1427
|
-
}
|
|
1428
|
-
ranges.push({
|
|
1429
|
-
start,
|
|
1430
|
-
end: closingPattern.lastIndex
|
|
1431
|
-
});
|
|
1432
|
-
cursor = closingPattern.lastIndex;
|
|
1433
|
-
}
|
|
1434
|
-
return ranges;
|
|
1435
|
-
}
|
|
1436
|
-
function prepareMdxParserSource(content) {
|
|
1437
|
-
const baseExcludedRanges = findDelimiterExclusionRanges(content);
|
|
1438
|
-
const svelteDirectives = findSvelteDirectiveRanges(content, baseExcludedRanges);
|
|
1439
|
-
const ranges = [...findHtmlLiteralRanges(content, mergeProtectedRanges([...baseExcludedRanges, ...svelteDirectives])), ...svelteDirectives];
|
|
1440
|
-
const frontmatter = findLeadingFrontmatterRange(content);
|
|
1441
|
-
if (frontmatter) ranges.push(frontmatter);
|
|
1442
|
-
return maskRangesForMdxParser(content, mergeProtectedRanges(ranges));
|
|
1443
|
-
}
|
|
1444
|
-
function getExpressionLiteralString(value) {
|
|
1445
|
-
if (!value || typeof value !== "object") return void 0;
|
|
1446
|
-
const expression = value.data?.estree?.body?.[0]?.expression;
|
|
1447
|
-
return expression?.type === "Literal" && typeof expression.value === "string" ? expression.value : void 0;
|
|
1448
|
-
}
|
|
1449
|
-
function getMdxAudienceAttributeInfo(attributes) {
|
|
1450
|
-
const hasSpreadAttribute = attributes.some((attribute) => attribute.type === "mdxJsxExpressionAttribute");
|
|
1451
|
-
const onlyAttribute = [...attributes].reverse().find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "only");
|
|
1452
|
-
if (hasSpreadAttribute) return {
|
|
1453
|
-
hasOnlyAttribute: Boolean(onlyAttribute),
|
|
1454
|
-
hasSpreadAttribute,
|
|
1455
|
-
onlyKind: "dynamic"
|
|
1456
|
-
};
|
|
1457
|
-
if (!onlyAttribute) return {
|
|
1458
|
-
hasOnlyAttribute: false,
|
|
1459
|
-
hasSpreadAttribute: false,
|
|
1460
|
-
onlyKind: "missing"
|
|
1461
|
-
};
|
|
1462
|
-
const value = typeof onlyAttribute.value === "string" ? onlyAttribute.value : getExpressionLiteralString(onlyAttribute.value);
|
|
1463
|
-
if (value === "human" || value === "agent") return {
|
|
1464
|
-
only: value,
|
|
1465
|
-
hasOnlyAttribute: true,
|
|
1466
|
-
hasSpreadAttribute: false,
|
|
1467
|
-
onlyKind: "static"
|
|
1468
|
-
};
|
|
1469
|
-
return {
|
|
1470
|
-
hasOnlyAttribute: true,
|
|
1471
|
-
hasSpreadAttribute: false,
|
|
1472
|
-
onlyKind: typeof onlyAttribute.value === "object" ? "dynamic" : "invalid"
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
function getEstreeAudienceAttributeInfo(attributes) {
|
|
1476
|
-
const records = attributes.filter((attribute) => Boolean(attribute && typeof attribute === "object"));
|
|
1477
|
-
const hasSpreadAttribute = records.some((attribute) => attribute.type === "JSXSpreadAttribute");
|
|
1478
|
-
const onlyAttribute = [...records].reverse().find((attribute) => {
|
|
1479
|
-
const name = attribute.name;
|
|
1480
|
-
return attribute.type === "JSXAttribute" && name?.type === "JSXIdentifier" && name.name === "only";
|
|
1481
|
-
});
|
|
1482
|
-
if (hasSpreadAttribute) return {
|
|
1483
|
-
hasOnlyAttribute: Boolean(onlyAttribute),
|
|
1484
|
-
hasSpreadAttribute,
|
|
1485
|
-
onlyKind: "dynamic"
|
|
1486
|
-
};
|
|
1487
|
-
if (!onlyAttribute) return {
|
|
1488
|
-
hasOnlyAttribute: false,
|
|
1489
|
-
hasSpreadAttribute: false,
|
|
1490
|
-
onlyKind: "missing"
|
|
1491
|
-
};
|
|
1492
|
-
const attributeValue = onlyAttribute.value;
|
|
1493
|
-
const value = attributeValue?.type === "Literal" ? attributeValue.value : attributeValue?.type === "JSXExpressionContainer" && attributeValue.expression?.type === "Literal" ? attributeValue.expression.value : void 0;
|
|
1494
|
-
if (value === "human" || value === "agent") return {
|
|
1495
|
-
only: value,
|
|
1496
|
-
hasOnlyAttribute: true,
|
|
1497
|
-
hasSpreadAttribute: false,
|
|
1498
|
-
onlyKind: "static"
|
|
1499
|
-
};
|
|
1500
|
-
return {
|
|
1501
|
-
hasOnlyAttribute: true,
|
|
1502
|
-
hasSpreadAttribute: false,
|
|
1503
|
-
onlyKind: attributeValue?.type === "JSXExpressionContainer" ? "dynamic" : "invalid"
|
|
1504
|
-
};
|
|
1505
|
-
}
|
|
1506
|
-
function getOpeningTagEnd(content, start, name, attributes) {
|
|
1507
|
-
const searchStart = Math.max(start + name.length + 1, ...attributes.map((attribute) => getOffset(attribute.position, "end") ?? 0));
|
|
1508
|
-
const closingBracket = content.indexOf(">", searchStart);
|
|
1509
|
-
return closingBracket === -1 ? void 0 : closingBracket + 1;
|
|
1510
|
-
}
|
|
1511
|
-
function addMdxAudienceElementTags(content, node, tags) {
|
|
1512
|
-
const name = node.name;
|
|
1513
|
-
const start = getOffset(node.position, "start");
|
|
1514
|
-
const nodeEnd = getOffset(node.position, "end");
|
|
1515
|
-
if (start === void 0 || nodeEnd === void 0) return;
|
|
1516
|
-
const attributes = node.attributes ?? [];
|
|
1517
|
-
const openingEnd = getOpeningTagEnd(content, start, name, attributes);
|
|
1518
|
-
if (openingEnd === void 0) return;
|
|
1519
|
-
const closingStart = content.lastIndexOf(`</${name}`, nodeEnd);
|
|
1520
|
-
const hasClosingTag = closingStart >= openingEnd;
|
|
1521
|
-
const attributeInfo = getMdxAudienceAttributeInfo(attributes);
|
|
1522
|
-
const rawAttributes = content.slice(start + name.length + 1, openingEnd - 1);
|
|
1523
|
-
tags.push({
|
|
1524
|
-
index: start,
|
|
1525
|
-
end: openingEnd,
|
|
1526
|
-
name,
|
|
1527
|
-
closing: false,
|
|
1528
|
-
selfClosing: !hasClosingTag,
|
|
1529
|
-
attributes: rawAttributes,
|
|
1530
|
-
only: name === "Agent" ? "agent" : name === "Human" ? "human" : attributeInfo.only,
|
|
1531
|
-
hasOnlyAttribute: attributeInfo.hasOnlyAttribute,
|
|
1532
|
-
hasSpreadAttribute: attributeInfo.hasSpreadAttribute,
|
|
1533
|
-
onlyKind: attributeInfo.onlyKind
|
|
1534
|
-
});
|
|
1535
|
-
if (hasClosingTag) tags.push({
|
|
1536
|
-
index: closingStart,
|
|
1537
|
-
end: nodeEnd,
|
|
1538
|
-
name,
|
|
1539
|
-
closing: true,
|
|
1540
|
-
selfClosing: false,
|
|
1541
|
-
attributes: ""
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1544
|
-
function getEstreeJsxIdentifierName(value) {
|
|
1545
|
-
if (!value || typeof value !== "object") return void 0;
|
|
1546
|
-
const name = value;
|
|
1547
|
-
return name.type === "JSXIdentifier" && DOCS_AUDIENCE_TAG_NAMES.some((candidate) => candidate === name.name) ? name.name : void 0;
|
|
1548
|
-
}
|
|
1549
|
-
function addEstreeAudienceElementTags(content, node, tags, jsxChild) {
|
|
1550
|
-
const opening = node.openingElement;
|
|
1551
|
-
if (!opening) return;
|
|
1552
|
-
const name = getEstreeJsxIdentifierName(opening.name);
|
|
1553
|
-
const start = typeof opening.start === "number" ? opening.start : void 0;
|
|
1554
|
-
const openingEnd = typeof opening.end === "number" ? opening.end : void 0;
|
|
1555
|
-
const nodeEnd = typeof node.end === "number" ? node.end : void 0;
|
|
1556
|
-
if (!name || start === void 0 || openingEnd === void 0 || nodeEnd === void 0) return;
|
|
1557
|
-
const attributeInfo = getEstreeAudienceAttributeInfo(Array.isArray(opening.attributes) ? opening.attributes : []);
|
|
1558
|
-
const closing = node.closingElement;
|
|
1559
|
-
const closingStart = closing && typeof closing.start === "number" ? closing.start : void 0;
|
|
1560
|
-
const closingEnd = closing && typeof closing.end === "number" ? closing.end : void 0;
|
|
1561
|
-
tags.push({
|
|
1562
|
-
index: start,
|
|
1563
|
-
end: openingEnd,
|
|
1564
|
-
name,
|
|
1565
|
-
closing: false,
|
|
1566
|
-
selfClosing: closingStart === void 0,
|
|
1567
|
-
attributes: content.slice(start + name.length + 1, openingEnd - 1),
|
|
1568
|
-
only: name === "Agent" ? "agent" : name === "Human" ? "human" : attributeInfo.only,
|
|
1569
|
-
expression: true,
|
|
1570
|
-
jsxChild,
|
|
1571
|
-
hasOnlyAttribute: attributeInfo.hasOnlyAttribute,
|
|
1572
|
-
hasSpreadAttribute: attributeInfo.hasSpreadAttribute,
|
|
1573
|
-
onlyKind: attributeInfo.onlyKind
|
|
1574
|
-
});
|
|
1575
|
-
if (closingStart !== void 0 && closingEnd !== void 0) tags.push({
|
|
1576
|
-
index: closingStart,
|
|
1577
|
-
end: closingEnd,
|
|
1578
|
-
name,
|
|
1579
|
-
closing: true,
|
|
1580
|
-
selfClosing: false,
|
|
1581
|
-
attributes: "",
|
|
1582
|
-
expression: true,
|
|
1583
|
-
jsxChild
|
|
1584
|
-
});
|
|
1585
|
-
}
|
|
1586
|
-
function collectEstreeAudienceTags(content, value, tags, seen = /* @__PURE__ */ new WeakSet(), parent, parentKey) {
|
|
1587
|
-
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
1588
|
-
seen.add(value);
|
|
1589
|
-
const record = value;
|
|
1590
|
-
if (record.type === "JSXElement") {
|
|
1591
|
-
const opening = record.openingElement;
|
|
1592
|
-
if (getEstreeJsxIdentifierName(opening?.name)) {
|
|
1593
|
-
addEstreeAudienceElementTags(content, record, tags, parentKey === "children" && (parent?.type === "JSXElement" || parent?.type === "JSXFragment"));
|
|
1594
|
-
for (const child of Array.isArray(record.children) ? record.children : []) collectEstreeAudienceTags(content, child, tags, seen, record, "children");
|
|
1595
|
-
return;
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
for (const [key, child] of Object.entries(record)) {
|
|
1599
|
-
if (key === "loc" || key === "range" || key === "comments") continue;
|
|
1600
|
-
if (Array.isArray(child)) for (const item of child) collectEstreeAudienceTags(content, item, tags, seen, record, key);
|
|
1601
|
-
else collectEstreeAudienceTags(content, child, tags, seen, record, key);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
function collectMdxAudienceTags(content, node, tags) {
|
|
1605
|
-
const isMdxJsxElement = node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
|
|
1606
|
-
if (isMdxJsxElement && (node.name === "script" || node.name === "style")) return;
|
|
1607
|
-
const isAudienceElement = Boolean(isMdxJsxElement && node.name && DOCS_AUDIENCE_TAG_NAMES.some((candidate) => candidate === node.name));
|
|
1608
|
-
if (isAudienceElement) addMdxAudienceElementTags(content, node, tags);
|
|
1609
|
-
if (node.type !== "mdxjsEsm") {
|
|
1610
|
-
if (node.type === "mdxFlowExpression" || node.type === "mdxTextExpression") collectEstreeAudienceTags(content, node.data?.estree, tags);
|
|
1611
|
-
if (isMdxJsxElement && !isAudienceElement) for (const attribute of node.attributes ?? []) {
|
|
1612
|
-
collectEstreeAudienceTags(content, attribute.data?.estree, tags);
|
|
1613
|
-
if (attribute.value && typeof attribute.value === "object") {
|
|
1614
|
-
const value = attribute.value;
|
|
1615
|
-
collectEstreeAudienceTags(content, value.data?.estree, tags);
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
for (const child of node.children ?? []) collectMdxAudienceTags(content, child, tags);
|
|
1620
|
-
}
|
|
1621
|
-
function findMdxAstAudienceTags(content) {
|
|
1622
|
-
try {
|
|
1623
|
-
const tree = docsAudienceMdxProcessor.parse(prepareMdxParserSource(content));
|
|
1624
|
-
const tags = [];
|
|
1625
|
-
collectMdxAudienceTags(content, tree, tags);
|
|
1626
|
-
return tags.sort((left, right) => left.index - right.index || left.end - right.end);
|
|
1627
|
-
} catch {
|
|
1628
|
-
return;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
/** Locate effective audience tags while excluding Markdown/MDX literal contexts. */
|
|
1632
|
-
function findDocsAudienceMdxTags(content) {
|
|
1633
|
-
if (!DOCS_AUDIENCE_TAG_CANDIDATE_PATTERN.test(content)) return [];
|
|
1634
|
-
return findMdxAstAudienceTags(content) ?? findAudienceTags(content, findProtectedRanges(content));
|
|
1635
|
-
}
|
|
1636
|
-
function isAudienceVisible(scopes, audience) {
|
|
1637
|
-
return scopes.every((scope) => resolveDocsAudienceExposure(scope.only, audience));
|
|
1638
|
-
}
|
|
1639
|
-
function mergeProtectedRanges(ranges) {
|
|
1640
|
-
const sorted = [...ranges].sort((left, right) => left.start - right.start || left.end - right.end);
|
|
1641
|
-
const merged = [];
|
|
1642
|
-
for (const range of sorted) {
|
|
1643
|
-
const previous = merged.at(-1);
|
|
1644
|
-
if (!previous || range.start > previous.end) {
|
|
1645
|
-
merged.push({ ...range });
|
|
1646
|
-
continue;
|
|
1647
|
-
}
|
|
1648
|
-
previous.end = Math.max(previous.end, range.end);
|
|
1649
|
-
}
|
|
1650
|
-
return merged;
|
|
1651
|
-
}
|
|
1652
|
-
function findMarkdownLiteralRanges(content) {
|
|
1653
|
-
try {
|
|
1654
|
-
const tree = docsAudienceMarkdownProcessor.parse(content);
|
|
1655
|
-
const ranges = [];
|
|
1656
|
-
const visit = (node) => {
|
|
1657
|
-
const start = getOffset(node.position, "start");
|
|
1658
|
-
const end = getOffset(node.position, "end");
|
|
1659
|
-
if (start !== void 0 && end !== void 0) {
|
|
1660
|
-
if (node.type === "code" || node.type === "inlineCode" || node.type === "definition") ranges.push({
|
|
1661
|
-
start,
|
|
1662
|
-
end
|
|
1663
|
-
});
|
|
1664
|
-
else if (node.type === "link" || node.type === "image") {
|
|
1665
|
-
const contentEnd = Math.max(start, ...(node.children ?? []).map((child) => getOffset(child.position, "end") ?? start));
|
|
1666
|
-
ranges.push({
|
|
1667
|
-
start: contentEnd,
|
|
1668
|
-
end
|
|
1669
|
-
});
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
for (const child of node.children ?? []) visit(child);
|
|
1673
|
-
};
|
|
1674
|
-
visit(tree);
|
|
1675
|
-
return ranges;
|
|
1676
|
-
} catch {
|
|
1677
|
-
return [];
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
function findMarkdownLinkDestinationRanges(content, protectedRanges) {
|
|
1681
|
-
const ranges = [];
|
|
1682
|
-
let cursor = 0;
|
|
1683
|
-
while (cursor < content.length) {
|
|
1684
|
-
const start = content.indexOf("](", cursor);
|
|
1685
|
-
if (start === -1) break;
|
|
1686
|
-
if (isEscapedAt(content, start) || isInsideRange(start, protectedRanges)) {
|
|
1687
|
-
cursor = start + 1;
|
|
1688
|
-
continue;
|
|
1689
|
-
}
|
|
1690
|
-
let end = start + 2;
|
|
1691
|
-
let parenthesisDepth = 1;
|
|
1692
|
-
let angleDestination = false;
|
|
1693
|
-
let quote;
|
|
1694
|
-
while (end < content.length && parenthesisDepth > 0) {
|
|
1695
|
-
const character = content[end];
|
|
1696
|
-
if (character === "\\") {
|
|
1697
|
-
end += 2;
|
|
1698
|
-
continue;
|
|
1699
|
-
}
|
|
1700
|
-
if (quote) {
|
|
1701
|
-
if (character === quote) quote = void 0;
|
|
1702
|
-
end += 1;
|
|
1703
|
-
continue;
|
|
1704
|
-
}
|
|
1705
|
-
if (character === "\"" || character === "'") quote = character;
|
|
1706
|
-
else if (character === "<" && parenthesisDepth === 1) {
|
|
1707
|
-
const closingAngle = content.indexOf(">", end + 1);
|
|
1708
|
-
const closingParenthesis = content.indexOf(")", end + 1);
|
|
1709
|
-
angleDestination = closingAngle !== -1 && closingAngle < closingParenthesis;
|
|
1710
|
-
} else if (character === ">" && angleDestination) angleDestination = false;
|
|
1711
|
-
else if (!angleDestination && character === "(") parenthesisDepth += 1;
|
|
1712
|
-
else if (!angleDestination && character === ")") parenthesisDepth -= 1;
|
|
1713
|
-
end += 1;
|
|
1714
|
-
}
|
|
1715
|
-
if (parenthesisDepth === 0) ranges.push({
|
|
1716
|
-
start: start + 1,
|
|
1717
|
-
end
|
|
1718
|
-
});
|
|
1719
|
-
cursor = Math.max(start + 1, end);
|
|
1720
|
-
}
|
|
1721
|
-
return ranges;
|
|
1722
|
-
}
|
|
1723
|
-
function findFenceRanges(content) {
|
|
1724
|
-
const ranges = [];
|
|
1725
|
-
let fence;
|
|
1726
|
-
let offset = 0;
|
|
1727
|
-
const stripBlockquoteContainers = (line) => {
|
|
1728
|
-
let value = line;
|
|
1729
|
-
while (/^ {0,3}>[\t ]?/.test(value)) value = value.replace(/^ {0,3}>[\t ]?/, "");
|
|
1730
|
-
return value;
|
|
1731
|
-
};
|
|
1732
|
-
const stripOpeningListContainers = (line) => {
|
|
1733
|
-
let value = line;
|
|
1734
|
-
while (/^ {0,3}(?:[-+*]|\d{1,9}[.)])[\t ]+/.test(value)) value = value.replace(/^ {0,3}(?:[-+*]|\d{1,9}[.)])[\t ]+/, "");
|
|
1735
|
-
return value;
|
|
1736
|
-
};
|
|
1737
|
-
for (const line of content.split(/(?<=\n)/)) {
|
|
1738
|
-
const containerRelativeLine = stripBlockquoteContainers(line.replace(/\r?\n$/, ""));
|
|
1739
|
-
const match = (fence ? containerRelativeLine : stripOpeningListContainers(containerRelativeLine)).match(/^[\t ]*(`{3,}|~{3,})(.*)$/);
|
|
1740
|
-
if (!fence && match?.[1]) fence = {
|
|
1741
|
-
marker: match[1][0],
|
|
1742
|
-
length: match[1].length,
|
|
1743
|
-
start: offset
|
|
1744
|
-
};
|
|
1745
|
-
else if (fence && match?.[1]?.[0] === fence.marker && match[1].length >= fence.length && /^[\t ]*$/.test(match[2] ?? "")) {
|
|
1746
|
-
ranges.push({
|
|
1747
|
-
start: fence.start,
|
|
1748
|
-
end: offset + line.length
|
|
1749
|
-
});
|
|
1750
|
-
fence = void 0;
|
|
1751
|
-
}
|
|
1752
|
-
offset += line.length;
|
|
1753
|
-
}
|
|
1754
|
-
if (fence) ranges.push({
|
|
1755
|
-
start: fence.start,
|
|
1756
|
-
end: content.length
|
|
1757
|
-
});
|
|
1758
|
-
return ranges;
|
|
1759
|
-
}
|
|
1760
|
-
function findSvelteDirectiveRanges(content, excludedRanges) {
|
|
1761
|
-
const ranges = [];
|
|
1762
|
-
let index = 0;
|
|
1763
|
-
while (index < content.length) {
|
|
1764
|
-
const start = content.indexOf("{", index);
|
|
1765
|
-
if (start === -1) break;
|
|
1766
|
-
const sigil = content[start + 1];
|
|
1767
|
-
if (!/[#/:@]/.test(sigil ?? "") || !/[A-Za-z]/.test(content[start + 2] ?? "") || isEscapedAt(content, start) || isInsideRange(start, excludedRanges)) {
|
|
1768
|
-
index = start + 1;
|
|
1769
|
-
continue;
|
|
1770
|
-
}
|
|
1771
|
-
let cursor = start + 2;
|
|
1772
|
-
let braceDepth = 1;
|
|
1773
|
-
let quote;
|
|
1774
|
-
let blockComment = false;
|
|
1775
|
-
let lineComment = false;
|
|
1776
|
-
let regex = false;
|
|
1777
|
-
let regexCharacterClass = false;
|
|
1778
|
-
while (cursor < content.length && braceDepth > 0) {
|
|
1779
|
-
const character = content[cursor];
|
|
1780
|
-
const next = content[cursor + 1];
|
|
1781
|
-
if (lineComment) {
|
|
1782
|
-
if (character === "\n") lineComment = false;
|
|
1783
|
-
cursor += 1;
|
|
1784
|
-
continue;
|
|
1785
|
-
}
|
|
1786
|
-
if (blockComment) {
|
|
1787
|
-
if (character === "*" && next === "/") {
|
|
1788
|
-
blockComment = false;
|
|
1789
|
-
cursor += 2;
|
|
1790
|
-
} else cursor += 1;
|
|
1791
|
-
continue;
|
|
1792
|
-
}
|
|
1793
|
-
if (quote) {
|
|
1794
|
-
if (character === "\\") {
|
|
1795
|
-
cursor += 2;
|
|
1796
|
-
continue;
|
|
1797
|
-
}
|
|
1798
|
-
if (character === quote) quote = void 0;
|
|
1799
|
-
cursor += 1;
|
|
1800
|
-
continue;
|
|
1801
|
-
}
|
|
1802
|
-
if (regex) {
|
|
1803
|
-
if (character === "\\") {
|
|
1804
|
-
cursor += 2;
|
|
1805
|
-
continue;
|
|
1806
|
-
}
|
|
1807
|
-
if (character === "[") regexCharacterClass = true;
|
|
1808
|
-
if (character === "]") regexCharacterClass = false;
|
|
1809
|
-
if (character === "/" && !regexCharacterClass) regex = false;
|
|
1810
|
-
cursor += 1;
|
|
1811
|
-
continue;
|
|
1812
|
-
}
|
|
1813
|
-
if (character === "/" && next === "*") {
|
|
1814
|
-
blockComment = true;
|
|
1815
|
-
cursor += 2;
|
|
1816
|
-
continue;
|
|
1817
|
-
}
|
|
1818
|
-
if (character === "/" && next === "/") {
|
|
1819
|
-
lineComment = true;
|
|
1820
|
-
cursor += 2;
|
|
1821
|
-
continue;
|
|
1822
|
-
}
|
|
1823
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
1824
|
-
quote = character;
|
|
1825
|
-
cursor += 1;
|
|
1826
|
-
continue;
|
|
1827
|
-
}
|
|
1828
|
-
if (character === "/") {
|
|
1829
|
-
if (canStartJavascriptRegex(content, cursor, start + 1)) {
|
|
1830
|
-
regex = true;
|
|
1831
|
-
cursor += 1;
|
|
1832
|
-
continue;
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
if (character === "{") braceDepth += 1;
|
|
1836
|
-
if (character === "}") braceDepth -= 1;
|
|
1837
|
-
cursor += 1;
|
|
1838
|
-
}
|
|
1839
|
-
if (braceDepth === 0) ranges.push({
|
|
1840
|
-
start,
|
|
1841
|
-
end: cursor
|
|
1842
|
-
});
|
|
1843
|
-
index = Math.max(start + 1, cursor);
|
|
1844
|
-
}
|
|
1845
|
-
return ranges;
|
|
1846
|
-
}
|
|
1847
|
-
function scanMdxModuleLine(line, state) {
|
|
1848
|
-
for (let index = 0; index < line.length; index += 1) {
|
|
1849
|
-
const character = line[index];
|
|
1850
|
-
const next = line[index + 1];
|
|
1851
|
-
if (state.blockComment) {
|
|
1852
|
-
if (character === "*" && next === "/") {
|
|
1853
|
-
state.blockComment = false;
|
|
1854
|
-
index += 1;
|
|
1855
|
-
}
|
|
1856
|
-
continue;
|
|
1857
|
-
}
|
|
1858
|
-
if (state.quote) {
|
|
1859
|
-
if (character === "\\") {
|
|
1860
|
-
index += 1;
|
|
1861
|
-
continue;
|
|
1862
|
-
}
|
|
1863
|
-
if (character === state.quote) state.quote = void 0;
|
|
1864
|
-
continue;
|
|
1865
|
-
}
|
|
1866
|
-
if (character === "/" && next === "/") break;
|
|
1867
|
-
if (character === "/" && next === "*") {
|
|
1868
|
-
state.blockComment = true;
|
|
1869
|
-
index += 1;
|
|
1870
|
-
continue;
|
|
1871
|
-
}
|
|
1872
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
1873
|
-
state.quote = character;
|
|
1874
|
-
continue;
|
|
1875
|
-
}
|
|
1876
|
-
if (state.jsxTag) {
|
|
1877
|
-
if (character === "{") state.braceDepth += 1;
|
|
1878
|
-
else if (character === "}" && state.braceDepth > 0) state.braceDepth -= 1;
|
|
1879
|
-
else if (character === ">" && state.braceDepth === 0) {
|
|
1880
|
-
let previous = index - 1;
|
|
1881
|
-
while (previous >= 0 && /[\t ]/.test(line[previous] ?? "")) previous -= 1;
|
|
1882
|
-
const selfClosing = line[previous] === "/";
|
|
1883
|
-
let following = index + 1;
|
|
1884
|
-
while (following < line.length && /[\t ]/.test(line[following] ?? "")) following += 1;
|
|
1885
|
-
const genericTypeParameters = !state.jsxTag.closing && line[following] === "(";
|
|
1886
|
-
if (state.jsxTag.closing) state.jsxDepth = Math.max(0, state.jsxDepth - 1);
|
|
1887
|
-
else if (!selfClosing && !genericTypeParameters) state.jsxDepth += 1;
|
|
1888
|
-
state.jsxTag = void 0;
|
|
1889
|
-
}
|
|
1890
|
-
continue;
|
|
1891
|
-
}
|
|
1892
|
-
if (character === "<") {
|
|
1893
|
-
let previous = index - 1;
|
|
1894
|
-
while (previous >= 0 && /[\t ]/.test(line[previous] ?? "")) previous -= 1;
|
|
1895
|
-
const previousCharacter = line[previous];
|
|
1896
|
-
const atExpressionStart = previousCharacter === void 0 || /[=([{,:?!;]/.test(previousCharacter);
|
|
1897
|
-
const openingElement = (state.jsxDepth > 0 || atExpressionStart) && /[A-Za-z]/.test(next ?? "");
|
|
1898
|
-
const closingElement = state.jsxDepth > 0 && next === "/" && /[A-Za-z]/.test(line[index + 2] ?? "");
|
|
1899
|
-
const openingFragment = atExpressionStart && next === ">";
|
|
1900
|
-
const closingFragment = state.jsxDepth > 0 && next === "/" && line[index + 2] === ">";
|
|
1901
|
-
if (openingFragment) {
|
|
1902
|
-
state.jsxDepth += 1;
|
|
1903
|
-
index += 1;
|
|
1904
|
-
continue;
|
|
1905
|
-
}
|
|
1906
|
-
if (closingFragment) {
|
|
1907
|
-
state.jsxDepth = Math.max(0, state.jsxDepth - 1);
|
|
1908
|
-
index += 2;
|
|
1909
|
-
continue;
|
|
1910
|
-
}
|
|
1911
|
-
if (openingElement || closingElement) {
|
|
1912
|
-
state.jsxTag = { closing: closingElement };
|
|
1913
|
-
continue;
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
if (character === "{") {
|
|
1917
|
-
state.braceDepth += 1;
|
|
1918
|
-
state.sawBlock = true;
|
|
1919
|
-
continue;
|
|
1920
|
-
}
|
|
1921
|
-
if (character === "}") state.braceDepth = Math.max(0, state.braceDepth - 1);
|
|
1922
|
-
else if (character === "[") state.bracketDepth += 1;
|
|
1923
|
-
else if (character === "]") state.bracketDepth = Math.max(0, state.bracketDepth - 1);
|
|
1924
|
-
else if (character === "(") state.parenthesisDepth += 1;
|
|
1925
|
-
else if (character === ")") state.parenthesisDepth = Math.max(0, state.parenthesisDepth - 1);
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
function shouldContinueMdxModule(statement, state, nextSignificantLine) {
|
|
1929
|
-
if (state.quote || state.blockComment || state.braceDepth > 0 || state.bracketDepth > 0 || state.parenthesisDepth > 0 || state.jsxTag || state.jsxDepth > 0) return true;
|
|
1930
|
-
const trimmed = statement.trimEnd();
|
|
1931
|
-
if (/^\s*import\b(?!\s*[.(])/.test(statement)) {
|
|
1932
|
-
if (!(/^\s*import\s*(?:type\s+)?["'`]/.test(statement) || /\bfrom\s*["'`][\s\S]*["'`]\s*;?\s*$/.test(trimmed)) && !/;\s*$/.test(trimmed)) return true;
|
|
1933
|
-
}
|
|
1934
|
-
const isExportList = /^\s*export\s+(?:type\s+)?\{/.test(statement);
|
|
1935
|
-
const isExportAll = /^\s*export\s+\*/.test(statement);
|
|
1936
|
-
if ((isExportList || isExportAll) && !/\bfrom\s*["'`][\s\S]*["'`]\s*;?\s*$/.test(trimmed) && (isExportAll || /^\s*from\b/.test(nextSignificantLine ?? ""))) return true;
|
|
1937
|
-
if (/=>\s*$/.test(trimmed) || /[=([{,:.?+\-*/%&|^!]$/.test(trimmed)) return true;
|
|
1938
|
-
if (/\b(?:as|default|extends|from|implements|satisfies)$/.test(trimmed)) return true;
|
|
1939
|
-
return /^\s*export\s+(?:default\s+)?(?:(?:async\s+)?function|class|enum|interface|module|namespace)\b/.test(statement) && !state.sawBlock;
|
|
1940
|
-
}
|
|
1941
|
-
function isInsideRange(index, ranges) {
|
|
1942
|
-
return ranges.some((range) => index >= range.start && index < range.end);
|
|
1943
|
-
}
|
|
1944
|
-
function findMdxModuleRanges(content, protectedRanges) {
|
|
1945
|
-
const ranges = [];
|
|
1946
|
-
const lines = content.split(/(?<=\n)/);
|
|
1947
|
-
let offset = 0;
|
|
1948
|
-
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
1949
|
-
const line = lines[lineIndex] ?? "";
|
|
1950
|
-
const firstNonWhitespace = line.search(/\S/);
|
|
1951
|
-
if (/^\s*(?:import|export)\b/.test(line) && !isInsideRange(offset + Math.max(0, firstNonWhitespace), protectedRanges)) {
|
|
1952
|
-
const start = offset;
|
|
1953
|
-
const state = {
|
|
1954
|
-
braceDepth: 0,
|
|
1955
|
-
bracketDepth: 0,
|
|
1956
|
-
parenthesisDepth: 0,
|
|
1957
|
-
blockComment: false,
|
|
1958
|
-
sawBlock: false,
|
|
1959
|
-
jsxDepth: 0
|
|
1960
|
-
};
|
|
1961
|
-
let statement = "";
|
|
1962
|
-
while (lineIndex < lines.length) {
|
|
1963
|
-
const moduleLine = lines[lineIndex] ?? "";
|
|
1964
|
-
statement += moduleLine;
|
|
1965
|
-
scanMdxModuleLine(moduleLine, state);
|
|
1966
|
-
offset += moduleLine.length;
|
|
1967
|
-
const nextSignificantLine = lines.slice(lineIndex + 1).find((candidate) => candidate.trim().length > 0);
|
|
1968
|
-
if (!shouldContinueMdxModule(statement, state, nextSignificantLine)) break;
|
|
1969
|
-
lineIndex += 1;
|
|
1970
|
-
}
|
|
1971
|
-
ranges.push({
|
|
1972
|
-
start,
|
|
1973
|
-
end: offset
|
|
1974
|
-
});
|
|
1975
|
-
continue;
|
|
1976
|
-
}
|
|
1977
|
-
offset += line.length;
|
|
1978
|
-
}
|
|
1979
|
-
return ranges;
|
|
1980
|
-
}
|
|
1981
|
-
function findInlineCodeRanges(content, protectedRanges) {
|
|
1982
|
-
const ranges = [];
|
|
1983
|
-
let index = 0;
|
|
1984
|
-
while (index < content.length) {
|
|
1985
|
-
if (content[index] !== "`" || isEscapedAt(content, index) || isInsideRange(index, protectedRanges)) {
|
|
1986
|
-
index += 1;
|
|
1987
|
-
continue;
|
|
1988
|
-
}
|
|
1989
|
-
let markerLength = 1;
|
|
1990
|
-
while (content[index + markerLength] === "`") markerLength += 1;
|
|
1991
|
-
let closingIndex = index + markerLength;
|
|
1992
|
-
while (closingIndex < content.length) {
|
|
1993
|
-
closingIndex = content.indexOf("`", closingIndex);
|
|
1994
|
-
if (closingIndex === -1) break;
|
|
1995
|
-
if (isInsideRange(closingIndex, protectedRanges)) {
|
|
1996
|
-
closingIndex += 1;
|
|
1997
|
-
continue;
|
|
1998
|
-
}
|
|
1999
|
-
let closingLength = 1;
|
|
2000
|
-
while (content[closingIndex + closingLength] === "`") closingLength += 1;
|
|
2001
|
-
if (closingLength === markerLength) break;
|
|
2002
|
-
closingIndex += closingLength;
|
|
2003
|
-
}
|
|
2004
|
-
if (closingIndex === -1) {
|
|
2005
|
-
index += markerLength;
|
|
2006
|
-
continue;
|
|
2007
|
-
}
|
|
2008
|
-
ranges.push({
|
|
2009
|
-
start: index,
|
|
2010
|
-
end: closingIndex + markerLength
|
|
2011
|
-
});
|
|
2012
|
-
index = closingIndex + markerLength;
|
|
2013
|
-
}
|
|
2014
|
-
return ranges;
|
|
2015
|
-
}
|
|
2016
|
-
function findMdxExpressionLiteralRanges(content, protectedRanges) {
|
|
2017
|
-
const ranges = [];
|
|
2018
|
-
let index = 0;
|
|
2019
|
-
while (index < content.length) {
|
|
2020
|
-
if (content[index] !== "{" || isEscapedAt(content, index) || isInsideRange(index, protectedRanges)) {
|
|
2021
|
-
index += 1;
|
|
2022
|
-
continue;
|
|
2023
|
-
}
|
|
2024
|
-
let cursor = index + 1;
|
|
2025
|
-
let depth = 1;
|
|
2026
|
-
while (cursor < content.length && depth > 0) {
|
|
2027
|
-
if (isInsideRange(cursor, protectedRanges)) {
|
|
2028
|
-
cursor += 1;
|
|
2029
|
-
continue;
|
|
2030
|
-
}
|
|
2031
|
-
const character = content[cursor];
|
|
2032
|
-
const next = content[cursor + 1];
|
|
2033
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
2034
|
-
const quote = character;
|
|
2035
|
-
const start = cursor;
|
|
2036
|
-
cursor += 1;
|
|
2037
|
-
while (cursor < content.length) {
|
|
2038
|
-
if (content[cursor] === "\\") {
|
|
2039
|
-
cursor += 2;
|
|
2040
|
-
continue;
|
|
2041
|
-
}
|
|
2042
|
-
if (content[cursor] === quote) {
|
|
2043
|
-
cursor += 1;
|
|
2044
|
-
break;
|
|
2045
|
-
}
|
|
2046
|
-
cursor += 1;
|
|
2047
|
-
}
|
|
2048
|
-
ranges.push({
|
|
2049
|
-
start,
|
|
2050
|
-
end: cursor
|
|
2051
|
-
});
|
|
2052
|
-
continue;
|
|
2053
|
-
}
|
|
2054
|
-
if (character === "/" && next === "*") {
|
|
2055
|
-
const start = cursor;
|
|
2056
|
-
const end = content.indexOf("*/", cursor + 2);
|
|
2057
|
-
cursor = end === -1 ? content.length : end + 2;
|
|
2058
|
-
ranges.push({
|
|
2059
|
-
start,
|
|
2060
|
-
end: cursor
|
|
2061
|
-
});
|
|
2062
|
-
continue;
|
|
2063
|
-
}
|
|
2064
|
-
if (character === "/" && next === "/") {
|
|
2065
|
-
const start = cursor;
|
|
2066
|
-
const newline = content.indexOf("\n", cursor + 2);
|
|
2067
|
-
cursor = newline === -1 ? content.length : newline;
|
|
2068
|
-
ranges.push({
|
|
2069
|
-
start,
|
|
2070
|
-
end: cursor
|
|
2071
|
-
});
|
|
2072
|
-
continue;
|
|
2073
|
-
}
|
|
2074
|
-
if (character === "/") {
|
|
2075
|
-
if (canStartJavascriptRegex(content, cursor, index)) {
|
|
2076
|
-
const start = cursor;
|
|
2077
|
-
let inCharacterClass = false;
|
|
2078
|
-
cursor += 1;
|
|
2079
|
-
while (cursor < content.length) {
|
|
2080
|
-
if (content[cursor] === "\\") {
|
|
2081
|
-
cursor += 2;
|
|
2082
|
-
continue;
|
|
2083
|
-
}
|
|
2084
|
-
if (content[cursor] === "[") inCharacterClass = true;
|
|
2085
|
-
if (content[cursor] === "]") inCharacterClass = false;
|
|
2086
|
-
if (content[cursor] === "/" && !inCharacterClass) {
|
|
2087
|
-
cursor += 1;
|
|
2088
|
-
while (/[A-Za-z]/.test(content[cursor] ?? "")) cursor += 1;
|
|
2089
|
-
break;
|
|
2090
|
-
}
|
|
2091
|
-
if (content[cursor] === "\n") break;
|
|
2092
|
-
cursor += 1;
|
|
2093
|
-
}
|
|
2094
|
-
ranges.push({
|
|
2095
|
-
start,
|
|
2096
|
-
end: cursor
|
|
2097
|
-
});
|
|
2098
|
-
continue;
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
if (character === "{") depth += 1;
|
|
2102
|
-
if (character === "}") depth -= 1;
|
|
2103
|
-
cursor += 1;
|
|
2104
|
-
}
|
|
2105
|
-
index = Math.max(index + 1, cursor);
|
|
2106
|
-
}
|
|
2107
|
-
return ranges;
|
|
2108
|
-
}
|
|
2109
|
-
function readGenericMdxJsxTag(content, index) {
|
|
2110
|
-
let cursor = index + 1;
|
|
2111
|
-
if (content[cursor] === "/") cursor += 1;
|
|
2112
|
-
if (!/[A-Za-z]/.test(content[cursor] ?? "")) return void 0;
|
|
2113
|
-
const nameStart = cursor;
|
|
2114
|
-
while (/[\w:.-]/.test(content[cursor] ?? "")) cursor += 1;
|
|
2115
|
-
const name = content.slice(nameStart, cursor);
|
|
2116
|
-
if (DOCS_AUDIENCE_TAG_NAMES.some((candidate) => candidate === name)) return void 0;
|
|
2117
|
-
if (!/[\s/>]/.test(content[cursor] ?? "")) return void 0;
|
|
2118
|
-
const literals = [];
|
|
2119
|
-
let braceDepth = 0;
|
|
2120
|
-
while (cursor < content.length) {
|
|
2121
|
-
const character = content[cursor];
|
|
2122
|
-
const next = content[cursor + 1];
|
|
2123
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
2124
|
-
const quote = character;
|
|
2125
|
-
const start = cursor;
|
|
2126
|
-
cursor += 1;
|
|
2127
|
-
while (cursor < content.length) {
|
|
2128
|
-
if (content[cursor] === "\\") {
|
|
2129
|
-
cursor += 2;
|
|
2130
|
-
continue;
|
|
2131
|
-
}
|
|
2132
|
-
if (content[cursor] === quote) {
|
|
2133
|
-
cursor += 1;
|
|
2134
|
-
break;
|
|
2135
|
-
}
|
|
2136
|
-
cursor += 1;
|
|
2137
|
-
}
|
|
2138
|
-
literals.push({
|
|
2139
|
-
start,
|
|
2140
|
-
end: cursor
|
|
2141
|
-
});
|
|
2142
|
-
continue;
|
|
2143
|
-
}
|
|
2144
|
-
if (braceDepth > 0 && character === "/" && next === "*") {
|
|
2145
|
-
const start = cursor;
|
|
2146
|
-
const end = content.indexOf("*/", cursor + 2);
|
|
2147
|
-
cursor = end === -1 ? content.length : end + 2;
|
|
2148
|
-
literals.push({
|
|
2149
|
-
start,
|
|
2150
|
-
end: cursor
|
|
2151
|
-
});
|
|
2152
|
-
continue;
|
|
2153
|
-
}
|
|
2154
|
-
if (braceDepth > 0 && character === "/" && next === "/") {
|
|
2155
|
-
const start = cursor;
|
|
2156
|
-
const newline = content.indexOf("\n", cursor + 2);
|
|
2157
|
-
cursor = newline === -1 ? content.length : newline;
|
|
2158
|
-
literals.push({
|
|
2159
|
-
start,
|
|
2160
|
-
end: cursor
|
|
2161
|
-
});
|
|
2162
|
-
continue;
|
|
2163
|
-
}
|
|
2164
|
-
if (character === "{") braceDepth += 1;
|
|
2165
|
-
else if (character === "}" && braceDepth > 0) braceDepth -= 1;
|
|
2166
|
-
else if (character === ">" && braceDepth === 0) {
|
|
2167
|
-
let previous = cursor - 1;
|
|
2168
|
-
while (previous >= index && /\s/.test(content[previous] ?? "")) previous -= 1;
|
|
2169
|
-
return {
|
|
2170
|
-
end: cursor + 1,
|
|
2171
|
-
literals,
|
|
2172
|
-
selfClosing: content[previous] === "/"
|
|
2173
|
-
};
|
|
2174
|
-
}
|
|
2175
|
-
cursor += 1;
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
function findGenericMdxJsxLiteralRanges(content, protectedRanges) {
|
|
2179
|
-
const ranges = [];
|
|
2180
|
-
let cursor = 0;
|
|
2181
|
-
while (cursor < content.length) {
|
|
2182
|
-
const index = content.indexOf("<", cursor);
|
|
2183
|
-
if (index === -1) break;
|
|
2184
|
-
if (isEscapedAt(content, index) || isInsideRange(index, protectedRanges)) {
|
|
2185
|
-
cursor = index + 1;
|
|
2186
|
-
continue;
|
|
2187
|
-
}
|
|
2188
|
-
const tag = readGenericMdxJsxTag(content, index);
|
|
2189
|
-
if (!tag) {
|
|
2190
|
-
cursor = index + 1;
|
|
2191
|
-
continue;
|
|
2192
|
-
}
|
|
2193
|
-
ranges.push(...tag.literals);
|
|
2194
|
-
cursor = tag.end;
|
|
2195
|
-
}
|
|
2196
|
-
return ranges;
|
|
2197
|
-
}
|
|
2198
|
-
function findDelimiterExclusionRanges(content) {
|
|
2199
|
-
const markdownLiterals = findMarkdownLiteralRanges(content);
|
|
2200
|
-
const fences = findFenceRanges(content);
|
|
2201
|
-
const frontmatter = findLeadingFrontmatterRange(content);
|
|
2202
|
-
const literalBlocks = mergeProtectedRanges([
|
|
2203
|
-
...markdownLiterals,
|
|
2204
|
-
...fences,
|
|
2205
|
-
...frontmatter ? [frontmatter] : []
|
|
2206
|
-
]);
|
|
2207
|
-
const inlineCode = findInlineCodeRanges(content, literalBlocks);
|
|
2208
|
-
const markdownLinks = findMarkdownLinkDestinationRanges(content, literalBlocks);
|
|
2209
|
-
const nonModuleRanges = mergeProtectedRanges([
|
|
2210
|
-
...literalBlocks,
|
|
2211
|
-
...inlineCode,
|
|
2212
|
-
...markdownLinks
|
|
2213
|
-
]);
|
|
2214
|
-
const moduleRanges = findMdxModuleRanges(content, nonModuleRanges);
|
|
2215
|
-
const nonExpressionRanges = mergeProtectedRanges([...nonModuleRanges, ...moduleRanges]);
|
|
2216
|
-
const expressionLiterals = findMdxExpressionLiteralRanges(content, nonExpressionRanges);
|
|
2217
|
-
const nonJsxRanges = mergeProtectedRanges([...nonExpressionRanges, ...expressionLiterals]);
|
|
2218
|
-
return mergeProtectedRanges([...nonJsxRanges, ...findGenericMdxJsxLiteralRanges(content, nonJsxRanges)]);
|
|
2219
|
-
}
|
|
2220
|
-
function findProtectedRanges(content) {
|
|
2221
|
-
const baseExcludedRanges = findDelimiterExclusionRanges(content);
|
|
2222
|
-
const svelteDirectives = findSvelteDirectiveRanges(content, baseExcludedRanges);
|
|
2223
|
-
const excludedRanges = mergeProtectedRanges([...baseExcludedRanges, ...svelteDirectives]);
|
|
2224
|
-
const htmlLiterals = findHtmlLiteralRanges(content, excludedRanges);
|
|
2225
|
-
return mergeProtectedRanges([...excludedRanges, ...htmlLiterals]);
|
|
2226
|
-
}
|
|
2227
|
-
function normalizeProjectedWhitespace(content) {
|
|
2228
|
-
const protectedRanges = findProtectedRanges(content);
|
|
2229
|
-
return content.replace(/\n{3,}/g, (newlines, offset) => {
|
|
2230
|
-
const end = offset + newlines.length;
|
|
2231
|
-
return protectedRanges.some((range) => range.start < end && range.end > offset) ? newlines : "\n\n";
|
|
2232
|
-
}).trim();
|
|
2233
|
-
}
|
|
2234
|
-
/** Find audience declarations that cannot be projected consistently at build time. */
|
|
2235
|
-
function findDocsAudienceMdxIssues(content) {
|
|
2236
|
-
const issues = [];
|
|
2237
|
-
for (const tag of findDocsAudienceMdxTags(content)) {
|
|
2238
|
-
if (tag.closing) continue;
|
|
2239
|
-
const { attributes } = tag;
|
|
2240
|
-
const hasOnlyAttribute = tag.hasOnlyAttribute ?? /(?:^|\s)only\s*=/.test(attributes);
|
|
2241
|
-
const hasSpreadAttribute = tag.hasSpreadAttribute ?? /\{\s*\.\.\./.test(attributes);
|
|
2242
|
-
if ((tag.name === "Agent" || tag.name === "Human") && hasOnlyAttribute) {
|
|
2243
|
-
const shorthandAudience = tag.name === "Agent" ? "agent" : "human";
|
|
2244
|
-
issues.push({
|
|
2245
|
-
code: tag.name === "Agent" ? "ignored-agent-only" : "ignored-human-only",
|
|
2246
|
-
index: tag.index,
|
|
2247
|
-
message: `<${tag.name}> is always ${shorthandAudience}-only, so its \`only\` prop is ignored. Use <Audience only="${shorthandAudience}"> when you need the explicit form.`
|
|
2248
|
-
});
|
|
2249
|
-
continue;
|
|
2250
|
-
}
|
|
2251
|
-
if (tag.name !== "Audience") continue;
|
|
2252
|
-
if (hasSpreadAttribute) {
|
|
2253
|
-
issues.push({
|
|
2254
|
-
code: "dynamic-only",
|
|
2255
|
-
index: tag.index,
|
|
2256
|
-
message: "Audience spread props cannot be projected consistently. Remove the spread and use the static literal `only=\"human\"` or `only=\"agent\"`."
|
|
2257
|
-
});
|
|
2258
|
-
continue;
|
|
2259
|
-
}
|
|
2260
|
-
if (tag.only) continue;
|
|
2261
|
-
if (!hasOnlyAttribute) {
|
|
2262
|
-
issues.push({
|
|
2263
|
-
code: "missing-only",
|
|
2264
|
-
index: tag.index,
|
|
2265
|
-
message: "<Audience> requires the static literal `only=\"human\"` or `only=\"agent\"`."
|
|
2266
|
-
});
|
|
2267
|
-
continue;
|
|
2268
|
-
}
|
|
2269
|
-
const dynamicOnly = tag.onlyKind === "dynamic" || /(?:^|\s)only\s*=\s*\{/.test(attributes);
|
|
2270
|
-
issues.push({
|
|
2271
|
-
code: dynamicOnly ? "dynamic-only" : "invalid-only",
|
|
2272
|
-
index: tag.index,
|
|
2273
|
-
message: dynamicOnly ? "Dynamic Audience.only expressions cannot be projected consistently. Use the static literal `only=\"human\"` or `only=\"agent\"`." : "Audience.only must be the static literal `\"human\"` or `\"agent\"`."
|
|
2274
|
-
});
|
|
2275
|
-
}
|
|
2276
|
-
return issues;
|
|
2277
|
-
}
|
|
2278
|
-
/**
|
|
2279
|
-
* Resolve MDX into its human or agent projection.
|
|
2280
|
-
*
|
|
2281
|
-
* `<Agent>` is shorthand for `<Audience only="agent">`, while `<Human>` is
|
|
2282
|
-
* shorthand for `<Audience only="human">`. Unknown `Audience.only` values are
|
|
2283
|
-
* treated as shared content so an authoring typo never silently deletes content.
|
|
2284
|
-
* These primitives shape content for each surface; they are not an access-control boundary.
|
|
2285
|
-
*/
|
|
2286
|
-
function resolveDocsAudienceMdxContent(content, audience) {
|
|
2287
|
-
const scopes = [];
|
|
2288
|
-
let output = "";
|
|
2289
|
-
let cursor = 0;
|
|
2290
|
-
let resolvedTag = false;
|
|
2291
|
-
for (const tag of findDocsAudienceMdxTags(content)) {
|
|
2292
|
-
const activeScope = scopes.at(-1);
|
|
2293
|
-
if (tag.closing && activeScope?.name !== tag.name) continue;
|
|
2294
|
-
resolvedTag = true;
|
|
2295
|
-
const visibleBeforeTag = isAudienceVisible(scopes, audience);
|
|
2296
|
-
if (visibleBeforeTag) output += content.slice(cursor, tag.index);
|
|
2297
|
-
cursor = tag.end;
|
|
2298
|
-
if (tag.closing) {
|
|
2299
|
-
if (tag.expression && visibleBeforeTag) output += "</>";
|
|
2300
|
-
scopes.pop();
|
|
2301
|
-
continue;
|
|
2302
|
-
}
|
|
2303
|
-
if (tag.selfClosing) {
|
|
2304
|
-
if (tag.expression && visibleBeforeTag) output += tag.jsxChild ? "{null}" : "null";
|
|
2305
|
-
continue;
|
|
2306
|
-
}
|
|
2307
|
-
scopes.push({
|
|
2308
|
-
name: tag.name,
|
|
2309
|
-
only: tag.only
|
|
2310
|
-
});
|
|
2311
|
-
if (tag.expression && visibleBeforeTag) output += isAudienceVisible(scopes, audience) ? "<>" : tag.jsxChild ? "{null}" : "null";
|
|
2312
|
-
}
|
|
2313
|
-
if (isAudienceVisible(scopes, audience)) output += content.slice(cursor);
|
|
2314
|
-
return resolvedTag ? normalizeProjectedWhitespace(output) : content;
|
|
2315
|
-
}
|
|
2316
|
-
/** Backwards-compatible name retained for existing integrations. */
|
|
2317
|
-
function resolveDocsAgentMdxContent(content, audience) {
|
|
2318
|
-
return resolveDocsAudienceMdxContent(content, audience);
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
//#endregion
|
|
2322
|
-
//#region src/agent.ts
|
|
2323
|
-
const DEFAULT_DOCS_API_ROUTE = "/api/docs";
|
|
2324
|
-
const DEFAULT_DOCS_CONFIG_FORMAT = "docs-config-map.v1";
|
|
2325
|
-
const DEFAULT_DOCS_CONFIG_ROUTE = `${DEFAULT_DOCS_API_ROUTE}?format=config`;
|
|
2326
|
-
const DEFAULT_DOCS_DIAGNOSTICS_FORMAT = "docs-diagnostics.v1";
|
|
2327
|
-
const DEFAULT_DOCS_DIAGNOSTICS_ROUTE = `${DEFAULT_DOCS_API_ROUTE}?format=diagnostics`;
|
|
2328
|
-
const DEFAULT_OPENAPI_SCHEMA_ROUTE = `${DEFAULT_DOCS_API_ROUTE}?format=openapi`;
|
|
2329
|
-
const DEFAULT_AGENT_SPEC_ROUTE = "/api/docs/agent/spec";
|
|
2330
|
-
const DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE = "/.well-known/agent";
|
|
2331
|
-
const DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE = "/.well-known/agent.json";
|
|
2332
|
-
const DEFAULT_MCP_ROUTE = "/api/docs/mcp";
|
|
2333
|
-
const DEFAULT_MCP_PUBLIC_ROUTE = "/mcp";
|
|
2334
|
-
const DEFAULT_MCP_WELL_KNOWN_ROUTE = "/.well-known/mcp";
|
|
2335
|
-
const DEFAULT_LLMS_TXT_ROUTE = "/llms.txt";
|
|
2336
|
-
const DEFAULT_LLMS_FULL_TXT_ROUTE = "/llms-full.txt";
|
|
2337
|
-
const DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE = "/.well-known/llms.txt";
|
|
2338
|
-
const DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE = "/.well-known/llms-full.txt";
|
|
2339
|
-
const DEFAULT_LLMS_TXT_MAX_CHARS = 5e4;
|
|
2340
|
-
const DEFAULT_SKILL_MD_ROUTE = "/skill.md";
|
|
2341
|
-
const DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE = "/.well-known/skill.md";
|
|
2342
|
-
const DEFAULT_AGENTS_MD_ROUTE = "/AGENTS.md";
|
|
2343
|
-
const DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE = "/.well-known/AGENTS.md";
|
|
2344
|
-
const DEFAULT_AGENT_MD_ROUTE = "/AGENT.md";
|
|
2345
|
-
const DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE = "/.well-known/AGENT.md";
|
|
2346
|
-
const DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE = "/robots.txt";
|
|
2347
|
-
const DEFAULT_AGENT_FEEDBACK_ROUTE = "/api/docs/agent/feedback";
|
|
2348
|
-
const DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA = {
|
|
2349
|
-
type: "object",
|
|
2350
|
-
additionalProperties: false,
|
|
2351
|
-
properties: {
|
|
2352
|
-
task: {
|
|
2353
|
-
type: "string",
|
|
2354
|
-
description: "Short description of what the agent was trying to do."
|
|
2355
|
-
},
|
|
2356
|
-
understanding: {
|
|
2357
|
-
type: "string",
|
|
2358
|
-
description: "How well the docs supported the task, e.g. \"partial\" or \"clear\"."
|
|
2359
|
-
},
|
|
2360
|
-
outcome: {
|
|
2361
|
-
type: "string",
|
|
2362
|
-
description: "What happened after reading the docs, e.g. \"implemented\" or \"blocked\"."
|
|
2363
|
-
},
|
|
2364
|
-
confidence: {
|
|
2365
|
-
type: "number",
|
|
2366
|
-
minimum: 0,
|
|
2367
|
-
maximum: 1,
|
|
2368
|
-
description: "Confidence score from 0 to 1."
|
|
2369
|
-
},
|
|
2370
|
-
neededCodeReading: {
|
|
2371
|
-
type: "boolean",
|
|
2372
|
-
description: "Whether the agent still needed to inspect repository code."
|
|
2373
|
-
},
|
|
2374
|
-
missingContext: {
|
|
2375
|
-
type: "array",
|
|
2376
|
-
items: { type: "string" },
|
|
2377
|
-
description: "Important details the docs did not provide clearly enough."
|
|
2378
|
-
},
|
|
2379
|
-
docIssues: {
|
|
2380
|
-
type: "array",
|
|
2381
|
-
items: { type: "string" },
|
|
2382
|
-
description: "Specific documentation problems encountered during the task."
|
|
2383
|
-
},
|
|
2384
|
-
suggestedImprovement: {
|
|
2385
|
-
type: "string",
|
|
2386
|
-
description: "Concrete suggestion for improving the docs page or examples."
|
|
2387
|
-
}
|
|
2388
|
-
},
|
|
2389
|
-
required: ["task", "outcome"]
|
|
2390
|
-
};
|
|
2391
|
-
const DEFAULT_DOCS_DIAGNOSTICS_MCP_TOOLS = {
|
|
2392
|
-
listDocs: true,
|
|
2393
|
-
listPages: true,
|
|
2394
|
-
readPage: true,
|
|
2395
|
-
listTasks: true,
|
|
2396
|
-
readTask: true,
|
|
2397
|
-
searchDocs: true,
|
|
2398
|
-
getNavigation: true,
|
|
2399
|
-
getCodeExamples: true,
|
|
2400
|
-
getConfigSchema: true,
|
|
2401
|
-
getContext: true
|
|
2402
|
-
};
|
|
2403
|
-
const DOCS_MARKDOWN_SIGNATURE_AGENT_HEADER = "Signature-Agent";
|
|
2404
|
-
const DOCS_AI_AGENT_USER_AGENT_PATTERNS = [
|
|
2405
|
-
"claudebot",
|
|
2406
|
-
"claude-searchbot",
|
|
2407
|
-
"claude-user",
|
|
2408
|
-
"anthropic-ai",
|
|
2409
|
-
"claude-web",
|
|
2410
|
-
"chatgpt",
|
|
2411
|
-
"gptbot",
|
|
2412
|
-
"oai-searchbot",
|
|
2413
|
-
"openai",
|
|
2414
|
-
"gemini",
|
|
2415
|
-
"bard",
|
|
2416
|
-
"google-cloudvertexbot",
|
|
2417
|
-
"google-extended",
|
|
2418
|
-
"meta-externalagent",
|
|
2419
|
-
"meta-externalfetcher",
|
|
2420
|
-
"meta-webindexer",
|
|
2421
|
-
"perplexity",
|
|
2422
|
-
"youbot",
|
|
2423
|
-
"you.com",
|
|
2424
|
-
"deepseekbot",
|
|
2425
|
-
"cursor",
|
|
2426
|
-
"github-copilot",
|
|
2427
|
-
"codeium",
|
|
2428
|
-
"tabnine",
|
|
2429
|
-
"sourcegraph",
|
|
2430
|
-
"cohere-ai",
|
|
2431
|
-
"bytespider",
|
|
2432
|
-
"amazonbot",
|
|
2433
|
-
"ai2bot",
|
|
2434
|
-
"diffbot",
|
|
2435
|
-
"omgili",
|
|
2436
|
-
"omgilibot"
|
|
2437
|
-
];
|
|
2438
|
-
const DOCS_TRADITIONAL_BOT_USER_AGENT_PATTERNS = [
|
|
2439
|
-
"googlebot",
|
|
2440
|
-
"bingbot",
|
|
2441
|
-
"yandexbot",
|
|
2442
|
-
"baiduspider",
|
|
2443
|
-
"duckduckbot",
|
|
2444
|
-
"slurp",
|
|
2445
|
-
"msnbot",
|
|
2446
|
-
"facebot",
|
|
2447
|
-
"twitterbot",
|
|
2448
|
-
"linkedinbot",
|
|
2449
|
-
"whatsapp",
|
|
2450
|
-
"telegrambot",
|
|
2451
|
-
"pingdom",
|
|
2452
|
-
"uptimerobot",
|
|
2453
|
-
"newrelic",
|
|
2454
|
-
"datadog",
|
|
2455
|
-
"statuspage",
|
|
2456
|
-
"site24x7",
|
|
2457
|
-
"applebot"
|
|
2458
|
-
];
|
|
2459
|
-
const DOCS_BOT_LIKE_USER_AGENT_PATTERN = /bot|agent|fetch|crawl|spider|search/i;
|
|
2460
|
-
const DOCS_BOT_LIKE_USER_AGENT_TERMS = [
|
|
2461
|
-
"bot",
|
|
2462
|
-
"agent",
|
|
2463
|
-
"fetch",
|
|
2464
|
-
"crawl",
|
|
2465
|
-
"spider",
|
|
2466
|
-
"search"
|
|
2467
|
-
];
|
|
2468
|
-
const DOCS_AI_AGENT_USER_AGENT_HEADER_PATTERN = buildDocsUserAgentHeaderPattern(DOCS_AI_AGENT_USER_AGENT_PATTERNS);
|
|
2469
|
-
const DOCS_TRADITIONAL_BOT_USER_AGENT_HEADER_PATTERN = buildDocsUserAgentHeaderPattern(DOCS_TRADITIONAL_BOT_USER_AGENT_PATTERNS);
|
|
2470
|
-
const DOCS_BOT_LIKE_USER_AGENT_HEADER_PATTERN = buildDocsUserAgentHeaderPattern(DOCS_BOT_LIKE_USER_AGENT_TERMS);
|
|
2471
|
-
const DOCS_LLMS_TXT_DIRECTIVE_LINE = "LLM index: /llms.txt";
|
|
2472
|
-
const DOCS_MCP_SERVICE_SUBDOMAIN_LABELS = new Set([
|
|
2473
|
-
"api",
|
|
2474
|
-
"developer",
|
|
2475
|
-
"developers",
|
|
2476
|
-
"dev",
|
|
2477
|
-
"docs",
|
|
2478
|
-
"help",
|
|
2479
|
-
"mcp",
|
|
2480
|
-
"reference"
|
|
2481
|
-
]);
|
|
2482
|
-
const COMMON_SECOND_LEVEL_PUBLIC_SUFFIX_LABELS = new Set([
|
|
2483
|
-
"ac",
|
|
2484
|
-
"co",
|
|
2485
|
-
"com",
|
|
2486
|
-
"edu",
|
|
2487
|
-
"go",
|
|
2488
|
-
"gov",
|
|
2489
|
-
"mil",
|
|
2490
|
-
"ne",
|
|
2491
|
-
"net",
|
|
2492
|
-
"or",
|
|
2493
|
-
"org"
|
|
2494
|
-
]);
|
|
2495
|
-
const DOCS_CONFIG_MAP_TOP_LEVEL_KEYS = [
|
|
2496
|
-
"entry",
|
|
2497
|
-
"docsPath",
|
|
2498
|
-
"contentDir",
|
|
2499
|
-
"i18n",
|
|
2500
|
-
"staticExport",
|
|
2501
|
-
"theme",
|
|
2502
|
-
"analytics",
|
|
2503
|
-
"telemetry",
|
|
2504
|
-
"cloud",
|
|
2505
|
-
"observability",
|
|
2506
|
-
"github",
|
|
2507
|
-
"nav",
|
|
2508
|
-
"themeToggle",
|
|
2509
|
-
"breadcrumb",
|
|
2510
|
-
"sidebar",
|
|
2511
|
-
"components",
|
|
2512
|
-
"onCopyClick",
|
|
2513
|
-
"codeBlocks",
|
|
2514
|
-
"feedback",
|
|
2515
|
-
"mcp",
|
|
2516
|
-
"icons",
|
|
2517
|
-
"pageActions",
|
|
2518
|
-
"search",
|
|
2519
|
-
"lastUpdated",
|
|
2520
|
-
"readingTime",
|
|
2521
|
-
"ai",
|
|
2522
|
-
"ordering",
|
|
2523
|
-
"llmsTxt",
|
|
2524
|
-
"sitemap",
|
|
2525
|
-
"robots",
|
|
2526
|
-
"changelog",
|
|
2527
|
-
"apiReference",
|
|
2528
|
-
"agent",
|
|
2529
|
-
"review",
|
|
2530
|
-
"metadata",
|
|
2531
|
-
"og"
|
|
2532
|
-
];
|
|
2533
|
-
function normalizeDocsPathSegment(value) {
|
|
2534
|
-
return value.replace(/^\/+|\/+$/g, "");
|
|
2535
|
-
}
|
|
2536
|
-
function normalizeDocsUrlPath(value) {
|
|
2537
|
-
const normalized = value.replace(/\/+/g, "/");
|
|
2538
|
-
if (normalized === "/") return normalized;
|
|
2539
|
-
return normalized.replace(/\/+$/, "");
|
|
2540
|
-
}
|
|
2541
|
-
function isPlainObject(value) {
|
|
2542
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2543
|
-
}
|
|
2544
|
-
function isDocsConfigRequest(url) {
|
|
2545
|
-
return url.searchParams.get("format")?.trim() === "config";
|
|
2546
|
-
}
|
|
2547
|
-
function isDocsDiagnosticsRequest(url) {
|
|
2548
|
-
return url.searchParams.get("format")?.trim() === "diagnostics";
|
|
2549
|
-
}
|
|
2550
|
-
/** Prefer an explicitly configured API route; otherwise infer a query-form route from the request. */
|
|
2551
|
-
function resolveDocsRequestApiRoute(url, configuredApiRoute) {
|
|
2552
|
-
const configured = configuredApiRoute?.trim();
|
|
2553
|
-
if (configured) return resolveDocsDiscoveryApiRoute(configured);
|
|
2554
|
-
const fallback = resolveDocsDiscoveryApiRoute();
|
|
2555
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
2556
|
-
const hasPublicRepresentationSuffix = /\.(?:md|txt|xml)$/i.test(pathname);
|
|
2557
|
-
if (pathname.startsWith("/.well-known/") || hasPublicRepresentationSuffix) return fallback;
|
|
2558
|
-
return Boolean(url.searchParams.get("format")?.trim()) || url.searchParams.get("agent")?.trim() === "spec" || url.searchParams.has("query") || url.searchParams.get("feedback")?.trim() === "agent" ? resolveDocsDiscoveryApiRoute(pathname) : fallback;
|
|
2559
|
-
}
|
|
2560
|
-
function buildDocsConfigMap(config, options = {}) {
|
|
2561
|
-
const pointers = {};
|
|
2562
|
-
const values = serializeDocsConfigMapValue(pickDocsConfigMapValues(config), [], /* @__PURE__ */ new WeakSet(), pointers);
|
|
2563
|
-
const mappedValues = isPlainObject(values) ? values : {};
|
|
2564
|
-
const language = containsDocsConfigKind(mappedValues, "jsx") ? "tsx" : "ts";
|
|
2565
|
-
return {
|
|
2566
|
-
schemaVersion: 1,
|
|
2567
|
-
format: DEFAULT_DOCS_CONFIG_FORMAT,
|
|
2568
|
-
source: {
|
|
2569
|
-
file: options.file ?? (language === "tsx" ? "docs.config.tsx" : "docs.config.ts"),
|
|
2570
|
-
language
|
|
2571
|
-
},
|
|
2572
|
-
serialization: {
|
|
2573
|
-
mode: "json-safe",
|
|
2574
|
-
redacted: "Scalar values under secret-like keys such as apiKey, token, secret, password, authorization, and credential are redacted unless the key ends with Env.",
|
|
2575
|
-
nonSerializable: "described"
|
|
2576
|
-
},
|
|
2577
|
-
values: mappedValues,
|
|
2578
|
-
pointers
|
|
2579
|
-
};
|
|
2580
|
-
}
|
|
2581
|
-
function buildDocsDiagnostics(config, options = {}) {
|
|
2582
|
-
const input = config;
|
|
2583
|
-
const entry = normalizeDocsPathSegment(stringConfigValue(input.entry) ?? options.entry ?? "docs");
|
|
2584
|
-
const docsRoute = routeFromConfigPath(stringConfigValue(input.docsPath) ?? entry);
|
|
2585
|
-
const cloud = isPlainObject(input.cloud) ? input.cloud : void 0;
|
|
2586
|
-
const apiRoute = resolveDocsDiscoveryApiRoute(options.apiRoute ?? stringConfigValue(cloud?.apiRoute));
|
|
2587
|
-
const apiQueryRoute = (query) => `${apiRoute}?${query}`;
|
|
2588
|
-
const staticExport = input.staticExport === true;
|
|
2589
|
-
const i18n = options.i18n ?? null;
|
|
2590
|
-
const localesEnabled = Boolean(i18n?.locales.length);
|
|
2591
|
-
const search = resolveDocsDiagnosticsSearch(input.search, staticExport);
|
|
2592
|
-
const ai = resolveDocsDiagnosticsAi(input.ai, staticExport);
|
|
2593
|
-
const llms = resolveDocsDiagnosticsLlms(input.llmsTxt);
|
|
2594
|
-
const apiCatalog = resolveDocsDiagnosticsApiCatalog(input.llmsTxt, staticExport, options.apiCatalog);
|
|
2595
|
-
const sitemapConfig = resolveDocsSitemapConfig(input.sitemap);
|
|
2596
|
-
const robotsEnabled = isRobotsDiscoveryEnabled(input.robots);
|
|
2597
|
-
const openapiConfig = resolveDocsOpenApiDiscoveryConfig(options.openapi ?? input.apiReference);
|
|
2598
|
-
const openapiUrl = resolveDocsOpenApiDiscoveryUrl(openapiConfig, apiRoute);
|
|
2599
|
-
const apiReferenceRoute = resolveDocsDiagnosticsApiReferenceRoute(input.apiReference);
|
|
2600
|
-
const mcp = options.mcp ?? resolveDocsDiagnosticsMcp(input.mcp);
|
|
2601
|
-
const feedback = options.feedback ?? resolveDocsAgentFeedbackConfig(input.feedback);
|
|
2602
|
-
const agentFeedbackEnabled = feedback.enabled === true;
|
|
2603
|
-
const agentFeedbackRoute = feedback.route ?? DEFAULT_AGENT_FEEDBACK_ROUTE;
|
|
2604
|
-
const agentFeedbackSchemaRoute = feedback.schemaRoute ?? `${agentFeedbackRoute}/schema`;
|
|
2605
|
-
const humanFeedback = isDocsDiagnosticsHumanFeedbackEnabled(input.feedback);
|
|
2606
|
-
const warnings = [];
|
|
2607
|
-
const errors = [];
|
|
2608
|
-
if (staticExport) warnings.push({
|
|
2609
|
-
severity: "warning",
|
|
2610
|
-
code: "static-export-runtime-api",
|
|
2611
|
-
path: "/staticExport",
|
|
2612
|
-
message: `staticExport is enabled; runtime API-backed capabilities at ${apiRoute} are unavailable in production static export builds.`
|
|
2613
|
-
});
|
|
2614
|
-
if (staticExport && ai.configured) errors.push({
|
|
2615
|
-
severity: "error",
|
|
2616
|
-
code: "ai-static-export",
|
|
2617
|
-
path: "/ai/enabled",
|
|
2618
|
-
message: `Ask AI requires the runtime ${apiRoute} POST handler and will not run in static export builds.`
|
|
2619
|
-
});
|
|
2620
|
-
if (ai.enabled && !search.enabled) warnings.push({
|
|
2621
|
-
severity: "warning",
|
|
2622
|
-
code: "ai-without-search",
|
|
2623
|
-
path: "/ai",
|
|
2624
|
-
message: "Ask AI is enabled while docs search is disabled, so retrieval context may be unavailable."
|
|
2625
|
-
});
|
|
2626
|
-
const adapter = options.adapter?.trim() || null;
|
|
2627
|
-
if (adapter && adapter !== "next") {
|
|
2628
|
-
if (!stringConfigValue(input.contentDir)) warnings.push({
|
|
2629
|
-
severity: "warning",
|
|
2630
|
-
code: "missing-content-dir",
|
|
2631
|
-
path: "/contentDir",
|
|
2632
|
-
message: `${adapter} docs usually need contentDir so the server can find markdown content.`
|
|
2633
|
-
});
|
|
2634
|
-
if (!isPlainObject(input.nav)) warnings.push({
|
|
2635
|
-
severity: "warning",
|
|
2636
|
-
code: "missing-nav",
|
|
2637
|
-
path: "/nav",
|
|
2638
|
-
message: `${adapter} docs usually need nav.title and nav.url for sidebar branding and root navigation.`
|
|
2639
|
-
});
|
|
2640
|
-
}
|
|
2641
|
-
validateDocsDiagnosticsSearchConfig(input.search, errors);
|
|
2642
|
-
return {
|
|
2643
|
-
schemaVersion: 1,
|
|
2644
|
-
format: DEFAULT_DOCS_DIAGNOSTICS_FORMAT,
|
|
2645
|
-
ok: errors.length === 0,
|
|
2646
|
-
adapter,
|
|
2647
|
-
routes: {
|
|
2648
|
-
docs: docsRoute,
|
|
2649
|
-
api: apiRoute,
|
|
2650
|
-
config: apiQueryRoute("format=config"),
|
|
2651
|
-
diagnostics: apiQueryRoute("format=diagnostics"),
|
|
2652
|
-
agentSpec: apiRoute === DEFAULT_DOCS_API_ROUTE ? DEFAULT_AGENT_SPEC_ROUTE : apiQueryRoute("agent=spec"),
|
|
2653
|
-
apiCatalog: apiCatalog.enabled ? DEFAULT_API_CATALOG_ROUTE : null,
|
|
2654
|
-
agentSkillsIndex: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
2655
|
-
agentSkillsArtifact: DEFAULT_AGENT_SKILLS_ROUTE_PATTERN,
|
|
2656
|
-
agents: apiQueryRoute("format=agents"),
|
|
2657
|
-
skill: apiQueryRoute("format=skill"),
|
|
2658
|
-
search: search.enabled ? apiQueryRoute("query={query}") : null,
|
|
2659
|
-
askAi: ai.enabled ? apiRoute : null,
|
|
2660
|
-
mcp: mcp.enabled ? mcp.route : null,
|
|
2661
|
-
llmsTxt: llms.enabled ? DEFAULT_LLMS_TXT_ROUTE : null,
|
|
2662
|
-
llmsFullTxt: llms.enabled ? DEFAULT_LLMS_FULL_TXT_ROUTE : null,
|
|
2663
|
-
sitemapXml: sitemapConfig.enabled && sitemapConfig.xml.enabled ? sitemapConfig.xml.route : null,
|
|
2664
|
-
sitemapMarkdown: sitemapConfig.enabled && sitemapConfig.markdown.enabled ? sitemapConfig.markdown.route : null,
|
|
2665
|
-
robots: robotsEnabled ? DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE : null,
|
|
2666
|
-
openapi: openapiConfig.enabled ? openapiUrl ?? apiQueryRoute("format=openapi") : null,
|
|
2667
|
-
apiReference: openapiConfig.enabled ? apiReferenceRoute : null
|
|
2668
|
-
},
|
|
2669
|
-
features: {
|
|
2670
|
-
staticExport: { status: staticExport ? "enabled" : "disabled" },
|
|
2671
|
-
config: {
|
|
2672
|
-
status: "enabled",
|
|
2673
|
-
route: apiQueryRoute("format=config")
|
|
2674
|
-
},
|
|
2675
|
-
diagnostics: {
|
|
2676
|
-
status: "enabled",
|
|
2677
|
-
route: apiQueryRoute("format=diagnostics")
|
|
2678
|
-
},
|
|
2679
|
-
apiCatalog: {
|
|
2680
|
-
status: apiCatalog.enabled ? "enabled" : "disabled",
|
|
2681
|
-
reason: apiCatalog.reason,
|
|
2682
|
-
route: apiCatalog.enabled ? DEFAULT_API_CATALOG_ROUTE : null,
|
|
2683
|
-
transport: "GET/HEAD"
|
|
2684
|
-
},
|
|
2685
|
-
search: {
|
|
2686
|
-
status: search.enabled ? "enabled" : "disabled",
|
|
2687
|
-
reason: search.reason,
|
|
2688
|
-
route: search.enabled ? apiQueryRoute("query={query}") : null,
|
|
2689
|
-
provider: search.provider,
|
|
2690
|
-
transport: "GET"
|
|
2691
|
-
},
|
|
2692
|
-
ai: {
|
|
2693
|
-
status: ai.enabled ? "enabled" : "disabled",
|
|
2694
|
-
reason: ai.reason,
|
|
2695
|
-
route: ai.enabled ? apiRoute : null,
|
|
2696
|
-
mode: ai.mode,
|
|
2697
|
-
transport: "POST"
|
|
2698
|
-
},
|
|
2699
|
-
mcp: {
|
|
2700
|
-
status: mcp.enabled ? "enabled" : "disabled",
|
|
2701
|
-
route: mcp.enabled ? mcp.route : null,
|
|
2702
|
-
transport: "GET/POST",
|
|
2703
|
-
tools: mcp.tools
|
|
2704
|
-
},
|
|
2705
|
-
feedback: {
|
|
2706
|
-
status: agentFeedbackEnabled || humanFeedback ? "enabled" : "disabled",
|
|
2707
|
-
human: humanFeedback,
|
|
2708
|
-
agent: agentFeedbackEnabled,
|
|
2709
|
-
routes: {
|
|
2710
|
-
agentSubmit: agentFeedbackEnabled ? agentFeedbackRoute : null,
|
|
2711
|
-
agentSchema: agentFeedbackEnabled ? agentFeedbackSchemaRoute : null
|
|
2712
|
-
}
|
|
2713
|
-
},
|
|
2714
|
-
llmsTxt: {
|
|
2715
|
-
status: llms.enabled ? "enabled" : "disabled",
|
|
2716
|
-
reason: llms.reason,
|
|
2717
|
-
routes: {
|
|
2718
|
-
txt: llms.enabled ? DEFAULT_LLMS_TXT_ROUTE : null,
|
|
2719
|
-
full: llms.enabled ? DEFAULT_LLMS_FULL_TXT_ROUTE : null,
|
|
2720
|
-
wellKnownTxt: llms.enabled ? DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE : null,
|
|
2721
|
-
wellKnownFull: llms.enabled ? DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE : null
|
|
2722
|
-
}
|
|
2723
|
-
},
|
|
2724
|
-
sitemap: {
|
|
2725
|
-
status: sitemapConfig.enabled ? "enabled" : "disabled",
|
|
2726
|
-
routes: {
|
|
2727
|
-
xml: sitemapConfig.enabled && sitemapConfig.xml.enabled ? sitemapConfig.xml.route : null,
|
|
2728
|
-
markdown: sitemapConfig.enabled && sitemapConfig.markdown.enabled ? sitemapConfig.markdown.route : null,
|
|
2729
|
-
docsMarkdown: sitemapConfig.enabled && sitemapConfig.markdown.enabled ? sitemapConfig.markdown.docsRoute ?? null : null,
|
|
2730
|
-
wellKnownMarkdown: sitemapConfig.enabled && sitemapConfig.markdown.enabled ? sitemapConfig.markdown.wellKnownRoute : null
|
|
2731
|
-
}
|
|
2732
|
-
},
|
|
2733
|
-
robots: {
|
|
2734
|
-
status: robotsEnabled ? "enabled" : "disabled",
|
|
2735
|
-
route: robotsEnabled ? DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE : null
|
|
2736
|
-
},
|
|
2737
|
-
apiReference: {
|
|
2738
|
-
status: openapiConfig.enabled ? "enabled" : "disabled",
|
|
2739
|
-
route: openapiConfig.enabled ? apiReferenceRoute : null,
|
|
2740
|
-
routes: { openapi: openapiConfig.enabled ? openapiUrl ?? apiQueryRoute("format=openapi") : null },
|
|
2741
|
-
provider: openapiConfig.source
|
|
2742
|
-
},
|
|
2743
|
-
agents: {
|
|
2744
|
-
status: "enabled",
|
|
2745
|
-
routes: {
|
|
2746
|
-
default: DEFAULT_AGENTS_MD_ROUTE,
|
|
2747
|
-
wellKnown: DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE,
|
|
2748
|
-
api: apiQueryRoute("format=agents")
|
|
2749
|
-
}
|
|
2750
|
-
},
|
|
2751
|
-
skills: {
|
|
2752
|
-
status: "enabled",
|
|
2753
|
-
transport: "GET/HEAD",
|
|
2754
|
-
routes: {
|
|
2755
|
-
default: DEFAULT_SKILL_MD_ROUTE,
|
|
2756
|
-
wellKnown: DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE,
|
|
2757
|
-
api: apiQueryRoute("format=skill"),
|
|
2758
|
-
index: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
2759
|
-
artifact: DEFAULT_AGENT_SKILLS_ROUTE_PATTERN
|
|
2760
|
-
}
|
|
2761
|
-
},
|
|
2762
|
-
locales: {
|
|
2763
|
-
status: localesEnabled ? "enabled" : "disabled",
|
|
2764
|
-
provider: localesEnabled ? "query-param" : void 0
|
|
2765
|
-
}
|
|
2766
|
-
},
|
|
2767
|
-
warnings,
|
|
2768
|
-
errors
|
|
2769
|
-
};
|
|
2770
|
-
}
|
|
2771
|
-
function pickDocsConfigMapValues(config) {
|
|
2772
|
-
const output = {};
|
|
2773
|
-
for (const key of DOCS_CONFIG_MAP_TOP_LEVEL_KEYS) if (Object.prototype.hasOwnProperty.call(config, key)) output[key] = config[key];
|
|
2774
|
-
return output;
|
|
2775
|
-
}
|
|
2776
|
-
function serializeDocsConfigMapValue(value, path, seen, pointers, depth = 0) {
|
|
2777
|
-
const key = path.at(-1) ?? "";
|
|
2778
|
-
const pointerPath = toDocsConfigJsonPointer(path);
|
|
2779
|
-
const setPointer = (kind) => {
|
|
2780
|
-
if (path.length > 0) pointers[pointerPath] = {
|
|
2781
|
-
path: toDocsConfigDotPath(path),
|
|
2782
|
-
kind
|
|
2783
|
-
};
|
|
2784
|
-
};
|
|
2785
|
-
if (value === void 0) return void 0;
|
|
2786
|
-
if (shouldRedactDocsConfigKey(key) && value !== null && typeof value !== "object") {
|
|
2787
|
-
setPointer("secret");
|
|
2788
|
-
return {
|
|
2789
|
-
$kind: "secret",
|
|
2790
|
-
value: "[redacted]"
|
|
2791
|
-
};
|
|
2792
|
-
}
|
|
2793
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
2794
|
-
setPointer(typeof value);
|
|
2795
|
-
return value;
|
|
2796
|
-
}
|
|
2797
|
-
if (typeof value === "number") {
|
|
2798
|
-
setPointer("number");
|
|
2799
|
-
return Number.isFinite(value) ? value : String(value);
|
|
2800
|
-
}
|
|
2801
|
-
if (typeof value === "bigint") {
|
|
2802
|
-
setPointer("bigint");
|
|
2803
|
-
return {
|
|
2804
|
-
$kind: "bigint",
|
|
2805
|
-
value: value.toString()
|
|
2806
|
-
};
|
|
2807
|
-
}
|
|
2808
|
-
if (typeof value === "symbol") {
|
|
2809
|
-
setPointer("symbol");
|
|
2810
|
-
return {
|
|
2811
|
-
$kind: "symbol",
|
|
2812
|
-
value: String(value)
|
|
2813
|
-
};
|
|
2814
|
-
}
|
|
2815
|
-
if (typeof value === "function") {
|
|
2816
|
-
setPointer("function");
|
|
2817
|
-
return {
|
|
2818
|
-
$kind: "function",
|
|
2819
|
-
name: getDocsConfigFunctionName(value)
|
|
2820
|
-
};
|
|
2821
|
-
}
|
|
2822
|
-
if (value instanceof Date) {
|
|
2823
|
-
setPointer("date");
|
|
2824
|
-
return {
|
|
2825
|
-
$kind: "date",
|
|
2826
|
-
value: value.toISOString()
|
|
2827
|
-
};
|
|
2828
|
-
}
|
|
2829
|
-
if (value instanceof URL) {
|
|
2830
|
-
setPointer("url");
|
|
2831
|
-
return value.toString();
|
|
2832
|
-
}
|
|
2833
|
-
if (depth >= 10) {
|
|
2834
|
-
setPointer("truncated");
|
|
2835
|
-
return {
|
|
2836
|
-
$kind: "truncated",
|
|
2837
|
-
reason: "Exceeded max depth of 10."
|
|
2838
|
-
};
|
|
2839
|
-
}
|
|
2840
|
-
if (Array.isArray(value)) {
|
|
2841
|
-
setPointer("array");
|
|
2842
|
-
return value.map((entry, index) => serializeDocsConfigMapValue(entry, [...path, String(index)], seen, pointers, depth + 1)).filter((entry) => entry !== void 0);
|
|
2843
|
-
}
|
|
2844
|
-
if (isDocsConfigReactElement(value)) {
|
|
2845
|
-
setPointer("jsx");
|
|
2846
|
-
return {
|
|
2847
|
-
$kind: "jsx",
|
|
2848
|
-
component: getDocsConfigReactElementName(value)
|
|
2849
|
-
};
|
|
2850
|
-
}
|
|
2851
|
-
if (seen.has(value)) {
|
|
2852
|
-
setPointer("circular");
|
|
2853
|
-
return { $kind: "circular" };
|
|
2854
|
-
}
|
|
2855
|
-
seen.add(value);
|
|
2856
|
-
const output = {};
|
|
2857
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
2858
|
-
const serialized = serializeDocsConfigMapValue(childValue, [...path, childKey], seen, pointers, depth + 1);
|
|
2859
|
-
if (serialized !== void 0) output[childKey] = serialized;
|
|
2860
|
-
}
|
|
2861
|
-
seen.delete(value);
|
|
2862
|
-
if (path.length === 1 && path[0] === "theme") {
|
|
2863
|
-
setPointer("theme");
|
|
2864
|
-
return {
|
|
2865
|
-
$kind: "theme",
|
|
2866
|
-
...output
|
|
2867
|
-
};
|
|
2868
|
-
}
|
|
2869
|
-
setPointer("object");
|
|
2870
|
-
return output;
|
|
2871
|
-
}
|
|
2872
|
-
function shouldRedactDocsConfigKey(key) {
|
|
2873
|
-
const normalized = key.replace(/[-_]/g, "").toLowerCase();
|
|
2874
|
-
if (!normalized || normalized === "env" || normalized.endsWith("env")) return false;
|
|
2875
|
-
return normalized.includes("apikey") || normalized.includes("token") || normalized.includes("secret") || normalized.includes("password") || normalized.includes("authorization") || normalized.includes("credential");
|
|
2876
|
-
}
|
|
2877
|
-
function isDocsConfigReactElement(value) {
|
|
2878
|
-
if (!isPlainObject(value) || !("$$typeof" in value)) return false;
|
|
2879
|
-
return String(value.$$typeof).includes("react.");
|
|
2880
|
-
}
|
|
2881
|
-
function getDocsConfigReactElementName(value) {
|
|
2882
|
-
const type = value.type;
|
|
2883
|
-
if (typeof type === "string") return type;
|
|
2884
|
-
if (typeof type === "function") return getDocsConfigFunctionName(type);
|
|
2885
|
-
if (isPlainObject(type)) {
|
|
2886
|
-
const displayName = type.displayName;
|
|
2887
|
-
if (typeof displayName === "string") return displayName;
|
|
2888
|
-
const render = type.render;
|
|
2889
|
-
if (typeof render === "function") return getDocsConfigFunctionName(render);
|
|
2890
|
-
}
|
|
2891
|
-
return "unknown";
|
|
2892
|
-
}
|
|
2893
|
-
function getDocsConfigFunctionName(value) {
|
|
2894
|
-
const displayName = value.displayName;
|
|
2895
|
-
return typeof displayName === "string" ? displayName : value.name || "anonymous";
|
|
2896
|
-
}
|
|
2897
|
-
function toDocsConfigJsonPointer(path) {
|
|
2898
|
-
return path.length === 0 ? "" : `/${path.map(escapeDocsConfigJsonPointerPart).join("/")}`;
|
|
2899
|
-
}
|
|
2900
|
-
function escapeDocsConfigJsonPointerPart(value) {
|
|
2901
|
-
return value.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2902
|
-
}
|
|
2903
|
-
function toDocsConfigDotPath(path) {
|
|
2904
|
-
return path.map((part, index) => /^\d+$/.test(part) ? `[${part}]` : index === 0 ? part : `.${part}`).join("");
|
|
2905
|
-
}
|
|
2906
|
-
function containsDocsConfigKind(value, kind) {
|
|
2907
|
-
if (!isPlainObject(value)) {
|
|
2908
|
-
if (!Array.isArray(value)) return false;
|
|
2909
|
-
}
|
|
2910
|
-
if (Array.isArray(value)) return value.some((entry) => containsDocsConfigKind(entry, kind));
|
|
2911
|
-
if (value["$kind"] === kind) return true;
|
|
2912
|
-
return Object.values(value).some((entry) => containsDocsConfigKind(entry, kind));
|
|
2913
|
-
}
|
|
2914
|
-
function stringConfigValue(value) {
|
|
2915
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
2916
|
-
}
|
|
2917
|
-
function routeFromConfigPath(value) {
|
|
2918
|
-
const normalized = normalizeDocsPathSegment(value);
|
|
2919
|
-
return normalized ? `/${normalized}` : "/";
|
|
2920
|
-
}
|
|
2921
|
-
function resolveDocsDiagnosticsSearch(search, staticExport) {
|
|
2922
|
-
const provider = isPlainObject(search) ? stringConfigValue(search.provider) : "simple";
|
|
2923
|
-
const configured = search !== false && !(isPlainObject(search) && search.enabled === false);
|
|
2924
|
-
if (staticExport) return {
|
|
2925
|
-
enabled: false,
|
|
2926
|
-
configured,
|
|
2927
|
-
provider,
|
|
2928
|
-
reason: "static-export"
|
|
2929
|
-
};
|
|
2930
|
-
if (!configured) return {
|
|
2931
|
-
enabled: false,
|
|
2932
|
-
configured,
|
|
2933
|
-
provider,
|
|
2934
|
-
reason: "configured-disabled"
|
|
2935
|
-
};
|
|
2936
|
-
return {
|
|
2937
|
-
enabled: true,
|
|
2938
|
-
configured,
|
|
2939
|
-
provider
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
function resolveDocsDiagnosticsAi(ai, staticExport) {
|
|
2943
|
-
const configured = isPlainObject(ai) && ai.enabled === true;
|
|
2944
|
-
const mode = isPlainObject(ai) ? stringConfigValue(ai.mode) : void 0;
|
|
2945
|
-
if (!configured) return {
|
|
2946
|
-
enabled: false,
|
|
2947
|
-
configured,
|
|
2948
|
-
reason: "not-configured"
|
|
2949
|
-
};
|
|
2950
|
-
if (staticExport) return {
|
|
2951
|
-
enabled: false,
|
|
2952
|
-
configured,
|
|
2953
|
-
mode,
|
|
2954
|
-
reason: "static-export"
|
|
2955
|
-
};
|
|
2956
|
-
return {
|
|
2957
|
-
enabled: true,
|
|
2958
|
-
configured,
|
|
2959
|
-
mode
|
|
2960
|
-
};
|
|
2961
|
-
}
|
|
2962
|
-
function resolveDocsDiagnosticsLlms(llmsTxt) {
|
|
2963
|
-
return llmsTxt !== false && !(isPlainObject(llmsTxt) && llmsTxt.enabled === false) ? { enabled: true } : {
|
|
2964
|
-
enabled: false,
|
|
2965
|
-
reason: "configured-disabled"
|
|
2966
|
-
};
|
|
2967
|
-
}
|
|
2968
|
-
function resolveDocsDiagnosticsApiCatalog(llmsTxt, staticExport, explicit) {
|
|
2969
|
-
if (staticExport) return {
|
|
2970
|
-
enabled: false,
|
|
2971
|
-
reason: "static-export"
|
|
2972
|
-
};
|
|
2973
|
-
if (explicit !== void 0) return explicit ? { enabled: true } : {
|
|
2974
|
-
enabled: false,
|
|
2975
|
-
reason: "configured-disabled"
|
|
2976
|
-
};
|
|
2977
|
-
if (isPlainObject(llmsTxt) && llmsTxt.apiCatalog === false) return {
|
|
2978
|
-
enabled: false,
|
|
2979
|
-
reason: "llms-txt-api-catalog-disabled"
|
|
2980
|
-
};
|
|
2981
|
-
return { enabled: true };
|
|
2982
|
-
}
|
|
2983
|
-
function resolveDocsDiagnosticsMcp(mcp) {
|
|
2984
|
-
const config = isPlainObject(mcp) ? mcp : {};
|
|
2985
|
-
const tools = isPlainObject(config.tools) ? config.tools : {};
|
|
2986
|
-
return {
|
|
2987
|
-
enabled: typeof mcp === "boolean" ? mcp : config.enabled !== false,
|
|
2988
|
-
route: normalizeDocsDiagnosticsMcpRoute(config.route),
|
|
2989
|
-
name: stringConfigValue(config.name) ?? "Documentation",
|
|
2990
|
-
version: stringConfigValue(config.version) ?? "0.0.0",
|
|
2991
|
-
tools: {
|
|
2992
|
-
...DEFAULT_DOCS_DIAGNOSTICS_MCP_TOOLS,
|
|
2993
|
-
listDocs: tools.listDocs !== false,
|
|
2994
|
-
listPages: tools.listPages !== false,
|
|
2995
|
-
readPage: tools.readPage !== false,
|
|
2996
|
-
listTasks: tools.listTasks !== false,
|
|
2997
|
-
readTask: tools.readTask !== false,
|
|
2998
|
-
searchDocs: tools.searchDocs !== false,
|
|
2999
|
-
getNavigation: tools.getNavigation !== false,
|
|
3000
|
-
getCodeExamples: tools.getCodeExamples !== false,
|
|
3001
|
-
getConfigSchema: tools.getConfigSchema !== false,
|
|
3002
|
-
getContext: tools.getContext !== false
|
|
3003
|
-
}
|
|
3004
|
-
};
|
|
3005
|
-
}
|
|
3006
|
-
function normalizeDocsDiagnosticsMcpRoute(route) {
|
|
3007
|
-
const value = stringConfigValue(route);
|
|
3008
|
-
if (!value) return DEFAULT_MCP_ROUTE;
|
|
3009
|
-
const normalized = `/${value}`.replace(/\/+/g, "/");
|
|
3010
|
-
return normalized !== "/" ? normalized.replace(/\/+$/, "") : DEFAULT_MCP_ROUTE;
|
|
3011
|
-
}
|
|
3012
|
-
function isDocsDiagnosticsHumanFeedbackEnabled(feedback) {
|
|
3013
|
-
return feedback === true || isPlainObject(feedback) && feedback.enabled !== false;
|
|
3014
|
-
}
|
|
3015
|
-
function resolveDocsDiagnosticsApiReferenceRoute(apiReference) {
|
|
3016
|
-
if (isPlainObject(apiReference)) return routeFromConfigPath(stringConfigValue(apiReference.path) ?? "api-reference");
|
|
3017
|
-
return "/api-reference";
|
|
3018
|
-
}
|
|
3019
|
-
function validateDocsDiagnosticsSearchConfig(search, errors) {
|
|
3020
|
-
if (!isPlainObject(search) || search.enabled === false) return;
|
|
3021
|
-
const provider = stringConfigValue(search.provider) ?? "simple";
|
|
3022
|
-
const requireString = (key) => {
|
|
3023
|
-
if (!stringConfigValue(search[key])) errors.push({
|
|
3024
|
-
severity: "error",
|
|
3025
|
-
code: `missing-search-${key.replace(/[A-Z]/g, (value) => `-${value.toLowerCase()}`)}`,
|
|
3026
|
-
path: `/search/${key}`,
|
|
3027
|
-
message: `Search provider "${provider}" requires search.${key}.`
|
|
3028
|
-
});
|
|
3029
|
-
};
|
|
3030
|
-
if (provider === "algolia") {
|
|
3031
|
-
requireString("appId");
|
|
3032
|
-
requireString("indexName");
|
|
3033
|
-
requireString("searchApiKey");
|
|
3034
|
-
}
|
|
3035
|
-
if (provider === "typesense") {
|
|
3036
|
-
requireString("baseUrl");
|
|
3037
|
-
requireString("collection");
|
|
3038
|
-
requireString("apiKey");
|
|
3039
|
-
}
|
|
3040
|
-
if (provider === "mcp") requireString("endpoint");
|
|
3041
|
-
if (provider === "custom" && !("adapter" in search)) errors.push({
|
|
3042
|
-
severity: "error",
|
|
3043
|
-
code: "missing-search-adapter",
|
|
3044
|
-
path: "/search/adapter",
|
|
3045
|
-
message: "Search provider \"custom\" requires search.adapter."
|
|
3046
|
-
});
|
|
3047
|
-
}
|
|
3048
|
-
function normalizeDocsAgentFeedbackRoute(route, fallback = DEFAULT_AGENT_FEEDBACK_ROUTE) {
|
|
3049
|
-
if (!route || route.trim().length === 0) return fallback;
|
|
3050
|
-
const normalized = `/${route}`.replace(/\/+/g, "/");
|
|
3051
|
-
return normalized !== "/" ? normalized.replace(/\/+$/, "") : fallback;
|
|
3052
|
-
}
|
|
3053
|
-
function buildDocsAgentFeedbackSchema(payloadSchema) {
|
|
3054
|
-
return {
|
|
3055
|
-
type: "object",
|
|
3056
|
-
additionalProperties: false,
|
|
3057
|
-
properties: {
|
|
3058
|
-
context: {
|
|
3059
|
-
type: "object",
|
|
3060
|
-
additionalProperties: false,
|
|
3061
|
-
properties: {
|
|
3062
|
-
page: { type: "string" },
|
|
3063
|
-
url: { type: "string" },
|
|
3064
|
-
slug: { type: "string" },
|
|
3065
|
-
locale: { type: "string" },
|
|
3066
|
-
source: { type: "string" }
|
|
3067
|
-
}
|
|
3068
|
-
},
|
|
3069
|
-
payload: payloadSchema
|
|
3070
|
-
},
|
|
3071
|
-
required: ["payload"]
|
|
3072
|
-
};
|
|
3073
|
-
}
|
|
3074
|
-
function resolveDocsAgentFeedbackConfig(feedback) {
|
|
3075
|
-
const route = normalizeDocsAgentFeedbackRoute();
|
|
3076
|
-
const enabled = {
|
|
3077
|
-
enabled: true,
|
|
3078
|
-
route,
|
|
3079
|
-
schemaRoute: `${route}/schema`,
|
|
3080
|
-
payloadSchema: DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA,
|
|
3081
|
-
schema: buildDocsAgentFeedbackSchema(DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA)
|
|
3082
|
-
};
|
|
3083
|
-
const disabled = {
|
|
3084
|
-
enabled: false,
|
|
3085
|
-
route,
|
|
3086
|
-
schemaRoute: `${route}/schema`,
|
|
3087
|
-
payloadSchema: DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA,
|
|
3088
|
-
schema: buildDocsAgentFeedbackSchema(DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA)
|
|
3089
|
-
};
|
|
3090
|
-
if (feedback === false) return disabled;
|
|
3091
|
-
if (!feedback || feedback === true || typeof feedback !== "object") return enabled;
|
|
3092
|
-
const agent = feedback.agent;
|
|
3093
|
-
if (agent === false) return disabled;
|
|
3094
|
-
if (!agent) return enabled;
|
|
3095
|
-
if (agent === true) return enabled;
|
|
3096
|
-
const resolvedRoute = normalizeDocsAgentFeedbackRoute(agent.route, route);
|
|
3097
|
-
const resolvedSchemaRoute = normalizeDocsAgentFeedbackRoute(agent.schemaRoute, `${resolvedRoute}/schema`);
|
|
3098
|
-
const payloadSchema = agent.schema ?? DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA;
|
|
3099
|
-
return {
|
|
3100
|
-
enabled: agent.enabled !== false,
|
|
3101
|
-
route: resolvedRoute,
|
|
3102
|
-
schemaRoute: resolvedSchemaRoute,
|
|
3103
|
-
payloadSchema,
|
|
3104
|
-
schema: buildDocsAgentFeedbackSchema(payloadSchema),
|
|
3105
|
-
onFeedback: agent.onFeedback
|
|
3106
|
-
};
|
|
3107
|
-
}
|
|
3108
|
-
function resolveDocsAgentFeedbackRequest(url, feedback) {
|
|
3109
|
-
if (!feedback.enabled) return null;
|
|
3110
|
-
const feedbackMode = url.searchParams.get("feedback")?.trim();
|
|
3111
|
-
const schemaMode = url.searchParams.get("schema")?.trim();
|
|
3112
|
-
if (feedbackMode === "agent") return { kind: schemaMode === "1" || schemaMode === "true" ? "schema" : "submit" };
|
|
3113
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3114
|
-
if (pathname === feedback.schemaRoute) return { kind: "schema" };
|
|
3115
|
-
if (pathname === feedback.route) return { kind: "submit" };
|
|
3116
|
-
return null;
|
|
3117
|
-
}
|
|
3118
|
-
function normalizeDocsAgentFeedbackContext(value) {
|
|
3119
|
-
if (!isPlainObject(value)) return void 0;
|
|
3120
|
-
const context = {};
|
|
3121
|
-
if (typeof value.page === "string") context.page = value.page;
|
|
3122
|
-
if (typeof value.url === "string") context.url = value.url;
|
|
3123
|
-
if (typeof value.slug === "string") context.slug = value.slug;
|
|
3124
|
-
if (typeof value.locale === "string") context.locale = value.locale;
|
|
3125
|
-
if (typeof value.source === "string") context.source = value.source;
|
|
3126
|
-
return Object.keys(context).length > 0 ? context : void 0;
|
|
3127
|
-
}
|
|
3128
|
-
async function parseDocsAgentFeedbackData(request) {
|
|
3129
|
-
let body;
|
|
3130
|
-
try {
|
|
3131
|
-
body = await request.json();
|
|
3132
|
-
} catch {
|
|
3133
|
-
return {
|
|
3134
|
-
ok: false,
|
|
3135
|
-
response: Response.json({ error: "Agent feedback body must be valid JSON" }, { status: 400 })
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
if (!isPlainObject(body)) return {
|
|
3139
|
-
ok: false,
|
|
3140
|
-
response: Response.json({ error: "Agent feedback body must be an object" }, { status: 400 })
|
|
3141
|
-
};
|
|
3142
|
-
if (!isPlainObject(body.payload)) return {
|
|
3143
|
-
ok: false,
|
|
3144
|
-
response: Response.json({ error: "Agent feedback body must include a payload object" }, { status: 400 })
|
|
3145
|
-
};
|
|
3146
|
-
return {
|
|
3147
|
-
ok: true,
|
|
3148
|
-
data: {
|
|
3149
|
-
context: normalizeDocsAgentFeedbackContext(body.context),
|
|
3150
|
-
payload: body.payload
|
|
3151
|
-
}
|
|
3152
|
-
};
|
|
3153
|
-
}
|
|
3154
|
-
function validateDocsAgentFeedbackPayload(value, schema, valuePath = "payload") {
|
|
3155
|
-
const schemaType = typeof schema.type === "string" ? schema.type : void 0;
|
|
3156
|
-
if (Array.isArray(schema.enum) && !schema.enum.some((entry) => Object.is(entry, value))) return `${valuePath} must be one of the configured enum values`;
|
|
3157
|
-
if (schemaType === "object" || !schemaType && (schema.properties || schema.required)) {
|
|
3158
|
-
if (!isPlainObject(value)) return `${valuePath} must be an object`;
|
|
3159
|
-
const properties = isPlainObject(schema.properties) ? schema.properties : {};
|
|
3160
|
-
const required = Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === "string") : [];
|
|
3161
|
-
for (const key of required) if (!(key in value)) return `${valuePath}.${key} is required`;
|
|
3162
|
-
if (schema.additionalProperties === false) {
|
|
3163
|
-
for (const key of Object.keys(value)) if (!(key in properties)) return `${valuePath}.${key} is not allowed`;
|
|
3164
|
-
}
|
|
3165
|
-
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
3166
|
-
if (!(key in value)) continue;
|
|
3167
|
-
if (!isPlainObject(propertySchema)) continue;
|
|
3168
|
-
const error = validateDocsAgentFeedbackPayload(value[key], propertySchema, `${valuePath}.${key}`);
|
|
3169
|
-
if (error) return error;
|
|
3170
|
-
}
|
|
3171
|
-
return;
|
|
3172
|
-
}
|
|
3173
|
-
if (schemaType === "array") {
|
|
3174
|
-
if (!Array.isArray(value)) return `${valuePath} must be an array`;
|
|
3175
|
-
if (!isPlainObject(schema.items)) return void 0;
|
|
3176
|
-
for (const [index, item] of value.entries()) {
|
|
3177
|
-
const error = validateDocsAgentFeedbackPayload(item, schema.items, `${valuePath}[${index}]`);
|
|
3178
|
-
if (error) return error;
|
|
3179
|
-
}
|
|
3180
|
-
return;
|
|
3181
|
-
}
|
|
3182
|
-
if (schemaType === "string") {
|
|
3183
|
-
if (typeof value !== "string") return `${valuePath} must be a string`;
|
|
3184
|
-
if (typeof schema.minLength === "number" && value.length < schema.minLength) return `${valuePath} must be at least ${schema.minLength} characters`;
|
|
3185
|
-
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) return `${valuePath} must be at most ${schema.maxLength} characters`;
|
|
3186
|
-
return;
|
|
3187
|
-
}
|
|
3188
|
-
if (schemaType === "number") {
|
|
3189
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return `${valuePath} must be a number`;
|
|
3190
|
-
if (typeof schema.minimum === "number" && value < schema.minimum) return `${valuePath} must be >= ${schema.minimum}`;
|
|
3191
|
-
if (typeof schema.maximum === "number" && value > schema.maximum) return `${valuePath} must be <= ${schema.maximum}`;
|
|
3192
|
-
return;
|
|
3193
|
-
}
|
|
3194
|
-
if (schemaType === "boolean") {
|
|
3195
|
-
if (typeof value !== "boolean") return `${valuePath} must be a boolean`;
|
|
3196
|
-
return;
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
function toDocsMarkdownUrl(url, options = {}) {
|
|
3200
|
-
const [withoutHash, hash = ""] = url.split("#", 2);
|
|
3201
|
-
const [pathname, query = ""] = withoutHash.split("?", 2);
|
|
3202
|
-
const normalizedPath = normalizeDocsUrlPath(pathname || "/");
|
|
3203
|
-
const markdownPath = normalizedPath.endsWith(".md") ? normalizedPath : `${normalizedPath}.md`;
|
|
3204
|
-
const params = new URLSearchParams(query);
|
|
3205
|
-
if (options.locale && !params.has("lang")) params.set("lang", options.locale);
|
|
3206
|
-
const search = params.toString();
|
|
3207
|
-
return `${markdownPath}${search ? `?${search}` : ""}${hash ? `#${hash}` : ""}`;
|
|
3208
|
-
}
|
|
3209
|
-
function resolveDocsMarkdownCanonicalUrl({ origin, entry = "docs", requestedPath, locale }) {
|
|
3210
|
-
const pathname = normalizeRequestedMarkdownPath(normalizeDocsPathSegment(entry) || "docs", requestedPath);
|
|
3211
|
-
const canonicalUrl = new URL(pathname, origin);
|
|
3212
|
-
if (locale) canonicalUrl.searchParams.set("lang", locale);
|
|
3213
|
-
return canonicalUrl.toString();
|
|
3214
|
-
}
|
|
3215
|
-
function getDocsMarkdownCanonicalLinkHeader(options) {
|
|
3216
|
-
return `<${resolveDocsMarkdownCanonicalUrl(options)}>; rel="canonical"`;
|
|
3217
|
-
}
|
|
3218
|
-
function joinDocsPublicRoute(prefix, suffix) {
|
|
3219
|
-
const normalizedPrefix = normalizeDocsUrlPath(`/${normalizeDocsPathSegment(prefix)}`);
|
|
3220
|
-
const normalizedSuffix = `/${normalizeDocsPathSegment(suffix)}`;
|
|
3221
|
-
if (normalizedPrefix === "/") return normalizedSuffix;
|
|
3222
|
-
return normalizeDocsUrlPath(`${normalizedPrefix}${normalizedSuffix}`);
|
|
3223
|
-
}
|
|
3224
|
-
function normalizeLlmsTxtMatch(value) {
|
|
3225
|
-
const trimmed = value.trim();
|
|
3226
|
-
if (!trimmed) return "/";
|
|
3227
|
-
return normalizeDocsUrlPath(trimmed.startsWith("/") ? trimmed : `/${trimmed}`);
|
|
3228
|
-
}
|
|
3229
|
-
function llmsTxtMatchPrefix(value) {
|
|
3230
|
-
const normalized = normalizeLlmsTxtMatch(value);
|
|
3231
|
-
const wildcardIndex = normalized.indexOf("*");
|
|
3232
|
-
return normalizeDocsUrlPath((wildcardIndex >= 0 ? normalized.slice(0, wildcardIndex) : normalized).replace(/\/+$/, "") || "/");
|
|
3233
|
-
}
|
|
3234
|
-
function deriveLlmsTxtSectionRoute(match) {
|
|
3235
|
-
return joinDocsPublicRoute(llmsTxtMatchPrefix(match[0] ?? "/"), "llms.txt");
|
|
3236
|
-
}
|
|
3237
|
-
function deriveLlmsTxtSectionFullRoute(route) {
|
|
3238
|
-
const normalized = normalizeDocsUrlPath(route);
|
|
3239
|
-
if (normalized.endsWith("/llms.txt")) return `${normalized.slice(0, -9)}/llms-full.txt`;
|
|
3240
|
-
return joinDocsPublicRoute(normalized, "llms-full.txt");
|
|
3241
|
-
}
|
|
3242
|
-
function resolveDocsBaseLlmsTxtRoute(basePath) {
|
|
3243
|
-
if (!basePath) return null;
|
|
3244
|
-
return joinDocsPublicRoute(normalizeDocsPathSegment(basePath) || "docs", "llms.txt");
|
|
3245
|
-
}
|
|
3246
|
-
function resolveDocsBaseLlmsFullTxtRoute(basePath) {
|
|
3247
|
-
const route = resolveDocsBaseLlmsTxtRoute(basePath);
|
|
3248
|
-
return route ? deriveLlmsTxtSectionFullRoute(route) : null;
|
|
3249
|
-
}
|
|
3250
|
-
function normalizeLlmsTxtMaxChars(value, fallback) {
|
|
3251
|
-
const mode = value?.mode ?? fallback?.mode ?? "warn";
|
|
3252
|
-
const chars = value?.chars ?? fallback?.chars ?? DEFAULT_LLMS_TXT_MAX_CHARS;
|
|
3253
|
-
return {
|
|
3254
|
-
mode,
|
|
3255
|
-
chars: Number.isFinite(chars) && chars > 0 ? Math.floor(chars) : DEFAULT_LLMS_TXT_MAX_CHARS
|
|
3256
|
-
};
|
|
3257
|
-
}
|
|
3258
|
-
function resolveDocsLlmsTxtSections(llms) {
|
|
3259
|
-
if (!llms || typeof llms !== "object" || !Array.isArray(llms.sections)) return [];
|
|
3260
|
-
const rootMaxChars = normalizeLlmsTxtMaxChars(llms.maxChars);
|
|
3261
|
-
return llms.sections.flatMap((section) => {
|
|
3262
|
-
const match = (Array.isArray(section.match) ? section.match : [section.match]).map(normalizeLlmsTxtMatch).filter(Boolean);
|
|
3263
|
-
if (match.length === 0) return [];
|
|
3264
|
-
const route = deriveLlmsTxtSectionRoute(match);
|
|
3265
|
-
const resolved = {
|
|
3266
|
-
title: section.title,
|
|
3267
|
-
match,
|
|
3268
|
-
route,
|
|
3269
|
-
fullRoute: deriveLlmsTxtSectionFullRoute(route),
|
|
3270
|
-
maxChars: normalizeLlmsTxtMaxChars(section.maxChars, rootMaxChars)
|
|
3271
|
-
};
|
|
3272
|
-
if (section.description !== void 0) resolved.description = section.description;
|
|
3273
|
-
return [resolved];
|
|
3274
|
-
});
|
|
3275
|
-
}
|
|
3276
|
-
function matchesDocsLlmsTxtSection(pageUrl, section) {
|
|
3277
|
-
const pathname = normalizeDocsUrlPath(pageUrl.split(/[?#]/, 1)[0] || "/");
|
|
3278
|
-
return section.match.some((pattern) => {
|
|
3279
|
-
const normalized = normalizeLlmsTxtMatch(pattern);
|
|
3280
|
-
if (normalized.endsWith("/**")) {
|
|
3281
|
-
const prefix = normalizeDocsUrlPath(normalized.slice(0, -3) || "/");
|
|
3282
|
-
if (prefix === "/") return true;
|
|
3283
|
-
return pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
3284
|
-
}
|
|
3285
|
-
if (normalized.endsWith("/*")) {
|
|
3286
|
-
const prefix = normalizeDocsUrlPath(normalized.slice(0, -2) || "/");
|
|
3287
|
-
if (prefix === "/") {
|
|
3288
|
-
const directChild = pathname.slice(1);
|
|
3289
|
-
return directChild.length > 0 && !directChild.includes("/");
|
|
3290
|
-
}
|
|
3291
|
-
if (!pathname.startsWith(`${prefix}/`)) return false;
|
|
3292
|
-
return !pathname.slice(prefix.length + 1).includes("/");
|
|
3293
|
-
}
|
|
3294
|
-
return pathname === normalized;
|
|
3295
|
-
});
|
|
3296
|
-
}
|
|
3297
|
-
function resolveDocsLlmsTxtRequest(url, llms, basePath, options = {}) {
|
|
3298
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3299
|
-
const apiRoute = resolveDocsDiscoveryApiRoute(options.apiRoute);
|
|
3300
|
-
const sections = resolveDocsLlmsTxtSections(llms);
|
|
3301
|
-
for (const section of sections) {
|
|
3302
|
-
if (pathname === section.route) return {
|
|
3303
|
-
format: "llms",
|
|
3304
|
-
section
|
|
3305
|
-
};
|
|
3306
|
-
if (pathname === section.fullRoute) return {
|
|
3307
|
-
format: "llms-full",
|
|
3308
|
-
section
|
|
3309
|
-
};
|
|
3310
|
-
}
|
|
3311
|
-
const format = url.searchParams.get("format");
|
|
3312
|
-
if (pathname === apiRoute && (format === "llms" || format === "llms-full")) {
|
|
3313
|
-
const sectionRoute = url.searchParams.get("section")?.trim();
|
|
3314
|
-
const normalizedSectionRoute = sectionRoute ? normalizeDocsUrlPath(sectionRoute) : void 0;
|
|
3315
|
-
return {
|
|
3316
|
-
format,
|
|
3317
|
-
section: normalizedSectionRoute ? sections.find((candidate) => candidate.route === normalizedSectionRoute || candidate.fullRoute === normalizedSectionRoute) ?? {
|
|
3318
|
-
title: "",
|
|
3319
|
-
match: [],
|
|
3320
|
-
route: normalizedSectionRoute,
|
|
3321
|
-
fullRoute: normalizedSectionRoute,
|
|
3322
|
-
maxChars: normalizeLlmsTxtMaxChars(void 0)
|
|
3323
|
-
} : void 0
|
|
3324
|
-
};
|
|
3325
|
-
}
|
|
3326
|
-
if (pathname === DEFAULT_LLMS_TXT_ROUTE || pathname === DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE) return { format: "llms" };
|
|
3327
|
-
if (pathname === resolveDocsBaseLlmsTxtRoute(basePath)) return { format: "llms" };
|
|
3328
|
-
if (pathname === DEFAULT_LLMS_FULL_TXT_ROUTE || pathname === DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE) return { format: "llms-full" };
|
|
3329
|
-
if (pathname === resolveDocsBaseLlmsFullTxtRoute(basePath)) return { format: "llms-full" };
|
|
3330
|
-
return null;
|
|
3331
|
-
}
|
|
3332
|
-
function renderLlmsTxtPageList(pages, baseUrl) {
|
|
3333
|
-
let content = "";
|
|
3334
|
-
for (const page of pages) {
|
|
3335
|
-
content += `- [${page.title}](${baseUrl}${page.markdownUrl ?? toDocsMarkdownUrl(page.url)})`;
|
|
3336
|
-
if (page.description) content += `: ${page.description}`;
|
|
3337
|
-
content += "\n";
|
|
3338
|
-
}
|
|
3339
|
-
return content;
|
|
3340
|
-
}
|
|
3341
|
-
function resolveDocsResourceUrl(baseUrl, url) {
|
|
3342
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(url)) return url;
|
|
3343
|
-
return `${baseUrl}${url.startsWith("/") ? url : `/${url}`}`;
|
|
3344
|
-
}
|
|
3345
|
-
function renderLlmsFullTxtPages(pages, baseUrl) {
|
|
3346
|
-
let content = "";
|
|
3347
|
-
for (const page of pages) {
|
|
3348
|
-
content += `## ${page.title}\n\n`;
|
|
3349
|
-
content += `URL: ${baseUrl}${page.url}\n\n`;
|
|
3350
|
-
if (page.description) content += `${page.description}\n\n`;
|
|
3351
|
-
const agentContent = page.agentRawContent ?? page.agentFallbackRawContent ?? page.agentContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content;
|
|
3352
|
-
content += `${agentContent}\n\n---\n\n`;
|
|
3353
|
-
}
|
|
3354
|
-
return content;
|
|
3355
|
-
}
|
|
3356
|
-
function renderDocsLlmsTxt(pages, options = {}) {
|
|
3357
|
-
const siteTitle = options.siteTitle ?? "Documentation";
|
|
3358
|
-
const siteDescription = options.siteDescription;
|
|
3359
|
-
const baseUrl = options.baseUrl ?? "";
|
|
3360
|
-
const maxChars = normalizeLlmsTxtMaxChars(options.maxChars);
|
|
3361
|
-
const apiCatalogEnabled = options.apiCatalog ?? true;
|
|
3362
|
-
const sections = resolveDocsLlmsTxtSections(options);
|
|
3363
|
-
const openapi = resolveDocsOpenApiDiscoveryConfig(options.openapi);
|
|
3364
|
-
const openapiUrl = resolveDocsOpenApiDiscoveryUrl(openapi, resolveDocsDiscoveryApiRoute(options.apiRoute));
|
|
3365
|
-
const matchedPageUrls = /* @__PURE__ */ new Set();
|
|
3366
|
-
const generatedSections = sections.map((section) => {
|
|
3367
|
-
const sectionPages = pages.filter((page) => matchesDocsLlmsTxtSection(page.url, section));
|
|
3368
|
-
for (const page of sectionPages) matchedPageUrls.add(normalizeDocsUrlPath(page.url));
|
|
3369
|
-
let llmsTxt = `# ${siteTitle} - ${section.title}\n\n`;
|
|
3370
|
-
if (section.description) llmsTxt += `> ${section.description}\n\n`;
|
|
3371
|
-
else if (siteDescription) llmsTxt += `> ${siteDescription}\n\n`;
|
|
3372
|
-
llmsTxt += "## Pages\n\n";
|
|
3373
|
-
llmsTxt += renderLlmsTxtPageList(sectionPages, baseUrl);
|
|
3374
|
-
let llmsFullTxt = `# ${siteTitle} - ${section.title}\n\n`;
|
|
3375
|
-
if (section.description) llmsFullTxt += `> ${section.description}\n\n`;
|
|
3376
|
-
else if (siteDescription) llmsFullTxt += `> ${siteDescription}\n\n`;
|
|
3377
|
-
llmsFullTxt += renderLlmsFullTxtPages(sectionPages, baseUrl);
|
|
3378
|
-
return {
|
|
3379
|
-
...section,
|
|
3380
|
-
llmsTxt,
|
|
3381
|
-
llmsFullTxt
|
|
3382
|
-
};
|
|
3383
|
-
});
|
|
3384
|
-
const rootPages = generatedSections.length > 0 ? pages.filter((page) => !matchedPageUrls.has(normalizeDocsUrlPath(page.url))) : pages;
|
|
3385
|
-
let llmsTxt = `# ${siteTitle}\n\n`;
|
|
3386
|
-
if (siteDescription) llmsTxt += `> ${siteDescription}\n\n`;
|
|
3387
|
-
llmsTxt += "## Agent Discovery\n\n";
|
|
3388
|
-
llmsTxt += `- [Agent manifest](${resolveDocsResourceUrl(baseUrl, DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE)}): Farming Labs discovery manifest\n`;
|
|
3389
|
-
if (apiCatalogEnabled) llmsTxt += `- [API catalog](${resolveDocsResourceUrl(baseUrl, DEFAULT_API_CATALOG_ROUTE)}): RFC 9727 API catalog\n`;
|
|
3390
|
-
llmsTxt += `- [Agent Skills index](${resolveDocsResourceUrl(baseUrl, DEFAULT_AGENT_SKILLS_INDEX_ROUTE)}): Hashed Agent Skills discovery\n\n`;
|
|
3391
|
-
if (generatedSections.length > 0) {
|
|
3392
|
-
llmsTxt += "## Sections\n\n";
|
|
3393
|
-
for (const section of generatedSections) {
|
|
3394
|
-
llmsTxt += `- [${section.title}](${baseUrl}${section.route})`;
|
|
3395
|
-
if (section.description) llmsTxt += `: ${section.description}`;
|
|
3396
|
-
llmsTxt += "\n";
|
|
3397
|
-
}
|
|
3398
|
-
llmsTxt += "\n";
|
|
3399
|
-
}
|
|
3400
|
-
if (openapi.enabled && openapiUrl) {
|
|
3401
|
-
llmsTxt += "## API Schemas\n\n";
|
|
3402
|
-
llmsTxt += `- [OpenAPI schema](${resolveDocsResourceUrl(baseUrl, openapiUrl)}): Machine-readable API schema for tool use and API clients`;
|
|
3403
|
-
if (openapi.apiReferencePath) llmsTxt += `; rendered API reference at ${resolveDocsResourceUrl(baseUrl, openapi.apiReferencePath)}`;
|
|
3404
|
-
llmsTxt += "\n\n";
|
|
3405
|
-
}
|
|
3406
|
-
if (rootPages.length > 0 || generatedSections.length === 0) {
|
|
3407
|
-
llmsTxt += "## Pages\n\n";
|
|
3408
|
-
llmsTxt += renderLlmsTxtPageList(rootPages, baseUrl);
|
|
3409
|
-
}
|
|
3410
|
-
let llmsFullTxt = `# ${siteTitle}\n\n`;
|
|
3411
|
-
if (siteDescription) llmsFullTxt += `> ${siteDescription}\n\n`;
|
|
3412
|
-
llmsFullTxt += renderLlmsFullTxtPages(pages, baseUrl);
|
|
3413
|
-
return {
|
|
3414
|
-
llmsTxt,
|
|
3415
|
-
llmsFullTxt,
|
|
3416
|
-
maxChars,
|
|
3417
|
-
sections: generatedSections
|
|
3418
|
-
};
|
|
3419
|
-
}
|
|
3420
|
-
function selectDocsLlmsTxtContent(content, request) {
|
|
3421
|
-
if (request.section) {
|
|
3422
|
-
const section = content.sections.find((candidate) => candidate.route === request.section?.route);
|
|
3423
|
-
if (!section) return null;
|
|
3424
|
-
return {
|
|
3425
|
-
content: request.format === "llms-full" ? section.llmsFullTxt : section.llmsTxt,
|
|
3426
|
-
label: request.format === "llms-full" ? section.fullRoute : section.route,
|
|
3427
|
-
maxChars: request.format === "llms-full" ? {
|
|
3428
|
-
...section.maxChars,
|
|
3429
|
-
mode: "off"
|
|
3430
|
-
} : section.maxChars
|
|
3431
|
-
};
|
|
3432
|
-
}
|
|
3433
|
-
return {
|
|
3434
|
-
content: request.format === "llms-full" ? content.llmsFullTxt : content.llmsTxt,
|
|
3435
|
-
label: request.format === "llms-full" ? DEFAULT_LLMS_FULL_TXT_ROUTE : DEFAULT_LLMS_TXT_ROUTE,
|
|
3436
|
-
maxChars: request.format === "llms-full" ? {
|
|
3437
|
-
...content.maxChars,
|
|
3438
|
-
mode: "off"
|
|
3439
|
-
} : content.maxChars
|
|
3440
|
-
};
|
|
3441
|
-
}
|
|
3442
|
-
function getDocsLlmsTxtMaxCharsIssue(label, content, maxChars) {
|
|
3443
|
-
if (maxChars.mode === "off" || content.length <= maxChars.chars) return null;
|
|
3444
|
-
return {
|
|
3445
|
-
mode: maxChars.mode,
|
|
3446
|
-
chars: maxChars.chars,
|
|
3447
|
-
actual: content.length,
|
|
3448
|
-
label,
|
|
3449
|
-
message: `${label} is ${content.length.toLocaleString()} chars, above the configured ${maxChars.chars.toLocaleString()} char llms.txt budget.`
|
|
3450
|
-
};
|
|
3451
|
-
}
|
|
3452
|
-
function resolveDocsStandardsOrigin(preferred, fallback) {
|
|
3453
|
-
for (const candidate of [preferred, fallback]) {
|
|
3454
|
-
if (!candidate) continue;
|
|
3455
|
-
try {
|
|
3456
|
-
const url = new URL(candidate);
|
|
3457
|
-
if (url.protocol === "http:" || url.protocol === "https:") return url.origin;
|
|
3458
|
-
} catch {}
|
|
3459
|
-
}
|
|
3460
|
-
return new URL(fallback).origin;
|
|
3461
|
-
}
|
|
3462
|
-
/** Build and serve standards-based discovery without replacing the custom agent manifest. */
|
|
3463
|
-
async function createDocsStandardsDiscoveryResponse({ request, preferredSkillDocument, fallbackSkillDocument, origin, entry = "docs", docsPath, apiCatalog: explicitApiCatalog, apiRoute, i18n: _i18n, search: _search, mcp, feedback, llms, sitemap, robots, openapi, markdown: _markdown }) {
|
|
3464
|
-
const url = new URL(request.url);
|
|
3465
|
-
const resolvedApiRoute = resolveDocsDiscoveryApiRoute(apiRoute);
|
|
3466
|
-
if (!isDocsStandardsDiscoveryRequest(url, { apiRoute: resolvedApiRoute })) return null;
|
|
3467
|
-
const normalizedEntry = normalizeDocsPathSegment(entry) || "docs";
|
|
3468
|
-
const normalizedDocsPath = normalizeDocsPathSegment(docsPath ?? normalizedEntry);
|
|
3469
|
-
const apiCatalogEnabled = explicitApiCatalog ?? llms?.apiCatalog ?? true;
|
|
3470
|
-
const catalogOrigin = resolveDocsStandardsOrigin(llms?.baseUrl, origin || url.origin);
|
|
3471
|
-
const sitemapConfig = resolveDocsSitemapConfig(sitemap, { baseUrl: llms?.baseUrl });
|
|
3472
|
-
const openapiConfig = resolveDocsOpenApiDiscoveryConfig(openapi);
|
|
3473
|
-
const openapiUrl = resolveDocsOpenApiDiscoveryUrl(openapiConfig, resolvedApiRoute);
|
|
3474
|
-
const feedbackRoute = feedback?.route ?? DEFAULT_AGENT_FEEDBACK_ROUTE;
|
|
3475
|
-
const feedbackSchemaRoute = feedback?.schemaRoute ?? `${feedbackRoute}/schema`;
|
|
3476
|
-
const llmsEnabled = llms?.enabled ?? true;
|
|
3477
|
-
const robotsEnabled = isRobotsDiscoveryEnabled(robots);
|
|
3478
|
-
const llmsSections = resolveDocsLlmsTxtSections(llms);
|
|
3479
|
-
const llmsRoutes = llmsEnabled ? [
|
|
3480
|
-
DEFAULT_LLMS_TXT_ROUTE,
|
|
3481
|
-
DEFAULT_LLMS_FULL_TXT_ROUTE,
|
|
3482
|
-
DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE,
|
|
3483
|
-
DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE,
|
|
3484
|
-
...llmsSections.flatMap((section) => [section.route, section.fullRoute])
|
|
3485
|
-
] : [];
|
|
3486
|
-
const sitemapRoutes = sitemapConfig.enabled ? [...sitemapConfig.xml.enabled ? [sitemapConfig.xml.route] : [], ...sitemapConfig.markdown.enabled ? [
|
|
3487
|
-
sitemapConfig.markdown.route,
|
|
3488
|
-
sitemapConfig.markdown.docsRoute,
|
|
3489
|
-
sitemapConfig.markdown.wellKnownRoute
|
|
3490
|
-
].filter((route) => Boolean(route)) : []] : [];
|
|
3491
|
-
return createDocsStandardsResponse({
|
|
3492
|
-
request,
|
|
3493
|
-
apiCatalogEnabled,
|
|
3494
|
-
apiRoute: resolvedApiRoute,
|
|
3495
|
-
preferredSkillDocument,
|
|
3496
|
-
fallbackSkillDocument,
|
|
3497
|
-
apiCatalog: apiCatalogEnabled ? buildDocsApiCatalog({
|
|
3498
|
-
origin: catalogOrigin,
|
|
3499
|
-
docsRoute: normalizedDocsPath ? `/${normalizedDocsPath}` : "/",
|
|
3500
|
-
apiRoute: resolvedApiRoute,
|
|
3501
|
-
configRoute: `${resolvedApiRoute}?format=config`,
|
|
3502
|
-
diagnosticsRoute: `${resolvedApiRoute}?format=diagnostics`,
|
|
3503
|
-
agentManifestRoute: DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
|
|
3504
|
-
agentSkillsIndexRoute: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
3505
|
-
agentsRoute: DEFAULT_AGENTS_MD_ROUTE,
|
|
3506
|
-
skillRoute: DEFAULT_SKILL_MD_ROUTE,
|
|
3507
|
-
markdownRootRoute: `/${normalizedEntry}.md`,
|
|
3508
|
-
llmsRoutes,
|
|
3509
|
-
sitemapRoutes,
|
|
3510
|
-
robotsRoute: robotsEnabled ? DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE : null,
|
|
3511
|
-
mcpRoute: mcp.enabled ? mcp.route : null,
|
|
3512
|
-
feedbackRoutes: feedback?.enabled ? [feedbackRoute, feedbackSchemaRoute] : [],
|
|
3513
|
-
openapiRoute: openapiConfig.enabled ? openapiUrl ?? `${resolvedApiRoute}?format=openapi` : null,
|
|
3514
|
-
apiReferenceRoute: openapiConfig.enabled && openapiConfig.apiReferencePath ? openapiConfig.apiReferencePath : null
|
|
3515
|
-
}) : void 0
|
|
3516
|
-
});
|
|
3517
|
-
}
|
|
3518
|
-
function isDocsAgentDiscoveryRequest(url, options = {}) {
|
|
3519
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3520
|
-
if (pathname === resolveDocsDiscoveryApiRoute(options.apiRoute) && url.searchParams.get("agent")?.trim() === "spec") return true;
|
|
3521
|
-
return pathname === DEFAULT_AGENT_SPEC_ROUTE || pathname === DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE || pathname === DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE;
|
|
3522
|
-
}
|
|
3523
|
-
function isDocsMcpRequest(url) {
|
|
3524
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3525
|
-
return pathname === DEFAULT_MCP_ROUTE || pathname === DEFAULT_MCP_PUBLIC_ROUTE || pathname === DEFAULT_MCP_WELL_KNOWN_ROUTE;
|
|
3526
|
-
}
|
|
3527
|
-
function buildDocsMcpEndpointCandidates(baseUrl, routes = [DEFAULT_MCP_PUBLIC_ROUTE, DEFAULT_MCP_WELL_KNOWN_ROUTE], options = {}) {
|
|
3528
|
-
const includeOriginFallback = options.includeOriginFallback !== false;
|
|
3529
|
-
const includeRootDomainFallback = options.includeRootDomainFallback !== false;
|
|
3530
|
-
const includeMcpSubdomainFallback = options.includeMcpSubdomainFallback !== false;
|
|
3531
|
-
const base = new URL(baseUrl);
|
|
3532
|
-
const primaryOrigin = base.origin;
|
|
3533
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3534
|
-
const candidates = [];
|
|
3535
|
-
const addCandidate = (candidateBaseUrl, route) => {
|
|
3536
|
-
const resolved = resolveDocsMcpCandidateUrl(candidateBaseUrl, route);
|
|
3537
|
-
if (seen.has(resolved.url)) return;
|
|
3538
|
-
seen.add(resolved.url);
|
|
3539
|
-
candidates.push({
|
|
3540
|
-
...resolved,
|
|
3541
|
-
label: formatDocsMcpCandidateLabel(resolved.url, primaryOrigin)
|
|
3542
|
-
});
|
|
3543
|
-
};
|
|
3544
|
-
for (const route of routes) addCandidate(baseUrl, route);
|
|
3545
|
-
const originBaseUrl = primaryOrigin;
|
|
3546
|
-
if (includeOriginFallback && originBaseUrl !== baseUrl.replace(/\/+$/, "")) for (const route of routes) addCandidate(originBaseUrl, route);
|
|
3547
|
-
if (includeRootDomainFallback) for (const rootDomainBaseUrl of toDocsRootDomainBaseUrls(base)) for (const route of routes) addCandidate(rootDomainBaseUrl, route);
|
|
3548
|
-
if (includeMcpSubdomainFallback) for (const mcpBaseUrl of toDocsMcpSubdomainBaseUrls(base)) {
|
|
3549
|
-
addCandidate(mcpBaseUrl, DEFAULT_MCP_PUBLIC_ROUTE);
|
|
3550
|
-
addCandidate(mcpBaseUrl, "/");
|
|
3551
|
-
}
|
|
3552
|
-
return candidates;
|
|
3553
|
-
}
|
|
3554
|
-
function resolveDocsMcpCandidateUrl(baseUrl, route) {
|
|
3555
|
-
if (/^https?:\/\//i.test(route)) {
|
|
3556
|
-
const parsed = new URL(route);
|
|
3557
|
-
const path = `${parsed.pathname || "/"}${parsed.search}`;
|
|
3558
|
-
return {
|
|
3559
|
-
baseUrl: parsed.origin,
|
|
3560
|
-
route: path,
|
|
3561
|
-
url: parsed.toString()
|
|
3562
|
-
};
|
|
3563
|
-
}
|
|
3564
|
-
const base = new URL(baseUrl);
|
|
3565
|
-
const basePath = base.pathname.replace(/\/+$/, "");
|
|
3566
|
-
const routePath = route.startsWith("/") ? route : `/${route}`;
|
|
3567
|
-
const parsed = new URL(`${basePath}${routePath}`, base.origin);
|
|
3568
|
-
return {
|
|
3569
|
-
baseUrl: parsed.origin,
|
|
3570
|
-
route: `${parsed.pathname}${parsed.search}`,
|
|
3571
|
-
url: parsed.toString()
|
|
3572
|
-
};
|
|
3573
|
-
}
|
|
3574
|
-
function formatDocsMcpCandidateLabel(url, primaryOrigin) {
|
|
3575
|
-
const parsed = new URL(url);
|
|
3576
|
-
const path = `${parsed.pathname}${parsed.search}`;
|
|
3577
|
-
return parsed.origin === primaryOrigin ? path : `${parsed.origin}${path}`;
|
|
3578
|
-
}
|
|
3579
|
-
function toDocsMcpSubdomainBaseUrls(base) {
|
|
3580
|
-
return getDocsMcpRootDomainCandidates(base.hostname).map((rootDomain) => `${base.protocol}//mcp.${rootDomain}${base.port ? `:${base.port}` : ""}`);
|
|
3581
|
-
}
|
|
3582
|
-
function toDocsRootDomainBaseUrls(base) {
|
|
3583
|
-
return getDocsMcpRootDomainCandidates(base.hostname).map((rootDomain) => `${base.protocol}//${rootDomain}${base.port ? `:${base.port}` : ""}`);
|
|
3584
|
-
}
|
|
3585
|
-
function getDocsMcpRootDomainCandidates(hostname) {
|
|
3586
|
-
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
3587
|
-
if (!normalized || !normalized.includes(".") || isDocsIpHostname(normalized)) return [];
|
|
3588
|
-
const labels = normalized.split(".").filter(Boolean);
|
|
3589
|
-
if (labels.length < 2) return [];
|
|
3590
|
-
const candidates = [];
|
|
3591
|
-
const addCandidate = (candidateLabels) => {
|
|
3592
|
-
if (candidateLabels.length < 2) return;
|
|
3593
|
-
const candidate = candidateLabels.join(".");
|
|
3594
|
-
if (!candidates.includes(candidate)) candidates.push(candidate);
|
|
3595
|
-
};
|
|
3596
|
-
if (labels.length >= 3 && DOCS_MCP_SERVICE_SUBDOMAIN_LABELS.has(labels[0] ?? "")) addCandidate(labels.slice(1));
|
|
3597
|
-
const tld = labels.at(-1) ?? "";
|
|
3598
|
-
const secondLevel = labels.at(-2) ?? "";
|
|
3599
|
-
if (labels.length >= 3 && tld.length === 2 && COMMON_SECOND_LEVEL_PUBLIC_SUFFIX_LABELS.has(secondLevel)) addCandidate(labels.slice(-3));
|
|
3600
|
-
else addCandidate(labels.slice(-2));
|
|
3601
|
-
return candidates;
|
|
3602
|
-
}
|
|
3603
|
-
function isDocsIpHostname(hostname) {
|
|
3604
|
-
return /^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname.includes(":");
|
|
3605
|
-
}
|
|
3606
|
-
function isDocsSkillRequest(url, options = {}) {
|
|
3607
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3608
|
-
if (pathname === DEFAULT_SKILL_MD_ROUTE || pathname === DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE) return true;
|
|
3609
|
-
return pathname === resolveDocsDiscoveryApiRoute(options.apiRoute) && resolveDocsSkillFormat(url) === "skill";
|
|
3610
|
-
}
|
|
3611
|
-
function resolveDocsSkillFormat(url) {
|
|
3612
|
-
return url.searchParams.get("format")?.trim() === "skill" ? "skill" : null;
|
|
3613
|
-
}
|
|
3614
|
-
function isDocsAgentsPath(pathname) {
|
|
3615
|
-
return pathname === DEFAULT_AGENTS_MD_ROUTE || pathname === DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE || pathname === DEFAULT_AGENT_MD_ROUTE || pathname === DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE;
|
|
3616
|
-
}
|
|
3617
|
-
function isDocsAgentsRequest(url, options = {}) {
|
|
3618
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3619
|
-
if (isDocsAgentsPath(pathname)) return true;
|
|
3620
|
-
return pathname === resolveDocsDiscoveryApiRoute(options.apiRoute) && resolveDocsAgentsFormat(url) === "agents";
|
|
3621
|
-
}
|
|
3622
|
-
function resolveDocsAgentsFormat(url) {
|
|
3623
|
-
return url.searchParams.get("format")?.trim() === "agents" ? "agents" : null;
|
|
3624
|
-
}
|
|
3625
|
-
function isDocsPublicGetRequest(entry, url, request, options = {}) {
|
|
3626
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3627
|
-
const apiRoute = resolveDocsDiscoveryApiRoute(options.apiRoute);
|
|
3628
|
-
if (pathname === apiRoute || pathname === DEFAULT_MCP_ROUTE) return false;
|
|
3629
|
-
return isDocsStandardsDiscoveryRequest(url, { apiRoute }) || isDocsAgentDiscoveryRequest(url, { apiRoute }) || isDocsAgentsRequest(url, { apiRoute }) || isDocsSkillRequest(url, { apiRoute }) || pathname === DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE && isRobotsDiscoveryEnabled(options.robots) || resolveDocsLlmsTxtRequest(url, options.llms, entry, { apiRoute }) !== null || resolveDocsSitemapRequest(url, options.sitemap, { apiRoute }) !== null || resolveDocsMarkdownRequest(entry, url, request, { apiRoute }) !== null;
|
|
3630
|
-
}
|
|
3631
|
-
function isDocsLlmsTxtPublicRequest(url, llms, basePath, options = {}) {
|
|
3632
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3633
|
-
const apiRoute = resolveDocsDiscoveryApiRoute(options.apiRoute);
|
|
3634
|
-
return pathname !== apiRoute && resolveDocsLlmsTxtRequest(url, llms, basePath, { apiRoute }) !== null;
|
|
3635
|
-
}
|
|
3636
|
-
function resolveDocsLlmsTxtFormat(url, basePath, options = {}) {
|
|
3637
|
-
return resolveDocsLlmsTxtRequest(url, void 0, basePath, options)?.format ?? null;
|
|
3638
|
-
}
|
|
3639
|
-
function resolveDocsMarkdownRequest(entry, url, request, options = {}) {
|
|
3640
|
-
const pathname = normalizeDocsUrlPath(url.pathname);
|
|
3641
|
-
const format = url.searchParams.get("format")?.trim();
|
|
3642
|
-
if (pathname === resolveDocsDiscoveryApiRoute(options.apiRoute) && format === "markdown") return { requestedPath: url.searchParams.get("path")?.trim() ?? "" };
|
|
3643
|
-
const normalizedEntry = `/${normalizeDocsPathSegment(entry) || "docs"}`;
|
|
3644
|
-
if (pathname === `${normalizedEntry}.md`) return { requestedPath: "" };
|
|
3645
|
-
const slugPrefix = `${normalizedEntry}/`;
|
|
3646
|
-
if (pathname.startsWith(slugPrefix) && pathname.endsWith(".md")) return { requestedPath: pathname.slice(slugPrefix.length, -3) };
|
|
3647
|
-
if (acceptsDocsMarkdown(request) || hasDocsMarkdownSignatureAgent(request) || detectDocsMarkdownAgentRequest(request).detected) {
|
|
3648
|
-
if (pathname === normalizedEntry) return { requestedPath: "" };
|
|
3649
|
-
if (pathname.startsWith(slugPrefix)) return { requestedPath: pathname.slice(slugPrefix.length) };
|
|
3650
|
-
}
|
|
3651
|
-
return null;
|
|
3652
|
-
}
|
|
3653
|
-
function hasDocsMarkdownSignatureAgent(request) {
|
|
3654
|
-
return Boolean(request.headers.get(DOCS_MARKDOWN_SIGNATURE_AGENT_HEADER)?.trim());
|
|
3655
|
-
}
|
|
3656
|
-
function detectDocsMarkdownAgentRequest(request) {
|
|
3657
|
-
if (hasDocsMarkdownSignatureAgent(request)) return {
|
|
3658
|
-
detected: true,
|
|
3659
|
-
method: "signature_agent"
|
|
3660
|
-
};
|
|
3661
|
-
const userAgent = request.headers.get("user-agent")?.trim().toLowerCase() ?? "";
|
|
3662
|
-
if (!userAgent) return {
|
|
3663
|
-
detected: false,
|
|
3664
|
-
method: null
|
|
3665
|
-
};
|
|
3666
|
-
if (DOCS_AI_AGENT_USER_AGENT_PATTERNS.some((pattern) => userAgent.includes(pattern))) return {
|
|
3667
|
-
detected: true,
|
|
3668
|
-
method: "user_agent"
|
|
3669
|
-
};
|
|
3670
|
-
if (!request.headers.get("sec-fetch-mode") && DOCS_BOT_LIKE_USER_AGENT_PATTERN.test(userAgent)) {
|
|
3671
|
-
if (!DOCS_TRADITIONAL_BOT_USER_AGENT_PATTERNS.some((pattern) => userAgent.includes(pattern))) return {
|
|
3672
|
-
detected: true,
|
|
3673
|
-
method: "heuristic"
|
|
3674
|
-
};
|
|
3675
|
-
}
|
|
3676
|
-
return {
|
|
3677
|
-
detected: false,
|
|
3678
|
-
method: null
|
|
3679
|
-
};
|
|
3680
|
-
}
|
|
3681
|
-
function getDocsMarkdownVaryHeader(request) {
|
|
3682
|
-
const values = /* @__PURE__ */ new Set();
|
|
3683
|
-
if (acceptsDocsMarkdown(request)) values.add("Accept");
|
|
3684
|
-
if (hasDocsMarkdownSignatureAgent(request)) {
|
|
3685
|
-
values.add("Accept");
|
|
3686
|
-
values.add(DOCS_MARKDOWN_SIGNATURE_AGENT_HEADER);
|
|
3687
|
-
}
|
|
3688
|
-
const agentDetection = detectDocsMarkdownAgentRequest(request);
|
|
3689
|
-
if (agentDetection.detected && agentDetection.method !== "signature_agent") {
|
|
3690
|
-
values.add("User-Agent");
|
|
3691
|
-
if (agentDetection.method === "heuristic") values.add("Sec-Fetch-Mode");
|
|
3692
|
-
}
|
|
3693
|
-
return values.size > 0 ? Array.from(values).join(", ") : null;
|
|
3694
|
-
}
|
|
3695
|
-
const DOCS_MARKDOWN_RECOVERY_MATCH_LIMIT = 5;
|
|
3696
|
-
const DOCS_MARKDOWN_RECOVERY_REDIRECT_CONFIDENCE = .99;
|
|
3697
|
-
const DOCS_MARKDOWN_RECOVERY_SLUG_MAX_LENGTH = 256;
|
|
3698
|
-
function normalizeDocsRecoveryText(value) {
|
|
3699
|
-
return value.toLowerCase().replace(/\.md$/i, "").replace(/[#?].*$/g, "").replace(/['"`]/g, "").replace(/[^a-z0-9]+/g, " ").trim();
|
|
3700
|
-
}
|
|
3701
|
-
function normalizeDocsRecoverySlug(entry, value) {
|
|
3702
|
-
const withoutHash = value.split("#", 1)[0] ?? value;
|
|
3703
|
-
const withoutMarkdown = (withoutHash.split("?", 1)[0] ?? withoutHash).replace(/\.md$/i, "");
|
|
3704
|
-
const normalizedPath = normalizeDocsUrlPath(withoutMarkdown.startsWith("/") ? withoutMarkdown : `/${withoutMarkdown}`);
|
|
3705
|
-
const normalizedEntry = `/${normalizeDocsPathSegment(entry) || "docs"}`;
|
|
3706
|
-
if (normalizedPath === normalizedEntry) return "";
|
|
3707
|
-
if (normalizedPath.startsWith(`${normalizedEntry}/`)) return normalizeDocsPathSegment(normalizedPath.slice(normalizedEntry.length + 1));
|
|
3708
|
-
return normalizeDocsPathSegment(normalizedPath);
|
|
3709
|
-
}
|
|
3710
|
-
function limitDocsRecoverySlug(value) {
|
|
3711
|
-
return value.slice(0, DOCS_MARKDOWN_RECOVERY_SLUG_MAX_LENGTH);
|
|
3712
|
-
}
|
|
3713
|
-
function tokenizeDocsRecoveryText(value) {
|
|
3714
|
-
return normalizeDocsRecoveryText(value).split(/\s+/).filter((token) => token.length > 1);
|
|
3715
|
-
}
|
|
3716
|
-
function levenshteinDistance(a, b) {
|
|
3717
|
-
if (a === b) return 0;
|
|
3718
|
-
if (!a) return b.length;
|
|
3719
|
-
if (!b) return a.length;
|
|
3720
|
-
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
3721
|
-
let current = new Array(b.length + 1);
|
|
3722
|
-
for (let i = 1; i <= a.length; i += 1) {
|
|
3723
|
-
current[0] = i;
|
|
3724
|
-
for (let j = 1; j <= b.length; j += 1) {
|
|
3725
|
-
const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
3726
|
-
current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, substitution);
|
|
3727
|
-
}
|
|
3728
|
-
[previous, current] = [current, previous];
|
|
3729
|
-
}
|
|
3730
|
-
return previous[b.length] ?? Math.max(a.length, b.length);
|
|
3731
|
-
}
|
|
3732
|
-
function normalizedEditSimilarity(a, b) {
|
|
3733
|
-
if (!a || !b) return 0;
|
|
3734
|
-
if (a === b) return 1;
|
|
3735
|
-
const maxLength = Math.max(a.length, b.length);
|
|
3736
|
-
if (maxLength === 0) return 1;
|
|
3737
|
-
return Math.max(0, 1 - levenshteinDistance(a, b) / maxLength);
|
|
3738
|
-
}
|
|
3739
|
-
function tokenOverlapScore(requestedTokens, candidateTokens) {
|
|
3740
|
-
if (requestedTokens.length === 0 || candidateTokens.length === 0) return 0;
|
|
3741
|
-
const candidates = new Set(candidateTokens);
|
|
3742
|
-
return requestedTokens.filter((token) => candidates.has(token)).length / requestedTokens.length;
|
|
3743
|
-
}
|
|
3744
|
-
function sameDocsRecoveryParent(a, b) {
|
|
3745
|
-
const aParts = a.split("/").filter(Boolean);
|
|
3746
|
-
const bParts = b.split("/").filter(Boolean);
|
|
3747
|
-
if (aParts.length !== bParts.length || aParts.length === 0) return false;
|
|
3748
|
-
return aParts.slice(0, -1).join("/") === bParts.slice(0, -1).join("/");
|
|
3749
|
-
}
|
|
3750
|
-
function scoreDocsMarkdownRecoveryMatch({ entry, requestedPath, page }) {
|
|
3751
|
-
const requestedSlug = limitDocsRecoverySlug(normalizeDocsRecoverySlug(entry, requestedPath));
|
|
3752
|
-
const pageSlug = limitDocsRecoverySlug(normalizeDocsRecoverySlug(entry, page.url));
|
|
3753
|
-
const requestedLast = requestedSlug.split("/").filter(Boolean).at(-1) ?? requestedSlug;
|
|
3754
|
-
const pageLast = pageSlug.split("/").filter(Boolean).at(-1) ?? pageSlug;
|
|
3755
|
-
const title = normalizeDocsRecoveryText(page.title);
|
|
3756
|
-
const requestedText = normalizeDocsRecoveryText(requestedSlug.replace(/\//g, " "));
|
|
3757
|
-
const requestedTokens = tokenizeDocsRecoveryText(requestedText);
|
|
3758
|
-
const candidateTokens = tokenizeDocsRecoveryText([
|
|
3759
|
-
pageSlug.replace(/\//g, " "),
|
|
3760
|
-
page.title,
|
|
3761
|
-
page.description ?? ""
|
|
3762
|
-
].join(" "));
|
|
3763
|
-
const pathDistance = levenshteinDistance(requestedSlug, pageSlug);
|
|
3764
|
-
const segmentDistance = levenshteinDistance(requestedLast, pageLast);
|
|
3765
|
-
if (requestedSlug && pageSlug && (sameDocsRecoveryParent(requestedSlug, pageSlug) && segmentDistance <= 1 || pathDistance <= 1)) return .995;
|
|
3766
|
-
const pathSimilarity = normalizedEditSimilarity(requestedSlug, pageSlug);
|
|
3767
|
-
const segmentSimilarity = normalizedEditSimilarity(requestedLast, pageLast);
|
|
3768
|
-
const titleSimilarity = normalizedEditSimilarity(requestedText, title);
|
|
3769
|
-
const overlap = tokenOverlapScore(requestedTokens, candidateTokens);
|
|
3770
|
-
const substringBoost = requestedText && (pageSlug.includes(requestedSlug) || title.includes(requestedText) || requestedText.includes(title)) ? .12 : 0;
|
|
3771
|
-
return Math.min(.98, Math.max(pathSimilarity * .86, segmentSimilarity * .8, titleSimilarity * .72, overlap * .75) + substringBoost);
|
|
3772
|
-
}
|
|
3773
|
-
function resolveDocsMarkdownRecovery({ entry = "docs", requestedPath, pages = [] }) {
|
|
3774
|
-
const normalizedEntry = normalizeDocsPathSegment(entry) || "docs";
|
|
3775
|
-
const matches = pages.map((page) => ({
|
|
3776
|
-
page,
|
|
3777
|
-
confidence: scoreDocsMarkdownRecoveryMatch({
|
|
3778
|
-
entry: normalizedEntry,
|
|
3779
|
-
requestedPath,
|
|
3780
|
-
page
|
|
3781
|
-
})
|
|
3782
|
-
})).filter((item) => item.confidence >= .35).sort((a, b) => {
|
|
3783
|
-
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
3784
|
-
return a.page.url.localeCompare(b.page.url);
|
|
3785
|
-
}).slice(0, DOCS_MARKDOWN_RECOVERY_MATCH_LIMIT).map(({ page, confidence }) => ({
|
|
3786
|
-
title: page.title,
|
|
3787
|
-
url: normalizeDocsUrlPath(page.url),
|
|
3788
|
-
markdownUrl: toDocsMarkdownUrl(page.url),
|
|
3789
|
-
description: page.description,
|
|
3790
|
-
confidence
|
|
3791
|
-
}));
|
|
3792
|
-
return {
|
|
3793
|
-
matches,
|
|
3794
|
-
redirect: matches[0] && matches[0].confidence >= DOCS_MARKDOWN_RECOVERY_REDIRECT_CONFIDENCE ? matches[0] : void 0
|
|
3795
|
-
};
|
|
3796
|
-
}
|
|
3797
|
-
function renderDocsMarkdownSitemapFooter(sitemap) {
|
|
3798
|
-
const sitemapConfig = resolveDocsSitemapConfig(sitemap);
|
|
3799
|
-
const lines = ["## Sitemap", ""];
|
|
3800
|
-
if (sitemapConfig.enabled && sitemapConfig.markdown.enabled) {
|
|
3801
|
-
lines.push(`See the full [sitemap](${sitemapConfig.markdown.route}) for all pages.`);
|
|
3802
|
-
if (sitemapConfig.markdown.docsRoute) lines.push(`Docs-scoped sitemap: [${sitemapConfig.markdown.docsRoute}](${sitemapConfig.markdown.docsRoute}).`);
|
|
3803
|
-
lines.push(`Well-known sitemap: [${sitemapConfig.markdown.wellKnownRoute}](${sitemapConfig.markdown.wellKnownRoute}).`);
|
|
3804
|
-
} else if (sitemapConfig.enabled && sitemapConfig.xml.enabled) lines.push(`See the XML [sitemap](${sitemapConfig.xml.route}) for all pages.`);
|
|
3805
|
-
else lines.push("Sitemap discovery is not enabled for this deployment.");
|
|
3806
|
-
return lines.join("\n");
|
|
3807
|
-
}
|
|
3808
|
-
function appendDocsMarkdownSitemapFooter(markdown, sitemap) {
|
|
3809
|
-
if (/^##\s+Sitemap\s*$/im.test(markdown)) return markdown;
|
|
3810
|
-
return `${markdown.replace(/\s+$/g, "")}\n\n${renderDocsMarkdownSitemapFooter(sitemap)}\n`;
|
|
3811
|
-
}
|
|
3812
|
-
function normalizeDocsMarkdownLastUpdated(value) {
|
|
3813
|
-
if (!value) return void 0;
|
|
3814
|
-
const trimmed = value.trim();
|
|
3815
|
-
const dateOnly = trimmed.match(/^\d{4}-\d{2}-\d{2}/)?.[0];
|
|
3816
|
-
if (dateOnly) return dateOnly;
|
|
3817
|
-
const date = new Date(trimmed);
|
|
3818
|
-
if (Number.isNaN(date.getTime())) return void 0;
|
|
3819
|
-
return date.toISOString().slice(0, 10);
|
|
3820
|
-
}
|
|
3821
|
-
function resolveDocsMarkdownMetadataUrl(value, origin) {
|
|
3822
|
-
if (!origin) return value;
|
|
3823
|
-
try {
|
|
3824
|
-
return new URL(value, origin).toString();
|
|
3825
|
-
} catch {
|
|
3826
|
-
return value;
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
function renderDocsMarkdownFrontmatter({ title, description, canonicalUrl, markdownUrl, lastUpdated, agent }) {
|
|
3830
|
-
return [
|
|
3831
|
-
"---",
|
|
3832
|
-
`title: ${toYamlString(title)}`,
|
|
3833
|
-
...description ? [`description: ${toYamlString(description)}`] : [],
|
|
3834
|
-
`canonical_url: ${toYamlString(canonicalUrl)}`,
|
|
3835
|
-
`markdown_url: ${toYamlString(markdownUrl)}`,
|
|
3836
|
-
...lastUpdated ? [`last_updated: ${toYamlString(lastUpdated)}`] : [],
|
|
3837
|
-
...renderPageAgentFrontmatterYamlLines(agent),
|
|
3838
|
-
"---"
|
|
3839
|
-
].join("\n");
|
|
3840
|
-
}
|
|
3841
|
-
function hasDocsMarkdownFrontmatter(markdown) {
|
|
3842
|
-
return /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/.test(markdown);
|
|
3843
|
-
}
|
|
3844
|
-
function prependDocsMarkdownFrontmatter(markdown, metadata) {
|
|
3845
|
-
if (hasDocsMarkdownFrontmatter(markdown)) return markdown;
|
|
3846
|
-
return `${renderDocsMarkdownFrontmatter(metadata)}\n\n${markdown.replace(/^\r?\n+/, "")}`;
|
|
3847
|
-
}
|
|
3848
|
-
function resolveDocsMarkdownPageMetadata(page, options) {
|
|
3849
|
-
return {
|
|
3850
|
-
title: page.title,
|
|
3851
|
-
description: page.description,
|
|
3852
|
-
canonicalUrl: resolveDocsMarkdownMetadataUrl(page.url, options?.origin),
|
|
3853
|
-
markdownUrl: resolveDocsMarkdownMetadataUrl(page.markdownUrl ?? toDocsMarkdownUrl(page.url), options?.origin),
|
|
3854
|
-
lastUpdated: normalizeDocsMarkdownLastUpdated(page.lastmod ?? page.lastModified),
|
|
3855
|
-
agent: page.agent
|
|
3856
|
-
};
|
|
3857
|
-
}
|
|
3858
|
-
function renderDocsMarkdownNotFound({ entry = "docs", apiRoute, requestedPath, origin, pages, sitemap }) {
|
|
3859
|
-
const normalizedEntry = normalizeDocsPathSegment(entry) || "docs";
|
|
3860
|
-
const resolvedApiRoute = resolveDocsDiscoveryApiRoute(apiRoute);
|
|
3861
|
-
const agentSpecApiRoute = resolvedApiRoute === DEFAULT_DOCS_API_ROUTE ? DEFAULT_AGENT_SPEC_ROUTE : `${resolvedApiRoute}?agent=spec`;
|
|
3862
|
-
const normalizedRequest = normalizeRequestedMarkdownPath(normalizedEntry, requestedPath);
|
|
3863
|
-
const slugPrefix = `/${normalizedEntry}/`;
|
|
3864
|
-
const requestedSlug = normalizedRequest === `/${normalizedEntry}` ? "" : normalizedRequest.slice(slugPrefix.length);
|
|
3865
|
-
const encodedRequestedSlug = requestedSlug.split("/").map(encodeURIComponent).join("/");
|
|
3866
|
-
const requestedMarkdownRoute = toDocsMarkdownUrl(normalizedRequest);
|
|
3867
|
-
const requestedApiRoute = requestedSlug ? `${resolvedApiRoute}?format=markdown&path=${encodedRequestedSlug}` : `${resolvedApiRoute}?format=markdown`;
|
|
3868
|
-
const sitemapConfig = resolveDocsSitemapConfig(sitemap);
|
|
3869
|
-
const recovery = resolveDocsMarkdownRecovery({
|
|
3870
|
-
entry: normalizedEntry,
|
|
3871
|
-
requestedPath,
|
|
3872
|
-
pages
|
|
3873
|
-
});
|
|
3874
|
-
const lines = [
|
|
3875
|
-
"# Docs Page Not Found",
|
|
3876
|
-
"",
|
|
3877
|
-
`Could not find a markdown page for \`${requestedMarkdownRoute}\`.`
|
|
3878
|
-
];
|
|
3879
|
-
if (recovery.matches.length > 0) {
|
|
3880
|
-
lines.push("", "## Closest Matches", "");
|
|
3881
|
-
for (const match of recovery.matches) {
|
|
3882
|
-
const confidence = `${Math.round(match.confidence * 1e3) / 10}%`;
|
|
3883
|
-
lines.push(`- [${match.title}](${match.markdownUrl}) (${confidence} confidence)${match.description ? ` - ${match.description}` : ""}`);
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
lines.push("", "## Recovery", "", "- If a closest match looks right, fetch that markdown URL directly.", "- If the match is uncertain, search the docs first and then fetch the smallest page that answers the task.", "- Use the sitemap routes below to browse the full docs index before guessing another slug.", "", "## Discovery Routes", "", "Use these discovery routes to find the right page:", "", `- Agent discovery spec: \`${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}\``, `- Agent discovery fallback: \`${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}\``, `- Agent discovery API: \`${agentSpecApiRoute}\``, `- API catalog: \`${DEFAULT_API_CATALOG_ROUTE}\``, `- Agent Skills index: \`${DEFAULT_AGENT_SKILLS_INDEX_ROUTE}\``, `- Agent instructions: \`${DEFAULT_AGENTS_MD_ROUTE}\``, `- Search endpoint: \`${resolvedApiRoute}?query={query}\``, `- Docs index markdown: \`/${normalizedEntry}.md\``, `- Requested markdown API route: \`${requestedApiRoute}\``);
|
|
3887
|
-
if (sitemapConfig.enabled) {
|
|
3888
|
-
if (sitemapConfig.markdown.enabled) {
|
|
3889
|
-
lines.push(`- Semantic sitemap: \`${sitemapConfig.markdown.route}\``);
|
|
3890
|
-
if (sitemapConfig.markdown.docsRoute) lines.push(`- Docs-scoped sitemap alias: \`${sitemapConfig.markdown.docsRoute}\``);
|
|
3891
|
-
lines.push(`- Semantic sitemap well-known alias: \`${sitemapConfig.markdown.wellKnownRoute}\``);
|
|
3892
|
-
}
|
|
3893
|
-
if (sitemapConfig.xml.enabled) lines.push(`- XML sitemap: \`${sitemapConfig.xml.route}\``);
|
|
3894
|
-
} else lines.push(`- Sitemap discovery, if enabled: \`${DEFAULT_SITEMAP_MD_ROUTE}\`, \`${DEFAULT_SITEMAP_MD_DOCS_ROUTE}\`, \`${DEFAULT_SITEMAP_MD_WELL_KNOWN_ROUTE}\`, or \`${DEFAULT_SITEMAP_XML_ROUTE}\``);
|
|
3895
|
-
lines.push("", "The agent discovery spec is the safest first step because it lists the active markdown, sitemap, robots, search, MCP, and feedback routes for this deployment.");
|
|
3896
|
-
return appendDocsMarkdownSitemapFooter(prependDocsMarkdownFrontmatter(lines.join("\n"), {
|
|
3897
|
-
title: "Docs Page Not Found",
|
|
3898
|
-
description: `Could not find a markdown page for ${requestedMarkdownRoute}.`,
|
|
3899
|
-
canonicalUrl: resolveDocsMarkdownMetadataUrl(normalizedRequest, origin),
|
|
3900
|
-
markdownUrl: resolveDocsMarkdownMetadataUrl(requestedMarkdownRoute, origin)
|
|
3901
|
-
}), sitemap);
|
|
3902
|
-
}
|
|
3903
|
-
function hashDocsMarkdownRepresentation(value) {
|
|
3904
|
-
let hash = 2166136261;
|
|
3905
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
3906
|
-
hash ^= value.charCodeAt(index);
|
|
3907
|
-
hash = Math.imul(hash, 16777619);
|
|
3908
|
-
}
|
|
3909
|
-
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
3910
|
-
}
|
|
3911
|
-
function createDocsMarkdownEtag(document) {
|
|
3912
|
-
return `W/"${document.length.toString(16)}-${hashDocsMarkdownRepresentation(document)}"`;
|
|
3913
|
-
}
|
|
3914
|
-
function normalizeDocsMarkdownEtag(value) {
|
|
3915
|
-
return value.trim().replace(/^W\//i, "");
|
|
3916
|
-
}
|
|
3917
|
-
function requestMatchesDocsMarkdownEtag(request, etag) {
|
|
3918
|
-
const header = request.headers.get("if-none-match");
|
|
3919
|
-
if (!header) return false;
|
|
3920
|
-
if (header.trim() === "*") return true;
|
|
3921
|
-
const expected = normalizeDocsMarkdownEtag(etag);
|
|
3922
|
-
return header.split(",").some((candidate) => normalizeDocsMarkdownEtag(candidate) === expected);
|
|
3923
|
-
}
|
|
3924
|
-
function resolveDocsMarkdownHttpDate(value) {
|
|
3925
|
-
if (!value) return void 0;
|
|
3926
|
-
if (typeof value === "string" && !/(?:T|\s)\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?/.test(value.trim())) return;
|
|
3927
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
3928
|
-
if (Number.isNaN(date.getTime())) return void 0;
|
|
3929
|
-
return date.toUTCString();
|
|
3930
|
-
}
|
|
3931
|
-
function extractDocsMarkdownLastModified(document) {
|
|
3932
|
-
const frontmatter = document.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
|
|
3933
|
-
if (!frontmatter) return void 0;
|
|
3934
|
-
const raw = frontmatter.match(/^last_updated:\s*(.+?)\s*$/m)?.[1]?.trim();
|
|
3935
|
-
if (!raw) return void 0;
|
|
3936
|
-
if (raw.startsWith("\"") && raw.endsWith("\"") || raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
|
|
3937
|
-
return raw;
|
|
3938
|
-
}
|
|
3939
|
-
function requestHasFreshDocsMarkdownDate(request, lastModified) {
|
|
3940
|
-
if (!lastModified || request.headers.has("if-none-match")) return false;
|
|
3941
|
-
const ifModifiedSince = request.headers.get("if-modified-since");
|
|
3942
|
-
if (!ifModifiedSince) return false;
|
|
3943
|
-
const resourceTime = Date.parse(lastModified);
|
|
3944
|
-
const requestTime = Date.parse(ifModifiedSince);
|
|
3945
|
-
return Number.isFinite(resourceTime) && Number.isFinite(requestTime) && Math.floor(resourceTime / 1e3) <= Math.floor(requestTime / 1e3);
|
|
3946
|
-
}
|
|
3947
|
-
function resolveDocsMarkdownContentLocation(canonicalUrl) {
|
|
3948
|
-
const url = new URL(canonicalUrl);
|
|
3949
|
-
url.pathname = toDocsMarkdownUrl(url.pathname);
|
|
3950
|
-
return url.toString();
|
|
3951
|
-
}
|
|
3952
|
-
/** Build one standards-aware Markdown response for every framework adapter. */
|
|
3953
|
-
function createDocsMarkdownResponse(options) {
|
|
3954
|
-
const { request, document, entry = "docs", requestedPath, origin = new URL(request.url).origin, pages, sitemap, locale } = options;
|
|
3955
|
-
const canonicalUrl = options.canonicalUrl ?? resolveDocsMarkdownCanonicalUrl({
|
|
3956
|
-
origin,
|
|
3957
|
-
entry,
|
|
3958
|
-
requestedPath,
|
|
3959
|
-
locale
|
|
3960
|
-
});
|
|
3961
|
-
const contentLocation = options.contentLocation ?? resolveDocsMarkdownContentLocation(canonicalUrl);
|
|
3962
|
-
const varyHeader = getDocsMarkdownVaryHeader(request);
|
|
3963
|
-
const sharedHeaders = {
|
|
3964
|
-
"Content-Location": contentLocation,
|
|
3965
|
-
Link: `<${canonicalUrl}>; rel="canonical"`,
|
|
3966
|
-
"X-Robots-Tag": "noindex",
|
|
3967
|
-
...locale ? { "Content-Language": locale } : {},
|
|
3968
|
-
...varyHeader ? { Vary: varyHeader } : {}
|
|
3969
|
-
};
|
|
3970
|
-
if (!document) {
|
|
3971
|
-
const recovery = resolveDocsMarkdownRecovery({
|
|
3972
|
-
entry,
|
|
3973
|
-
requestedPath,
|
|
3974
|
-
pages,
|
|
3975
|
-
sitemap
|
|
3976
|
-
});
|
|
3977
|
-
if (recovery.redirect) return new Response(null, {
|
|
3978
|
-
status: 307,
|
|
3979
|
-
headers: {
|
|
3980
|
-
...sharedHeaders,
|
|
3981
|
-
"Cache-Control": "no-store",
|
|
3982
|
-
Location: new URL(recovery.redirect.markdownUrl, request.url).toString()
|
|
3983
|
-
}
|
|
3984
|
-
});
|
|
3985
|
-
return new Response(renderDocsMarkdownNotFound({
|
|
3986
|
-
entry,
|
|
3987
|
-
apiRoute: options.apiRoute,
|
|
3988
|
-
requestedPath,
|
|
3989
|
-
origin,
|
|
3990
|
-
pages,
|
|
3991
|
-
sitemap
|
|
3992
|
-
}), {
|
|
3993
|
-
status: 404,
|
|
3994
|
-
headers: {
|
|
3995
|
-
...sharedHeaders,
|
|
3996
|
-
"Cache-Control": "no-store",
|
|
3997
|
-
"Content-Type": "text/markdown; charset=utf-8"
|
|
3998
|
-
}
|
|
3999
|
-
});
|
|
4000
|
-
}
|
|
4001
|
-
const etag = createDocsMarkdownEtag(document);
|
|
4002
|
-
const lastModified = resolveDocsMarkdownHttpDate(options.lastModified ?? extractDocsMarkdownLastModified(document));
|
|
4003
|
-
const responseHeaders = {
|
|
4004
|
-
...sharedHeaders,
|
|
4005
|
-
"Cache-Control": options.cacheControl ?? "public, max-age=0, s-maxage=3600",
|
|
4006
|
-
"Content-Type": "text/markdown; charset=utf-8",
|
|
4007
|
-
ETag: etag,
|
|
4008
|
-
...lastModified ? { "Last-Modified": lastModified } : {}
|
|
4009
|
-
};
|
|
4010
|
-
if (requestMatchesDocsMarkdownEtag(request, etag) || requestHasFreshDocsMarkdownDate(request, lastModified)) {
|
|
4011
|
-
const { "Content-Type": _contentType, ...notModifiedHeaders } = responseHeaders;
|
|
4012
|
-
return new Response(null, {
|
|
4013
|
-
status: 304,
|
|
4014
|
-
headers: notModifiedHeaders
|
|
4015
|
-
});
|
|
4016
|
-
}
|
|
4017
|
-
return new Response(document, { headers: responseHeaders });
|
|
4018
|
-
}
|
|
4019
|
-
function findDocsMarkdownPage(entry, pages, requestedPath) {
|
|
4020
|
-
const normalizedRequest = normalizeRequestedMarkdownPath(entry, requestedPath);
|
|
4021
|
-
for (const page of pages) if (normalizeDocsUrlPath(page.url) === normalizedRequest) return page;
|
|
4022
|
-
const normalizedSlug = normalizeDocsPathSegment(requestedPath.replace(/^\//, "").replace(/\.md$/i, ""));
|
|
4023
|
-
for (const page of pages) if (page.slug !== void 0 && normalizeDocsPathSegment(page.slug) === normalizedSlug) return page;
|
|
4024
|
-
return null;
|
|
4025
|
-
}
|
|
4026
|
-
function shouldRenderLlmsDirective(options) {
|
|
4027
|
-
if (options?.llms === false) return false;
|
|
4028
|
-
if (options?.llms && typeof options.llms === "object" && options.llms.enabled === false) return false;
|
|
4029
|
-
return true;
|
|
4030
|
-
}
|
|
4031
|
-
function resolveDocsAgentDocumentContext({ entry = "docs", apiRoute, apiCatalog: explicitApiCatalog, search, mcp, feedback, llms, sitemap, robots, openapi, markdown }) {
|
|
4032
|
-
const feedbackRoute = feedback?.route ?? DEFAULT_AGENT_FEEDBACK_ROUTE;
|
|
4033
|
-
const resolvedApiRoute = resolveDocsDiscoveryApiRoute(apiRoute);
|
|
4034
|
-
const openapiConfig = resolveDocsOpenApiDiscoveryConfig(openapi);
|
|
4035
|
-
return {
|
|
4036
|
-
apiRoute: resolvedApiRoute,
|
|
4037
|
-
normalizedEntry: normalizeDocsPathSegment(entry) || "docs",
|
|
4038
|
-
siteTitle: compactSkillText(llms?.siteTitle ?? "Documentation"),
|
|
4039
|
-
siteDescription: llms?.siteDescription ? compactSkillText(llms.siteDescription) : void 0,
|
|
4040
|
-
llmsEnabled: llms?.enabled ?? true,
|
|
4041
|
-
searchEnabled: isSearchEnabled(search),
|
|
4042
|
-
mcpEnabled: mcp.enabled,
|
|
4043
|
-
feedbackEnabled: feedback?.enabled ?? false,
|
|
4044
|
-
sitemapConfig: resolveDocsSitemapConfig(sitemap),
|
|
4045
|
-
robotsEnabled: isRobotsDiscoveryEnabled(robots),
|
|
4046
|
-
openapiConfig: {
|
|
4047
|
-
...openapiConfig,
|
|
4048
|
-
url: resolveDocsOpenApiDiscoveryUrl(openapiConfig, resolvedApiRoute)
|
|
4049
|
-
},
|
|
4050
|
-
feedbackRoute,
|
|
4051
|
-
feedbackSchemaRoute: feedback?.schemaRoute ?? `${feedbackRoute}/schema`,
|
|
4052
|
-
llmsSections: resolveDocsLlmsTxtSections(llms),
|
|
4053
|
-
markdownAcceptHeader: markdown?.acceptHeader === false ? null : "text/markdown",
|
|
4054
|
-
markdownSignatureAgentHeader: markdown?.signatureAgentHeader === false ? null : DOCS_MARKDOWN_SIGNATURE_AGENT_HEADER,
|
|
4055
|
-
apiCatalogEnabled: explicitApiCatalog ?? llms?.apiCatalog ?? true
|
|
4056
|
-
};
|
|
4057
|
-
}
|
|
4058
|
-
function appendDocsMarkdownNegotiationStartLines(lines, context, variant) {
|
|
4059
|
-
if (context.markdownAcceptHeader) lines.push(variant === "skill" ? `- You can also request ${context.markdownAcceptHeader} from normal page URLs.` : `- Normal docs pages can return markdown with Accept: ${context.markdownAcceptHeader}.`);
|
|
4060
|
-
if (context.markdownSignatureAgentHeader) lines.push(variant === "skill" ? `- Requests with ${context.markdownSignatureAgentHeader} on normal page URLs receive markdown automatically.` : `- Normal docs pages can also return markdown when ${context.markdownSignatureAgentHeader} is present.`);
|
|
4061
|
-
}
|
|
4062
|
-
function appendDocsSearchStartLine(lines, context, variant) {
|
|
4063
|
-
if (!context.searchEnabled) return;
|
|
4064
|
-
lines.push(variant === "skill" ? `- Search with ${context.apiRoute}?query={query} when you do not know the page.` : `- Search with ${context.apiRoute}?query={query} when the route is unknown.`);
|
|
4065
|
-
}
|
|
4066
|
-
function appendDocsOpenApiStartLine(lines, context, variant) {
|
|
4067
|
-
if (!context.openapiConfig.enabled || !context.openapiConfig.url) return;
|
|
4068
|
-
lines.push(variant === "skill" ? `- Fetch ${context.openapiConfig.url} for the machine-readable OpenAPI schema before scraping API reference pages.` : `- Fetch ${context.openapiConfig.url} before scraping API reference pages; prefer schemas over prose.`);
|
|
4069
|
-
}
|
|
4070
|
-
function appendDocsLlmsStartLines(lines, context, variant) {
|
|
4071
|
-
if (!context.llmsEnabled) return;
|
|
4072
|
-
lines.push(variant === "skill" ? `- Use ${DEFAULT_LLMS_TXT_ROUTE} for a compact docs index.` : `- Use ${DEFAULT_LLMS_TXT_ROUTE} as the compact docs map.`, variant === "skill" ? `- Use ${DEFAULT_LLMS_FULL_TXT_ROUTE} for full markdown context.` : `- Use ${DEFAULT_LLMS_FULL_TXT_ROUTE} when you need the full markdown bundle.`);
|
|
4073
|
-
for (const section of context.llmsSections) lines.push(variant === "skill" ? `- Use ${section.route} for the ${section.title} llms.txt section.` : `- Use ${section.route} for the ${section.title} section map.`);
|
|
4074
|
-
}
|
|
4075
|
-
function appendDocsSitemapStartLines(lines, context, variant) {
|
|
4076
|
-
if (!context.sitemapConfig.enabled) return;
|
|
4077
|
-
if (variant === "skill") {
|
|
4078
|
-
if (context.sitemapConfig.xml.enabled) lines.push(`- Use ${context.sitemapConfig.xml.route} to check canonical page freshness.`);
|
|
4079
|
-
if (context.sitemapConfig.markdown.enabled) lines.push(`- Use ${context.sitemapConfig.markdown.route} for a semantic docs map.`);
|
|
4080
|
-
return;
|
|
4081
|
-
}
|
|
4082
|
-
if (context.sitemapConfig.markdown.enabled) lines.push(`- Use ${context.sitemapConfig.markdown.route} for a semantic sitemap with sections.`);
|
|
4083
|
-
if (context.sitemapConfig.xml.enabled) lines.push(`- Use ${context.sitemapConfig.xml.route} for canonical URLs and freshness metadata.`);
|
|
4084
|
-
}
|
|
4085
|
-
function appendDocsRobotsStartLine(lines, context, variant) {
|
|
4086
|
-
if (!context.robotsEnabled) return;
|
|
4087
|
-
lines.push(variant === "skill" ? `- Check ${DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE} for crawler and AI-agent access policy.` : `- Check ${DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE} before crawling broadly.`);
|
|
4088
|
-
}
|
|
4089
|
-
function appendDocsMcpStartLine(lines, context, variant) {
|
|
4090
|
-
if (!context.mcpEnabled) return;
|
|
4091
|
-
lines.push(variant === "skill" ? `- Use ${DEFAULT_MCP_WELL_KNOWN_ROUTE} or ${DEFAULT_MCP_PUBLIC_ROUTE} for MCP tools when your environment supports MCP.` : `- Use MCP at ${DEFAULT_MCP_PUBLIC_ROUTE} or ${DEFAULT_MCP_WELL_KNOWN_ROUTE} when your environment supports MCP tools.`);
|
|
4092
|
-
}
|
|
4093
|
-
function appendDocsFeedbackStartLine(lines, context, variant) {
|
|
4094
|
-
if (!context.feedbackEnabled) return;
|
|
4095
|
-
lines.push(variant === "skill" ? `- Read ${context.feedbackSchemaRoute} before posting agent feedback to ${context.feedbackRoute}.` : `- Read ${context.feedbackSchemaRoute} before posting feedback to ${context.feedbackRoute}.`);
|
|
4096
|
-
}
|
|
4097
|
-
function appendDocsAgentStartHereLines(lines, context, variant) {
|
|
4098
|
-
const agentSpecFallback = context.apiRoute === DEFAULT_DOCS_API_ROUTE ? DEFAULT_AGENT_SPEC_ROUTE : `${context.apiRoute}?agent=spec`;
|
|
4099
|
-
lines.push(variant === "skill" ? `- Fetch ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}; fall back to ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE} or ${agentSpecFallback}.` : `- Read ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE} first; fall back to ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE} or ${agentSpecFallback}.`);
|
|
4100
|
-
if (context.apiCatalogEnabled) lines.push(variant === "skill" ? `- Use ${DEFAULT_API_CATALOG_ROUTE} for standards-based API discovery.` : `- Use ${DEFAULT_API_CATALOG_ROUTE} for RFC 9727 API discovery.`);
|
|
4101
|
-
lines.push(variant === "skill" ? `- Use ${DEFAULT_AGENT_SKILLS_INDEX_ROUTE} for hashed skill discovery.` : `- Use ${DEFAULT_AGENT_SKILLS_INDEX_ROUTE} for integrity-checked skills.`, variant === "skill" ? `- Fetch /${context.normalizedEntry}.md for the root docs page.` : `- Read /${context.normalizedEntry}.md for the root docs page.`, variant === "skill" ? `- Fetch /${context.normalizedEntry}/{slug}.md for page-specific context.` : `- Read /${context.normalizedEntry}/{slug}.md for page-specific context.`);
|
|
4102
|
-
if (variant === "skill") {
|
|
4103
|
-
appendDocsMarkdownNegotiationStartLines(lines, context, variant);
|
|
4104
|
-
appendDocsSearchStartLine(lines, context, variant);
|
|
4105
|
-
appendDocsOpenApiStartLine(lines, context, variant);
|
|
4106
|
-
appendDocsLlmsStartLines(lines, context, variant);
|
|
4107
|
-
appendDocsSitemapStartLines(lines, context, variant);
|
|
4108
|
-
appendDocsRobotsStartLine(lines, context, variant);
|
|
4109
|
-
appendDocsMcpStartLine(lines, context, variant);
|
|
4110
|
-
appendDocsFeedbackStartLine(lines, context, variant);
|
|
4111
|
-
return;
|
|
4112
|
-
}
|
|
4113
|
-
appendDocsLlmsStartLines(lines, context, variant);
|
|
4114
|
-
appendDocsSitemapStartLines(lines, context, variant);
|
|
4115
|
-
appendDocsRobotsStartLine(lines, context, variant);
|
|
4116
|
-
appendDocsSearchStartLine(lines, context, variant);
|
|
4117
|
-
appendDocsOpenApiStartLine(lines, context, variant);
|
|
4118
|
-
appendDocsMcpStartLine(lines, context, variant);
|
|
4119
|
-
appendDocsFeedbackStartLine(lines, context, variant);
|
|
4120
|
-
appendDocsMarkdownNegotiationStartLines(lines, context, variant);
|
|
4121
|
-
}
|
|
4122
|
-
function appendDocsLlmsRouteLines(lines, context) {
|
|
4123
|
-
if (!context.llmsEnabled) return;
|
|
4124
|
-
lines.push(`- llms.txt: ${DEFAULT_LLMS_TXT_ROUTE}`, `- llms-full.txt: ${DEFAULT_LLMS_FULL_TXT_ROUTE}`, `- llms well-known aliases: ${DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE}, ${DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE}`);
|
|
4125
|
-
for (const section of context.llmsSections) {
|
|
4126
|
-
lines.push(`- ${section.title} llms.txt: ${section.route}`);
|
|
4127
|
-
lines.push(`- ${section.title} llms-full.txt: ${section.fullRoute}`);
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
|
-
function appendDocsOpenApiRouteLines(lines, context) {
|
|
4131
|
-
if (!context.openapiConfig.enabled || !context.openapiConfig.url) return;
|
|
4132
|
-
lines.push(`- OpenAPI schema: ${context.openapiConfig.url}`);
|
|
4133
|
-
if (context.openapiConfig.apiReferencePath) lines.push(`- API reference: ${context.openapiConfig.apiReferencePath}`);
|
|
4134
|
-
}
|
|
4135
|
-
function appendDocsSitemapRouteLines(lines, context) {
|
|
4136
|
-
if (!context.sitemapConfig.enabled) return;
|
|
4137
|
-
if (context.sitemapConfig.xml.enabled) lines.push(`- Sitemap XML: ${context.sitemapConfig.xml.route}`);
|
|
4138
|
-
if (context.sitemapConfig.markdown.enabled) lines.push(`- Sitemap Markdown: ${context.sitemapConfig.markdown.route}`, ...context.sitemapConfig.markdown.docsRoute ? [`- Sitemap docs alias: ${context.sitemapConfig.markdown.docsRoute}`] : [], `- Sitemap well-known alias: ${context.sitemapConfig.markdown.wellKnownRoute}`);
|
|
4139
|
-
}
|
|
4140
|
-
function appendDocsMcpRouteLines(lines, context) {
|
|
4141
|
-
if (!context.mcpEnabled) return;
|
|
4142
|
-
lines.push(`- MCP: ${DEFAULT_MCP_PUBLIC_ROUTE}, ${DEFAULT_MCP_WELL_KNOWN_ROUTE}`);
|
|
4143
|
-
}
|
|
4144
|
-
function appendDocsAgentPublicRouteLines(lines, context, variant) {
|
|
4145
|
-
if (variant === "skill") {
|
|
4146
|
-
lines.push(`- Agent instructions: ${DEFAULT_AGENTS_MD_ROUTE}`, `- Agent instructions well-known alias: ${DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE}`, `- Agent instructions API format: ${context.apiRoute}?format=agents`, `- Skill document: ${DEFAULT_SKILL_MD_ROUTE}`, `- Skill well-known alias: ${DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE}`, `- Skill API format: ${context.apiRoute}?format=skill`, `- Agent discovery: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}`, `- Agent discovery fallback: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}`);
|
|
4147
|
-
if (context.apiCatalogEnabled) lines.push(`- API catalog (RFC 9727): ${DEFAULT_API_CATALOG_ROUTE}`);
|
|
4148
|
-
lines.push(`- Agent Skills index: ${DEFAULT_AGENT_SKILLS_INDEX_ROUTE}`, `- Agent Skills artifacts: ${DEFAULT_AGENT_SKILLS_ROUTE_PATTERN}`, `- Markdown root: /${context.normalizedEntry}.md`, `- Markdown pages: /${context.normalizedEntry}/{slug}.md`);
|
|
4149
|
-
if (context.robotsEnabled) lines.push(`- Robots policy: ${DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE}`);
|
|
4150
|
-
appendDocsLlmsRouteLines(lines, context);
|
|
4151
|
-
appendDocsOpenApiRouteLines(lines, context);
|
|
4152
|
-
appendDocsSitemapRouteLines(lines, context);
|
|
4153
|
-
appendDocsMcpRouteLines(lines, context);
|
|
4154
|
-
return;
|
|
4155
|
-
}
|
|
4156
|
-
lines.push(`- Agent instructions: ${DEFAULT_AGENTS_MD_ROUTE}`, `- Agent instructions well-known alias: ${DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE}`, `- Agent instructions API format: ${context.apiRoute}?format=agents`, `- Agent instructions aliases: ${DEFAULT_AGENT_MD_ROUTE}, ${DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE}`, `- Site skill: ${DEFAULT_SKILL_MD_ROUTE}`, `- Site skill well-known alias: ${DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE}`, `- Site skill API format: ${context.apiRoute}?format=skill`, `- Markdown root: /${context.normalizedEntry}.md`, `- Markdown pages: /${context.normalizedEntry}/{slug}.md`, `- Agent discovery: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}`, `- Agent discovery fallback: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}`);
|
|
4157
|
-
if (context.apiCatalogEnabled) lines.push(`- API catalog (RFC 9727): ${DEFAULT_API_CATALOG_ROUTE}`);
|
|
4158
|
-
lines.push(`- Agent Skills index: ${DEFAULT_AGENT_SKILLS_INDEX_ROUTE}`, `- Agent Skills artifacts: ${DEFAULT_AGENT_SKILLS_ROUTE_PATTERN}`);
|
|
4159
|
-
appendDocsLlmsRouteLines(lines, context);
|
|
4160
|
-
if (context.robotsEnabled) lines.push(`- Robots policy: ${DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE}`);
|
|
4161
|
-
appendDocsSitemapRouteLines(lines, context);
|
|
4162
|
-
appendDocsOpenApiRouteLines(lines, context);
|
|
4163
|
-
appendDocsMcpRouteLines(lines, context);
|
|
4164
|
-
}
|
|
4165
|
-
function renderDocsMarkdownDocument(page, options) {
|
|
4166
|
-
const explicitAgentContent = page.agentRawContent ?? page.agentContent;
|
|
4167
|
-
if (explicitAgentContent !== void 0) return appendDocsMarkdownSitemapFooter(prependDocsMarkdownFrontmatter(upsertPageAgentContractMarkdown(explicitAgentContent, page.agent), resolveDocsMarkdownPageMetadata(page, options)), options?.sitemap);
|
|
4168
|
-
const relatedLines = renderDocsRelatedMarkdownLines(page.related);
|
|
4169
|
-
const lines = [`# ${page.title}`, `URL: ${page.url}`];
|
|
4170
|
-
if (shouldRenderLlmsDirective(options)) lines.push(DOCS_LLMS_TXT_DIRECTIVE_LINE);
|
|
4171
|
-
if (page.description) lines.push(`Description: ${page.description}`);
|
|
4172
|
-
lines.push(...relatedLines);
|
|
4173
|
-
lines.push("", upsertPageAgentContractMarkdown(page.agentFallbackRawContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content, page.agent));
|
|
4174
|
-
return appendDocsMarkdownSitemapFooter(prependDocsMarkdownFrontmatter(lines.join("\n"), resolveDocsMarkdownPageMetadata(page, options)), options?.sitemap);
|
|
4175
|
-
}
|
|
4176
|
-
function renderDocsSkillDocument(options) {
|
|
4177
|
-
const { origin } = options;
|
|
4178
|
-
const context = resolveDocsAgentDocumentContext(options);
|
|
4179
|
-
const lines = [
|
|
4180
|
-
"---",
|
|
4181
|
-
"name: docs",
|
|
4182
|
-
`description: ${toYamlString(truncateSkillDescription(`Use ${context.siteTitle} through markdown routes, llms.txt, robots.txt, agent discovery, search, and MCP when available.`))}`,
|
|
4183
|
-
"---",
|
|
4184
|
-
"",
|
|
4185
|
-
`# ${context.siteTitle} Skill`,
|
|
4186
|
-
"",
|
|
4187
|
-
`Base URL: ${origin}`
|
|
4188
|
-
];
|
|
4189
|
-
if (context.siteDescription) lines.push(`Description: ${context.siteDescription}`);
|
|
4190
|
-
lines.push("", "## When To Use", "Use this skill when you need to read or implement against this documentation site.", "", "## Start Here");
|
|
4191
|
-
appendDocsAgentStartHereLines(lines, context, "skill");
|
|
4192
|
-
lines.push("", "## Routes");
|
|
4193
|
-
appendDocsAgentPublicRouteLines(lines, context, "skill");
|
|
4194
|
-
lines.push("", "## Reusable Framework Skills", "For framework setup, CLI, page actions, Ask AI, or configuration work, install the reusable Farming Labs skills:", "", "```sh", "npx skills add farming-labs/docs", "```");
|
|
4195
|
-
return lines.join("\n");
|
|
4196
|
-
}
|
|
4197
|
-
function renderDocsAgentsDocument(options) {
|
|
4198
|
-
const { origin } = options;
|
|
4199
|
-
const context = resolveDocsAgentDocumentContext(options);
|
|
4200
|
-
const lines = [
|
|
4201
|
-
"# Agent Instructions",
|
|
4202
|
-
"",
|
|
4203
|
-
`Site: ${context.siteTitle}`,
|
|
4204
|
-
`Base URL: ${origin}`
|
|
4205
|
-
];
|
|
4206
|
-
if (context.siteDescription) lines.push(`Description: ${context.siteDescription}`);
|
|
4207
|
-
lines.push("", "## Start Here");
|
|
4208
|
-
appendDocsAgentStartHereLines(lines, context, "agents");
|
|
4209
|
-
lines.push("", "## Working Rules", "- Prefer markdown routes, llms.txt, sitemap.md, OpenAPI schemas, and MCP tools over scraping rendered HTML.", "- Treat generated context files as discovery aids, then fetch the smallest page or section that answers the task.", "- Preserve canonical docs URLs when citing pages back to humans.", "- If a markdown route returns a recovery page, use its closest matches, sitemap, and discovery spec before guessing another slug.", "", "## Public Routes");
|
|
4210
|
-
appendDocsAgentPublicRouteLines(lines, context, "agents");
|
|
4211
|
-
lines.push("", "## Framework Maintenance", "- For @farming-labs/docs projects, keep the framework package current before debugging missing agent surfaces.", "", "```sh", "npx @farming-labs/docs@latest upgrade --latest", "```", "", "- For framework setup, configuration, CLI, Ask AI, page actions, or theme work, install the reusable Skills pack:", "", "```sh", "npx skills add farming-labs/docs", "```");
|
|
4212
|
-
return lines.join("\n");
|
|
4213
|
-
}
|
|
4214
|
-
/** Resolve only the task tools that the advertised MCP endpoint actually exposes. */
|
|
4215
|
-
function resolveDocsAgentContractMcpTools(mcp) {
|
|
4216
|
-
if (!mcp.enabled) return void 0;
|
|
4217
|
-
const tools = {};
|
|
4218
|
-
if (mcp.tools.listTasks !== false) tools.list = "list_tasks";
|
|
4219
|
-
if (mcp.tools.readTask !== false) tools.read = "read_task";
|
|
4220
|
-
return tools.list || tools.read ? tools : void 0;
|
|
4221
|
-
}
|
|
4222
|
-
function buildDocsAgentDiscoverySpec({ origin, entry = "docs", apiRoute, apiCatalog: explicitApiCatalog, i18n = null, search, mcp, feedback, llms, sitemap, robots, openapi, markdown }) {
|
|
4223
|
-
const normalizedEntry = normalizeDocsPathSegment(entry) || "docs";
|
|
4224
|
-
const resolvedApiRoute = resolveDocsDiscoveryApiRoute(apiRoute);
|
|
4225
|
-
const apiQueryRoute = (query) => `${resolvedApiRoute}?${query}`;
|
|
4226
|
-
const localesEnabled = i18n !== null;
|
|
4227
|
-
const searchEnabled = isSearchEnabled(search);
|
|
4228
|
-
const feedbackRoute = feedback?.route ?? DEFAULT_AGENT_FEEDBACK_ROUTE;
|
|
4229
|
-
const feedbackSchemaRoute = feedback?.schemaRoute ?? `${feedbackRoute}/schema`;
|
|
4230
|
-
const llmsEnabled = llms?.enabled ?? true;
|
|
4231
|
-
const llmsSections = resolveDocsLlmsTxtSections(llms);
|
|
4232
|
-
const sitemapConfig = resolveDocsSitemapConfig(sitemap, { baseUrl: llms?.baseUrl });
|
|
4233
|
-
const robotsEnabled = isRobotsDiscoveryEnabled(robots);
|
|
4234
|
-
const openapiConfig = resolveDocsOpenApiDiscoveryConfig(openapi);
|
|
4235
|
-
const defaultOpenapiRoute = apiQueryRoute("format=openapi");
|
|
4236
|
-
const openapiUrl = resolveDocsOpenApiDiscoveryUrl(openapiConfig, resolvedApiRoute);
|
|
4237
|
-
const agentContractMcpTools = resolveDocsAgentContractMcpTools(mcp);
|
|
4238
|
-
const apiCatalogEnabled = explicitApiCatalog ?? llms?.apiCatalog ?? true;
|
|
4239
|
-
return {
|
|
4240
|
-
version: "1",
|
|
4241
|
-
name: "@farming-labs/docs",
|
|
4242
|
-
baseUrl: origin,
|
|
4243
|
-
site: {
|
|
4244
|
-
title: llms?.siteTitle ?? "Documentation",
|
|
4245
|
-
description: llms?.siteDescription,
|
|
4246
|
-
entry: normalizedEntry,
|
|
4247
|
-
baseUrl: llms?.baseUrl ?? origin
|
|
4248
|
-
},
|
|
4249
|
-
locales: {
|
|
4250
|
-
enabled: localesEnabled,
|
|
4251
|
-
available: i18n?.locales ?? [],
|
|
4252
|
-
default: i18n?.defaultLocale ?? null,
|
|
4253
|
-
queryParam: "lang",
|
|
4254
|
-
fallbackQueryParam: "locale"
|
|
4255
|
-
},
|
|
4256
|
-
capabilities: {
|
|
4257
|
-
markdownRoutes: true,
|
|
4258
|
-
agentMdOverrides: true,
|
|
4259
|
-
agentBlocks: true,
|
|
4260
|
-
structuredAgentContracts: true,
|
|
4261
|
-
agents: true,
|
|
4262
|
-
llms: llmsEnabled,
|
|
4263
|
-
skills: true,
|
|
4264
|
-
apiCatalog: apiCatalogEnabled,
|
|
4265
|
-
agentSkillsDiscovery: true,
|
|
4266
|
-
mcp: mcp.enabled,
|
|
4267
|
-
search: searchEnabled,
|
|
4268
|
-
sitemap: sitemapConfig.enabled,
|
|
4269
|
-
robots: robotsEnabled,
|
|
4270
|
-
structuredData: true,
|
|
4271
|
-
apiReference: openapiConfig.enabled,
|
|
4272
|
-
openapi: openapiConfig.enabled,
|
|
4273
|
-
agentFeedback: feedback?.enabled ?? false,
|
|
4274
|
-
locales: localesEnabled
|
|
4275
|
-
},
|
|
4276
|
-
api: {
|
|
4277
|
-
docs: resolvedApiRoute,
|
|
4278
|
-
config: apiQueryRoute("format=config"),
|
|
4279
|
-
diagnostics: apiQueryRoute("format=diagnostics"),
|
|
4280
|
-
agentSpec: resolvedApiRoute === DEFAULT_DOCS_API_ROUTE ? DEFAULT_AGENT_SPEC_ROUTE : apiQueryRoute("agent=spec"),
|
|
4281
|
-
agentSpecDefault: DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
|
|
4282
|
-
agentSpecFallback: DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE,
|
|
4283
|
-
agentSpecWellKnown: DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE,
|
|
4284
|
-
agentSpecWellKnownJson: DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
|
|
4285
|
-
agentSpecQuery: apiQueryRoute("agent=spec"),
|
|
4286
|
-
agents: apiQueryRoute("format=agents"),
|
|
4287
|
-
...apiCatalogEnabled ? {
|
|
4288
|
-
apiCatalog: DEFAULT_API_CATALOG_ROUTE,
|
|
4289
|
-
apiCatalogQuery: apiQueryRoute(`format=${DEFAULT_API_CATALOG_FORMAT}`)
|
|
4290
|
-
} : {},
|
|
4291
|
-
agentSkillsIndex: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
4292
|
-
openapi: defaultOpenapiRoute
|
|
4293
|
-
},
|
|
4294
|
-
apiCatalog: {
|
|
4295
|
-
enabled: apiCatalogEnabled,
|
|
4296
|
-
route: apiCatalogEnabled ? DEFAULT_API_CATALOG_ROUTE : null,
|
|
4297
|
-
api: apiCatalogEnabled ? apiQueryRoute(`format=${DEFAULT_API_CATALOG_FORMAT}`) : null,
|
|
4298
|
-
mediaType: API_CATALOG_MEDIA_TYPE,
|
|
4299
|
-
profile: API_CATALOG_PROFILE_URI
|
|
4300
|
-
},
|
|
4301
|
-
config: {
|
|
4302
|
-
format: DEFAULT_DOCS_CONFIG_FORMAT,
|
|
4303
|
-
endpoint: apiQueryRoute("format=config")
|
|
4304
|
-
},
|
|
4305
|
-
markdown: {
|
|
4306
|
-
enabled: true,
|
|
4307
|
-
acceptHeader: markdown?.acceptHeader === false ? null : "text/markdown",
|
|
4308
|
-
signatureAgentHeader: markdown?.signatureAgentHeader === false ? null : DOCS_MARKDOWN_SIGNATURE_AGENT_HEADER,
|
|
4309
|
-
pagePattern: `/${normalizedEntry}/{slug}.md`,
|
|
4310
|
-
rootPage: `/${normalizedEntry}.md`,
|
|
4311
|
-
apiPattern: apiQueryRoute("format=markdown&path={slug}"),
|
|
4312
|
-
resolutionOrder: [
|
|
4313
|
-
"agent.md",
|
|
4314
|
-
"agent audience projection",
|
|
4315
|
-
"shared page markdown"
|
|
4316
|
-
]
|
|
4317
|
-
},
|
|
4318
|
-
agentContract: {
|
|
4319
|
-
enabled: true,
|
|
4320
|
-
schemaVersion: "page-agent-contract.v1",
|
|
4321
|
-
source: "page-frontmatter",
|
|
4322
|
-
frontmatterPath: "agent",
|
|
4323
|
-
markdownSection: "Agent Contract",
|
|
4324
|
-
mcpField: "agent",
|
|
4325
|
-
...agentContractMcpTools ? { mcpTools: agentContractMcpTools } : {},
|
|
4326
|
-
usefulContractFields: ["task", "outcome"],
|
|
4327
|
-
fields: PAGE_AGENT_CONTRACT_FIELD_SCHEMA
|
|
4328
|
-
},
|
|
4329
|
-
llms: {
|
|
4330
|
-
enabled: llmsEnabled,
|
|
4331
|
-
defaultTxt: DEFAULT_LLMS_TXT_ROUTE,
|
|
4332
|
-
defaultFull: DEFAULT_LLMS_FULL_TXT_ROUTE,
|
|
4333
|
-
txt: apiQueryRoute("format=llms"),
|
|
4334
|
-
full: apiQueryRoute("format=llms-full"),
|
|
4335
|
-
publicTxt: DEFAULT_LLMS_TXT_ROUTE,
|
|
4336
|
-
publicFull: DEFAULT_LLMS_FULL_TXT_ROUTE,
|
|
4337
|
-
wellKnownTxt: DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE,
|
|
4338
|
-
wellKnownFull: DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE,
|
|
4339
|
-
...llmsSections.length > 0 ? { sections: llmsSections.map((section) => ({
|
|
4340
|
-
title: section.title,
|
|
4341
|
-
description: section.description,
|
|
4342
|
-
match: section.match,
|
|
4343
|
-
txt: section.route,
|
|
4344
|
-
full: section.fullRoute
|
|
4345
|
-
})) } : {}
|
|
4346
|
-
},
|
|
4347
|
-
sitemap: {
|
|
4348
|
-
enabled: sitemapConfig.enabled,
|
|
4349
|
-
xml: {
|
|
4350
|
-
enabled: sitemapConfig.xml.enabled,
|
|
4351
|
-
route: sitemapConfig.xml.route,
|
|
4352
|
-
api: apiQueryRoute("format=sitemap-xml"),
|
|
4353
|
-
defaultRoute: DEFAULT_SITEMAP_XML_ROUTE
|
|
4354
|
-
},
|
|
4355
|
-
markdown: {
|
|
4356
|
-
enabled: sitemapConfig.markdown.enabled,
|
|
4357
|
-
route: sitemapConfig.markdown.route,
|
|
4358
|
-
docsRoute: sitemapConfig.markdown.docsRoute,
|
|
4359
|
-
wellKnownRoute: sitemapConfig.markdown.wellKnownRoute,
|
|
4360
|
-
api: apiQueryRoute("format=sitemap-md"),
|
|
4361
|
-
defaultRoute: DEFAULT_SITEMAP_MD_ROUTE,
|
|
4362
|
-
defaultDocsRoute: DEFAULT_SITEMAP_MD_DOCS_ROUTE,
|
|
4363
|
-
defaultWellKnownRoute: DEFAULT_SITEMAP_MD_WELL_KNOWN_ROUTE
|
|
4364
|
-
}
|
|
4365
|
-
},
|
|
4366
|
-
robots: {
|
|
4367
|
-
enabled: robotsEnabled,
|
|
4368
|
-
route: DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE,
|
|
4369
|
-
defaultRoute: DEFAULT_AGENT_DISCOVERY_ROBOTS_TXT_ROUTE
|
|
4370
|
-
},
|
|
4371
|
-
structuredData: {
|
|
4372
|
-
enabled: true,
|
|
4373
|
-
format: "application/ld+json",
|
|
4374
|
-
schema: "https://schema.org/TechArticle",
|
|
4375
|
-
fields: [
|
|
4376
|
-
"headline",
|
|
4377
|
-
"description",
|
|
4378
|
-
"url",
|
|
4379
|
-
"dateModified",
|
|
4380
|
-
"breadcrumb",
|
|
4381
|
-
"mainEntity"
|
|
4382
|
-
],
|
|
4383
|
-
canonicalUrlField: "url",
|
|
4384
|
-
breadcrumbType: "BreadcrumbList",
|
|
4385
|
-
agentContractType: "HowTo"
|
|
4386
|
-
},
|
|
4387
|
-
openapi: {
|
|
4388
|
-
enabled: openapiConfig.enabled,
|
|
4389
|
-
url: openapiUrl ?? null,
|
|
4390
|
-
source: openapiConfig.source ?? null,
|
|
4391
|
-
specUrl: openapiConfig.specUrl ?? null,
|
|
4392
|
-
apiReferencePath: openapiConfig.apiReferencePath ?? null,
|
|
4393
|
-
format: "OpenAPI 3.1"
|
|
4394
|
-
},
|
|
4395
|
-
search: {
|
|
4396
|
-
enabled: searchEnabled,
|
|
4397
|
-
endpoint: apiQueryRoute("query={query}"),
|
|
4398
|
-
method: "GET",
|
|
4399
|
-
queryParam: "query",
|
|
4400
|
-
localeParam: "lang"
|
|
4401
|
-
},
|
|
4402
|
-
agents: {
|
|
4403
|
-
enabled: true,
|
|
4404
|
-
file: "AGENTS.md",
|
|
4405
|
-
route: DEFAULT_AGENTS_MD_ROUTE,
|
|
4406
|
-
wellKnown: DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE,
|
|
4407
|
-
api: apiQueryRoute("format=agents"),
|
|
4408
|
-
generatedFallback: true,
|
|
4409
|
-
aliases: [DEFAULT_AGENT_MD_ROUTE, DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE]
|
|
4410
|
-
},
|
|
4411
|
-
skills: {
|
|
4412
|
-
enabled: true,
|
|
4413
|
-
file: "skill.md",
|
|
4414
|
-
route: DEFAULT_SKILL_MD_ROUTE,
|
|
4415
|
-
wellKnown: DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE,
|
|
4416
|
-
api: apiQueryRoute("format=skill"),
|
|
4417
|
-
generatedFallback: true,
|
|
4418
|
-
registry: "skills.sh",
|
|
4419
|
-
install: "npx skills add farming-labs/docs",
|
|
4420
|
-
discovery: {
|
|
4421
|
-
schema: AGENT_SKILLS_DISCOVERY_SCHEMA_URI,
|
|
4422
|
-
index: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
|
|
4423
|
-
artifact: DEFAULT_AGENT_SKILLS_ROUTE_PATTERN,
|
|
4424
|
-
apiIndex: apiQueryRoute(`format=${DEFAULT_AGENT_SKILLS_INDEX_FORMAT}`),
|
|
4425
|
-
apiArtifact: apiQueryRoute("format=agent-skill&name={name}"),
|
|
4426
|
-
digest: "sha256"
|
|
4427
|
-
},
|
|
4428
|
-
recommended: [{
|
|
4429
|
-
name: "getting-started",
|
|
4430
|
-
description: "Use for installation, init, framework setup, theme CSS, and first docs.config wiring."
|
|
4431
|
-
}]
|
|
4432
|
-
},
|
|
4433
|
-
mcp: {
|
|
4434
|
-
enabled: mcp.enabled,
|
|
4435
|
-
endpoint: mcp.route,
|
|
4436
|
-
defaultEndpoint: DEFAULT_MCP_PUBLIC_ROUTE,
|
|
4437
|
-
publicEndpoint: DEFAULT_MCP_PUBLIC_ROUTE,
|
|
4438
|
-
wellKnownEndpoint: DEFAULT_MCP_WELL_KNOWN_ROUTE,
|
|
4439
|
-
publicEndpoints: [DEFAULT_MCP_PUBLIC_ROUTE, DEFAULT_MCP_WELL_KNOWN_ROUTE],
|
|
4440
|
-
canonicalEndpoint: DEFAULT_MCP_ROUTE,
|
|
4441
|
-
name: mcp.name,
|
|
4442
|
-
version: mcp.version,
|
|
4443
|
-
tools: mcp.tools
|
|
4444
|
-
},
|
|
4445
|
-
feedback: {
|
|
4446
|
-
enabled: feedback?.enabled ?? false,
|
|
4447
|
-
schema: feedbackSchemaRoute,
|
|
4448
|
-
submit: feedbackRoute,
|
|
4449
|
-
schemaQuery: apiQueryRoute("feedback=agent&schema=1"),
|
|
4450
|
-
submitQuery: apiQueryRoute("feedback=agent")
|
|
4451
|
-
},
|
|
4452
|
-
instructions: {
|
|
4453
|
-
preferMarkdownRoutes: true,
|
|
4454
|
-
useMcpWhenAvailable: true,
|
|
4455
|
-
useOpenApiWhenAvailable: true,
|
|
4456
|
-
readFeedbackSchemaBeforeSubmitting: true,
|
|
4457
|
-
doNotAssumeFeedbackPayloadShape: true
|
|
4458
|
-
}
|
|
4459
|
-
};
|
|
4460
|
-
}
|
|
4461
|
-
function acceptsDocsMarkdown(request) {
|
|
4462
|
-
const accept = request.headers.get("accept");
|
|
4463
|
-
if (!accept) return false;
|
|
4464
|
-
const ranges = accept.split(",").map((value) => {
|
|
4465
|
-
const [rawMediaType = "", ...params] = value.split(";").map((part) => part.trim().toLowerCase());
|
|
4466
|
-
const qualityParam = params.find((param) => param.split("=", 1)[0]?.trim() === "q");
|
|
4467
|
-
const parsedQuality = qualityParam ? Number.parseFloat(qualityParam.slice(qualityParam.indexOf("=") + 1).trim()) : 1;
|
|
4468
|
-
return {
|
|
4469
|
-
mediaType: rawMediaType,
|
|
4470
|
-
quality: Number.isFinite(parsedQuality) ? Math.min(1, Math.max(0, parsedQuality)) : 0
|
|
4471
|
-
};
|
|
4472
|
-
});
|
|
4473
|
-
const qualityFor = (mediaType) => {
|
|
4474
|
-
const matches = ranges.filter((range) => range.mediaType === mediaType);
|
|
4475
|
-
return matches.length > 0 ? Math.max(...matches.map((range) => range.quality)) : void 0;
|
|
4476
|
-
};
|
|
4477
|
-
const markdownQuality = qualityFor("text/markdown");
|
|
4478
|
-
if (!markdownQuality || markdownQuality <= 0) return false;
|
|
4479
|
-
const htmlQuality = qualityFor("text/html") ?? qualityFor("text/*") ?? qualityFor("*/*");
|
|
4480
|
-
return htmlQuality === void 0 || htmlQuality <= markdownQuality;
|
|
4481
|
-
}
|
|
4482
|
-
function buildDocsUserAgentHeaderPattern(patterns) {
|
|
4483
|
-
return `.*(?:${patterns.map(toCaseInsensitiveHeaderPattern).join("|")}).*`;
|
|
4484
|
-
}
|
|
4485
|
-
function toCaseInsensitiveHeaderPattern(value) {
|
|
4486
|
-
return value.split("").map((char) => {
|
|
4487
|
-
if (/^[a-z]$/i.test(char)) return `[${char.toLowerCase()}${char.toUpperCase()}]`;
|
|
4488
|
-
return escapeRegex(char);
|
|
4489
|
-
}).join("");
|
|
4490
|
-
}
|
|
4491
|
-
function escapeRegex(value) {
|
|
4492
|
-
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
4493
|
-
}
|
|
4494
|
-
function normalizeRequestedMarkdownPath(entry, requestedPath) {
|
|
4495
|
-
const normalizedEntry = `/${normalizeDocsPathSegment(entry) || "docs"}`;
|
|
4496
|
-
const trimmed = requestedPath.trim().replace(/\.md$/i, "");
|
|
4497
|
-
if (!trimmed) return normalizedEntry;
|
|
4498
|
-
const normalized = normalizeDocsUrlPath(trimmed.startsWith("/") ? trimmed : `/${trimmed}`);
|
|
4499
|
-
if (normalized === normalizedEntry || normalized.startsWith(`${normalizedEntry}/`)) return normalized;
|
|
4500
|
-
const slug = normalizeDocsPathSegment(trimmed);
|
|
4501
|
-
return slug ? normalizeDocsUrlPath(`${normalizedEntry}/${slug}`) : normalizedEntry;
|
|
4502
|
-
}
|
|
4503
|
-
function isSearchEnabled(search) {
|
|
4504
|
-
if (search === false) return false;
|
|
4505
|
-
if (search && typeof search === "object" && search.enabled === false) return false;
|
|
4506
|
-
return true;
|
|
4507
|
-
}
|
|
4508
|
-
function isRobotsDiscoveryEnabled(robots) {
|
|
4509
|
-
if (robots === false) return false;
|
|
4510
|
-
if (robots && typeof robots === "object" && robots.enabled === false) return false;
|
|
4511
|
-
return true;
|
|
4512
|
-
}
|
|
4513
|
-
function resolveDocsOpenApiDiscoveryConfig(openapi) {
|
|
4514
|
-
if (openapi === false || openapi === void 0) return { enabled: false };
|
|
4515
|
-
if (openapi === true) return {
|
|
4516
|
-
enabled: true,
|
|
4517
|
-
url: DEFAULT_OPENAPI_SCHEMA_ROUTE,
|
|
4518
|
-
urlSource: "default",
|
|
4519
|
-
source: "generated"
|
|
4520
|
-
};
|
|
4521
|
-
if (openapi.enabled === false) return { enabled: false };
|
|
4522
|
-
return {
|
|
4523
|
-
enabled: true,
|
|
4524
|
-
url: openapi.url ?? DEFAULT_OPENAPI_SCHEMA_ROUTE,
|
|
4525
|
-
urlSource: openapi.urlSource ?? (openapi.url === void 0 ? "default" : "configured"),
|
|
4526
|
-
source: openapi.source ?? "generated",
|
|
4527
|
-
specUrl: openapi.specUrl,
|
|
4528
|
-
apiReferencePath: openapi.apiReferencePath
|
|
4529
|
-
};
|
|
4530
|
-
}
|
|
4531
|
-
function resolveDocsOpenApiDiscoveryUrl(openapi, apiRoute) {
|
|
4532
|
-
return openapi.urlSource === "default" && openapi.url === DEFAULT_OPENAPI_SCHEMA_ROUTE ? `${apiRoute}?format=openapi` : openapi.url;
|
|
4533
|
-
}
|
|
4534
|
-
function compactSkillText(value) {
|
|
4535
|
-
return value.replace(/\s+/g, " ").trim();
|
|
4536
|
-
}
|
|
4537
|
-
function truncateSkillDescription(value) {
|
|
4538
|
-
const normalized = compactSkillText(value);
|
|
4539
|
-
if (normalized.length <= 1024) return normalized;
|
|
4540
|
-
return `${normalized.slice(0, 1021).trimEnd()}...`;
|
|
4541
|
-
}
|
|
4542
|
-
function toYamlString(value) {
|
|
4543
|
-
return JSON.stringify(value);
|
|
4544
|
-
}
|
|
4545
|
-
|
|
4546
|
-
//#endregion
|
|
4547
|
-
export { matchesDocsLlmsTxtSection as $, appendDocsDiscoveryLinkHeader as $t, DOCS_TRADITIONAL_BOT_USER_AGENT_HEADER_PATTERN as A, DEFAULT_SITEMAP_MD_DOCS_ROUTE as At, findDocsMarkdownPage as B, resolveDocsSitemapPageLastmod as Bt, DEFAULT_OPENAPI_SCHEMA_ROUTE as C, validateDocsAgentFeedbackPayload as Ct, DOCS_BOT_LIKE_USER_AGENT_HEADER_PATTERN as D, resolveDocsAudienceExposure as Dt, DOCS_AI_AGENT_USER_AGENT_HEADER_PATTERN as E, resolveDocsAgentMdxContent as Et, buildDocsDiagnostics as F, createDocsSitemapResponse as Ft, isDocsAgentDiscoveryRequest as G, API_CATALOG_PROFILE_URI as Gt, getDocsMarkdownCanonicalLinkHeader as H, toDocsSitemapMarkdownUrl as Ht, buildDocsMcpEndpointCandidates as I, readDocsSitemapManifestFromContentMap as It, isDocsDiagnosticsRequest as J, DEFAULT_AGENT_SKILLS_ROUTE_PATTERN as Jt, isDocsAgentsRequest as K, DEFAULT_AGENT_SKILLS_INDEX_FORMAT as Kt, createDocsMarkdownResponse as L, renderDocsSitemapMarkdown as Lt, buildDocsAgentDiscoverySpec as M, DEFAULT_SITEMAP_MD_WELL_KNOWN_ROUTE as Mt, buildDocsAgentFeedbackSchema as N, DEFAULT_SITEMAP_XML_ROUTE as Nt, DOCS_CONFIG_MAP_TOP_LEVEL_KEYS as O, resolveDocsAudienceMdxContent as Ot, buildDocsConfigMap as P, buildDocsSitemapManifest as Pt, isDocsSkillRequest as Q, DEFAULT_API_CATALOG_ROUTE as Qt, createDocsStandardsDiscoveryResponse as R, renderDocsSitemapXml as Rt, DEFAULT_MCP_WELL_KNOWN_ROUTE as S, upsertPageAgentContractMarkdown as Sn, toDocsMarkdownUrl as St, DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE as T, findDocsAudienceMdxTags as Tt, getDocsMarkdownVaryHeader as U, AGENT_SKILLS_DISCOVERY_SCHEMA_URI as Ut, getDocsLlmsTxtMaxCharsIssue as V, resolveDocsSitemapRequest as Vt, hasDocsMarkdownSignatureAgent as W, API_CATALOG_MEDIA_TYPE as Wt, isDocsMcpRequest as X, DEFAULT_AGENT_SKILL_FORMAT as Xt, isDocsLlmsTxtPublicRequest as Y, DEFAULT_AGENT_SKILLS_ROUTE_PREFIX as Yt, isDocsPublicGetRequest as Z, DEFAULT_API_CATALOG_FORMAT as Zt, DEFAULT_LLMS_TXT_MAX_CHARS as _, hasStructuredPageAgentContract as _n, resolveDocsMarkdownRequest as _t, DEFAULT_AGENT_MD_ROUTE as a, resolveDocsDiscoveryApiRoute as an, renderDocsMarkdownDocument as at, DEFAULT_MCP_PUBLIC_ROUTE as b, renderPageAgentFrontmatterYamlLines as bn, resolveDocsSkillFormat as bt, DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE as c, sha256DocsDiscoveryContent as cn, resolveDocsAgentContractMcpTools as ct, DEFAULT_DOCS_CONFIG_FORMAT as d, PAGE_AGENT_CONTRACT_END_MARKER as dn, resolveDocsAgentsFormat as dt, buildDocsAgentSkillsIndex as en, normalizeDocsPathSegment as et, DEFAULT_DOCS_CONFIG_ROUTE as f, PAGE_AGENT_CONTRACT_FIELDS as fn, resolveDocsLlmsTxtFormat as ft, DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE as g, getPageAgentFrontmatterIssues as gn, resolveDocsMarkdownRecovery as gt, DEFAULT_LLMS_FULL_TXT_ROUTE as h, PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS as hn, resolveDocsMarkdownCanonicalUrl as ht, DEFAULT_AGENT_FEEDBACK_ROUTE as i, isDocsStandardsDiscoveryRequest as in, renderDocsLlmsTxt as it, acceptsDocsMarkdown as j, DEFAULT_SITEMAP_MD_ROUTE as jt, DOCS_MARKDOWN_SIGNATURE_AGENT_HEADER as k, DEFAULT_SITEMAP_MANIFEST_PATH as kt, DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE as l, normalizeDocsRelated as ln, resolveDocsAgentFeedbackConfig as lt, DEFAULT_DOCS_DIAGNOSTICS_ROUTE as m, PAGE_AGENT_CONTRACT_START_MARKER as mn, resolveDocsLlmsTxtSections as mt, DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE as n, createDocsStandardsResponse as nn, parseDocsAgentFeedbackData as nt, DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE as o, resolveDocsPublishedAgentSkill as on, renderDocsMarkdownNotFound as ot, DEFAULT_DOCS_DIAGNOSTICS_FORMAT as p, PAGE_AGENT_CONTRACT_FIELD_SCHEMA as pn, resolveDocsLlmsTxtRequest as pt, isDocsConfigRequest as q, DEFAULT_AGENT_SKILLS_INDEX_ROUTE as qt, DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA as r, getDocsDiscoveryLinkHeader as rn, renderDocsAgentsDocument as rt, DEFAULT_AGENT_SPEC_ROUTE as s, resolveDocsStandardsDiscoveryRequest as sn, renderDocsSkillDocument as st, DEFAULT_AGENTS_MD_ROUTE as t, buildDocsApiCatalog as tn, normalizeDocsUrlPath as tt, DEFAULT_DOCS_API_ROUTE as u, renderDocsRelatedMarkdownLines as un, resolveDocsAgentFeedbackRequest as ut, DEFAULT_LLMS_TXT_ROUTE as v, normalizePageAgentFrontmatter as vn, resolveDocsOpenApiDiscoveryConfig as vt, DEFAULT_SKILL_MD_ROUTE as w, findDocsAudienceMdxIssues as wt, DEFAULT_MCP_ROUTE as x, stripGeneratedPageAgentContractMarkdown as xn, selectDocsLlmsTxtContent as xt, DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE as y, renderPageAgentContractMarkdown as yn, resolveDocsRequestApiRoute as yt, detectDocsMarkdownAgentRequest as z, resolveDocsSitemapConfig as zt };
|