@farming-labs/farmjs 0.2.104 → 0.2.106

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/dist/server.js CHANGED
@@ -4,10 +4,11 @@ import { PassThrough } from "node:stream";
4
4
  import matter from "gray-matter";
5
5
  import { createElement } from "react";
6
6
  import * as ReactDOMServer from "react-dom/server";
7
- import { applySidebarFolderIndexBehavior, buildDocsAskAIContext, buildDocsSearchFacets, buildDocsAgentDiscoverySpec, buildDocsConfigMap, buildDocsDiagnostics, createDocsContentChangeFeed, createDocsContentChangesHttpResponse, createDocsCacheableResponse, createDocsStandardsDiscoveryResponse, createDocsMarkdownResponse, createDocsRobotsResponse, createDocsSitemapResponse, createDocsAgentTraceContext, createDocsAgentTraceId, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, emitDocsTelemetryAgentSurfaceEvent, emitDocsTelemetryProjectEvent, formatDocsAskAIPackageHints, findDocsMarkdownPage, getDocsLlmsTxtMaxCharsIssue, getDocsAgentManifestLinkHeader, getDocsDiscoveryLinkHeader, isDocsAgentDiscoveryRequest, isDocsContentChangesRequest, isDocsAgentsRequest, isDocsConfigRequest, isDocsDiagnosticsRequest, isDocsSkillRequest, normalizeDocsRelated, normalizePageAgentFrontmatter, normalizeDocsOkfTrustMetadataInput, parseDocsAgentFeedbackData, performDocsSearch, performDocsSearchWithMetadata, renderDocsMarkdownDocument, renderDocsLlmsTxt, renderDocsAgentsDocument, renderDocsSkillDocument, readDocsSitemapManifestFromContentMap, stripGeneratedAgentProvenance, resolveDocsAudienceMdxContent, resolveDocsAgentFeedbackConfig, resolveDocsAgentFeedbackRequest, resolvePageSidebarFolderIndexBehavior, resolveAskAISearchRequestConfig, resolveDocsSearchAudience, resolveDocsSearchError, resolveDocsSearchRequest, resolveSearchRequestConfig, resolveDocsI18n, resolveDocsLlmsTxtRequest, resolveDocsLocale, resolveDocsMarkdownRequest, resolveDocsMetadataBaseUrl, resolveDocsContentChangesConfig, resolveDocsRequestApiRoute, resolveDocsRetrievalLastModified, resolveDocsPublishedAgentSkill, resolveDocsStandardsDiscoveryRequest, resolveDocsPath, resolvePageReadingTime, resolveReadingTimeOptions, resolveDocsSitemapPageLastmod, resolveDocsAgentsFormat, resolveDocsSkillFormat, inferDocsTelemetryAgentSurface, renderDocsPageStructuredDataJson, selectDocsLlmsTxtContent, validateDocsAgentFeedbackPayload, } from "@farming-labs/docs";
7
+ import { applySidebarFolderIndexBehavior, buildDocsAskAIContext, buildDocsSearchFacets, buildDocsAgentDiscoverySpec, compactDocsAgentDiscoverySpec, buildDocsConfigMap, buildDocsDiagnostics, createDocsContentChangeFeed, createDocsContentChangesHttpResponse, createDocsCacheableResponse, createDocsStandardsDiscoveryResponse, createDocsMarkdownResponse, createDocsRobotsResponse, createDocsSitemapResponse, createDocsAgentTraceContext, createDocsAgentTraceId, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, emitDocsTelemetryAgentSurfaceEvent, emitDocsTelemetryProjectEvent, formatDocsAskAIPackageHints, findDocsMarkdownPage, getDocsLlmsTxtMaxCharsIssue, getDocsAgentManifestLinkHeader, getDocsDiscoveryLinkHeader, isDocsAgentDiscoveryRequest, isDocsContentChangesRequest, isDocsAgentsRequest, isDocsConfigRequest, isDocsDiagnosticsRequest, isDocsSkillRequest, normalizeDocsRelated, normalizePageAgentFrontmatter, normalizeDocsOkfTrustMetadataInput, parseDocsAgentFeedbackData, performDocsSearch, performDocsSearchWithMetadata, renderDocsMarkdownDocument, renderDocsLlmsTxt, renderDocsAgentsDocument, renderDocsSkillDocument, readDocsSitemapManifestFromContentMap, stripGeneratedAgentProvenance, resolveDocsAudienceMdxContent, resolveDocsAgentFeedbackConfig, resolveDocsAgentFeedbackRequest, resolvePageSidebarFolderIndexBehavior, resolveAskAISearchRequestConfig, resolveDocsSearchAudience, resolveDocsSearchError, resolveDocsSearchRequest, resolveSearchRequestConfig, resolveDocsI18n, resolveDocsLlmsTxtRequest, resolveDocsLocale, resolveDocsMarkdownRequest, resolveDocsMetadataBaseUrl, resolveDocsContentChangesConfig, resolveDocsRequestApiRoute, resolveDocsRetrievalLastModified, resolveDocsPublishedAgentSkill, resolveDocsStandardsDiscoveryRequest, resolveDocsPath, resolvePageReadingTime, resolveReadingTimeOptions, resolveDocsSitemapPageLastmod, resolveDocsAgentsFormat, resolveDocsSkillFormat, inferDocsTelemetryAgentSurface, renderDocsPageStructuredDataJson, selectDocsLlmsTxtContent, validateDocsAgentFeedbackPayload, } from "@farming-labs/docs";
8
8
  import { buildApiReferenceOpenApiDocumentAsync, createDocsMcpHttpHandler, readDocsSitemapManifest, resolveApiReferenceConfig, resolveApiReferenceOpenApiDiscovery, resolveDocsMcpConfig, resolveConfiguredAgentSkills, } from "@farming-labs/docs/server";
9
9
  import { loadDocsNavTree, loadDocsContent, flattenNavTree, normalizeDocsFrontmatterLastmod, readFarmDocsLastModifiedManifest, resolveFarmDocsLastModified, } from "./content.js";
10
10
  import farmDocsBrowserCss from "./browser-css.generated.js";
11
+ import { FARM_DOCS_NAVIGATION_HEADER } from "./runtime.js";
11
12
  export { createFarmjsApiReference } from "./api-reference.js";
12
13
  const FARM_DOCS_BROWSER_CSS_PATH = "/__farm_docs/browser.css";
13
14
  function loadFarmDocsBrowserCss() {
@@ -87,7 +88,8 @@ function navTreeFromNavigation(config, entry) {
87
88
  const configured = config.navigation?.sidebar;
88
89
  if (!Array.isArray(configured) || configured.length === 0)
89
90
  return null;
90
- const defaultOpen = config.theme?.name === "fumadocs-pixel-border";
91
+ const flat = config.sidebar?.flat === true;
92
+ const defaultOpen = flat || config.theme?.name === "fumadocs-pixel-border";
91
93
  function toNode(item) {
92
94
  const children = item.children ?? item.items ?? [];
93
95
  const name = navigationItemName(item);
@@ -98,6 +100,7 @@ function navTreeFromNavigation(config, entry) {
98
100
  type: "folder",
99
101
  name,
100
102
  ...(defaultOpen ? { defaultOpen: true } : {}),
103
+ ...(flat ? { collapsible: false } : {}),
101
104
  ...(item.icon ? { icon: item.icon } : {}),
102
105
  ...(url
103
106
  ? {
@@ -204,27 +207,29 @@ function toSourcePath(contentDir, relPath, rootDir) {
204
207
  return `/${joinPathParts(base, toPosixPath(relPath))}`;
205
208
  }
206
209
  function navTreeFromMap(contentMap, dirPrefix, entry, ordering) {
207
- const dirs = [];
210
+ const pages = [];
208
211
  for (const key of Object.keys(contentMap)) {
209
212
  if (!key.startsWith(dirPrefix))
210
213
  continue;
211
214
  const rel = key.slice(dirPrefix.length);
212
215
  const segments = rel.split("/");
213
216
  const fileName = segments.pop();
214
- const base = fileName.replace(/\.(md|mdx)$/, "");
215
- if (base !== "page" && base !== "index")
217
+ if (fileName === "agent.md" || (!fileName.endsWith(".md") && !fileName.endsWith(".mdx"))) {
216
218
  continue;
219
+ }
220
+ const base = fileName.replace(/\.(md|mdx)$/, "");
221
+ const isIndex = base === "page" || base === "index";
217
222
  const { data } = matter(contentMap[key]);
218
- const dirParts = segments;
219
- const slug = dirParts.join("/");
223
+ const pageParts = isIndex ? segments : [...segments, base];
224
+ const slug = pageParts.join("/");
220
225
  const url = slug ? `/${entry}/${slug}` : `/${entry}`;
221
- const fallbackTitle = dirParts.length > 0
222
- ? dirParts[dirParts.length - 1]
226
+ const fallbackTitle = pageParts.length > 0
227
+ ? pageParts[pageParts.length - 1]
223
228
  .replace(/-/g, " ")
224
229
  .replace(/\b\w/g, (char) => char.toUpperCase())
225
230
  : "Documentation";
226
- dirs.push({
227
- parts: dirParts,
231
+ pages.push({
232
+ parts: pageParts,
228
233
  title: data.title ?? fallbackTitle,
229
234
  url,
230
235
  icon: data.icon,
@@ -232,13 +237,13 @@ function navTreeFromMap(contentMap, dirPrefix, entry, ordering) {
232
237
  order: typeof data.order === "number" ? data.order : Infinity,
233
238
  });
234
239
  }
235
- dirs.sort((a, b) => {
240
+ pages.sort((a, b) => {
236
241
  if (a.parts.length !== b.parts.length)
237
242
  return a.parts.length - b.parts.length;
238
243
  return a.parts.join("/").localeCompare(b.parts.join("/"));
239
244
  });
240
245
  const children = [];
241
- const rootInfo = dirs.find((dir) => dir.parts.length === 0);
246
+ const rootInfo = pages.find((page) => page.parts.length === 0);
242
247
  if (rootInfo) {
243
248
  children.push({
244
249
  type: "page",
@@ -261,14 +266,25 @@ function navTreeFromMap(contentMap, dirPrefix, entry, ordering) {
261
266
  }
262
267
  function buildLevel(parentParts) {
263
268
  const depth = parentParts.length;
264
- const directChildren = dirs.filter((dir) => {
265
- if (dir.parts.length !== depth + 1)
269
+ const directSlugs = Array.from(new Set(pages
270
+ .filter((page) => {
271
+ if (page.parts.length <= depth)
266
272
  return false;
267
- for (let index = 0; index < depth; index++) {
268
- if (dir.parts[index] !== parentParts[index])
269
- return false;
270
- }
271
- return true;
273
+ return parentParts.every((part, index) => page.parts[index] === part);
274
+ })
275
+ .map((page) => page.parts[depth])));
276
+ const directChildren = directSlugs.map((slug) => {
277
+ const parts = [...parentParts, slug];
278
+ const page = pages.find((candidate) => candidate.parts.length === parts.length &&
279
+ candidate.parts.every((part, index) => part === parts[index]));
280
+ return {
281
+ parts,
282
+ title: page?.title ?? slug.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()),
283
+ url: page?.url,
284
+ icon: page?.icon,
285
+ folderIndexBehavior: page?.folderIndexBehavior,
286
+ order: page?.order ?? Infinity,
287
+ };
272
288
  });
273
289
  const slugOrder = findSlugOrder(parentParts);
274
290
  if (slugOrder) {
@@ -284,17 +300,26 @@ function navTreeFromMap(contentMap, dirPrefix, entry, ordering) {
284
300
  ordered.push(child);
285
301
  }
286
302
  return ordered.map((child) => {
287
- const hasGrandChildren = dirs.some((dir) => {
288
- if (dir.parts.length <= child.parts.length)
303
+ const hasGrandChildren = pages.some((page) => {
304
+ if (page.parts.length <= child.parts.length)
289
305
  return false;
290
- return child.parts.every((part, index) => dir.parts[index] === part);
306
+ return child.parts.every((part, index) => page.parts[index] === part);
291
307
  });
292
308
  if (hasGrandChildren) {
293
309
  return {
294
310
  type: "folder",
295
311
  name: child.title,
296
312
  icon: child.icon,
297
- index: { type: "page", name: child.title, url: child.url, icon: child.icon },
313
+ ...(child.url
314
+ ? {
315
+ index: {
316
+ type: "page",
317
+ name: child.title,
318
+ url: child.url,
319
+ icon: child.icon,
320
+ },
321
+ }
322
+ : {}),
298
323
  folderIndexBehavior: child.folderIndexBehavior,
299
324
  children: buildLevel(child.parts),
300
325
  };
@@ -311,17 +336,26 @@ function navTreeFromMap(contentMap, dirPrefix, entry, ordering) {
311
336
  directChildren.sort((a, b) => a.order - b.order);
312
337
  }
313
338
  return directChildren.map((child) => {
314
- const hasGrandChildren = dirs.some((dir) => {
315
- if (dir.parts.length <= child.parts.length)
339
+ const hasGrandChildren = pages.some((page) => {
340
+ if (page.parts.length <= child.parts.length)
316
341
  return false;
317
- return child.parts.every((part, index) => dir.parts[index] === part);
342
+ return child.parts.every((part, index) => page.parts[index] === part);
318
343
  });
319
344
  if (hasGrandChildren) {
320
345
  return {
321
346
  type: "folder",
322
347
  name: child.title,
323
348
  icon: child.icon,
324
- index: { type: "page", name: child.title, url: child.url, icon: child.icon },
349
+ ...(child.url
350
+ ? {
351
+ index: {
352
+ type: "page",
353
+ name: child.title,
354
+ url: child.url,
355
+ icon: child.icon,
356
+ },
357
+ }
358
+ : {}),
325
359
  folderIndexBehavior: child.folderIndexBehavior,
326
360
  children: buildLevel(child.parts),
327
361
  };
@@ -814,6 +848,7 @@ export function createDocsServer(config) {
814
848
  okf: config.agent?.okf,
815
849
  }),
816
850
  lastModified: resolveDocsRetrievalLastModified(page, "agent"),
851
+ access: page.agent?.access,
817
852
  }
818
853
  : null;
819
854
  }
@@ -901,7 +936,7 @@ export function createDocsServer(config) {
901
936
  if (standardsDiscoveryResponse)
902
937
  return standardsDiscoveryResponse;
903
938
  if (isDocsAgentDiscoveryRequest(url, { apiRoute: discoveryApiRoute })) {
904
- const content = `${JSON.stringify(buildDocsAgentDiscoverySpec({
939
+ const fullSpec = buildDocsAgentDiscoverySpec({
905
940
  ...discoveryOptions,
906
941
  publishedSkills: [
907
942
  await resolveDocsPublishedAgentSkill({
@@ -911,13 +946,16 @@ export function createDocsServer(config) {
911
946
  ...(await getPublishedAgentSkills()),
912
947
  ],
913
948
  agentCard: config.agent?.a2a,
914
- }), null, 2)}\n`;
949
+ });
950
+ const compact = url.searchParams.get("profile") === "compact";
951
+ const spec = compact ? compactDocsAgentDiscoverySpec(fullSpec) : fullSpec;
952
+ const content = `${JSON.stringify(spec, null, compact ? undefined : 2)}\n`;
915
953
  return createDocsCacheableResponse({
916
954
  request: event.request,
917
955
  content,
918
956
  headers: {
919
957
  "Content-Type": "application/json; charset=utf-8",
920
- "Cache-Control": "public, max-age=0, s-maxage=3600",
958
+ "Cache-Control": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400",
921
959
  "X-Robots-Tag": "noindex",
922
960
  Link: agentManifestLinkHeader,
923
961
  },
@@ -1056,6 +1094,7 @@ export function createDocsServer(config) {
1056
1094
  origin: markdownOrigin,
1057
1095
  locale: ctx.locale,
1058
1096
  lastModified: representation?.lastModified,
1097
+ access: representation?.access,
1059
1098
  pages: getSearchIndex(ctx),
1060
1099
  sitemap: config.sitemap,
1061
1100
  });
@@ -1899,7 +1938,7 @@ function normalizedPublicRoute(value, fallback) {
1899
1938
  return "/";
1900
1939
  return `/${normalized.replace(/^\/+|\/+$/g, "")}`;
1901
1940
  }
1902
- function isHtmlDocsPageRequest(config, request) {
1941
+ function isDocsPageRequest(config, request) {
1903
1942
  const method = request.method.toUpperCase();
1904
1943
  if (method !== "GET" && method !== "HEAD")
1905
1944
  return false;
@@ -1909,7 +1948,10 @@ function isHtmlDocsPageRequest(config, request) {
1909
1948
  const isPagePath = docsPath === "/"
1910
1949
  ? !pathname.endsWith(".md")
1911
1950
  : pathname === docsPath || pathname.startsWith(`${docsPath}/`);
1912
- if (!isPagePath || pathname.endsWith(".md"))
1951
+ return isPagePath && !pathname.endsWith(".md");
1952
+ }
1953
+ function isHtmlDocsPageRequest(config, request) {
1954
+ if (!isDocsPageRequest(config, request))
1913
1955
  return false;
1914
1956
  const accept = request.headers.get("accept")?.toLowerCase() ?? "";
1915
1957
  return !accept.includes("text/markdown");
@@ -1947,6 +1989,17 @@ function runtimeFontPreloads(fonts) {
1947
1989
  .join(", "),
1948
1990
  };
1949
1991
  }
1992
+ function resolveDefaultTheme(config) {
1993
+ const toggle = config.themeToggle;
1994
+ const value = toggle && typeof toggle === "object" ? toggle.default : undefined;
1995
+ return value === "light" || value === "dark" || value === "system" ? value : "system";
1996
+ }
1997
+ function renderThemeBootstrap(config) {
1998
+ const defaultTheme = resolveDefaultTheme(config);
1999
+ const toggle = config.themeToggle;
2000
+ const allowStoredTheme = toggle !== false && !(toggle && typeof toggle === "object" && toggle.enabled === false);
2001
+ return `<script>(()=>{try{const fallback=${JSON.stringify(defaultTheme)};const stored=${allowStoredTheme ? 'localStorage.getItem("theme")' : "null"};const selected=stored==="light"||stored==="dark"||stored==="system"?stored:fallback;const resolved=selected==="system"?(matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):selected;const root=document.documentElement;root.classList.remove("light","dark");root.classList.add(resolved);root.style.colorScheme=resolved}catch{}})()</script>`;
2002
+ }
1950
2003
  function renderFarmDocsDocument(input) {
1951
2004
  const { config, data } = input;
1952
2005
  const description = data.description ?? config.metadata?.description;
@@ -1955,8 +2008,10 @@ function renderFarmDocsDocument(input) {
1955
2008
  const rootClasses = runtimeFontClasses(input.fonts);
1956
2009
  const rootStyle = runtimeFontStyle(input.fonts);
1957
2010
  const fontPreloads = runtimeFontPreloads(input.fonts).markup;
2011
+ const defaultTheme = resolveDefaultTheme(config);
2012
+ const fallbackTheme = defaultTheme === "dark" ? "dark" : "light";
1958
2013
  return `<!doctype html>
1959
- <html class="dark" lang="${escapeDocumentText(data.locale ?? "en")}" data-docs-theme="${escapeDocumentText(themeName)}" data-farm-docs-runtime="true">
2014
+ <html class="${fallbackTheme}" lang="${escapeDocumentText(data.locale ?? "en")}" data-docs-theme="${escapeDocumentText(themeName)}" data-farm-docs-runtime="true">
1960
2015
  <head>
1961
2016
  <meta charset="utf-8">
1962
2017
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -1964,6 +2019,7 @@ function renderFarmDocsDocument(input) {
1964
2019
  <title>${escapeDocumentText(data.title)}</title>
1965
2020
  ${description ? `<meta name="description" content="${escapeDocumentText(description)}">` : ""}
1966
2021
  ${favicon ? `<link rel="icon" href="${escapeDocumentText(favicon)}">` : ""}
2022
+ ${renderThemeBootstrap(config)}
1967
2023
  ${fontPreloads}
1968
2024
  <link rel="stylesheet" href="${FARM_DOCS_BROWSER_CSS_PATH}">
1969
2025
  ${input.stylesheets
@@ -2001,6 +2057,30 @@ export function createFarmDocsRuntimeHandler(config, options = {}) {
2001
2057
  },
2002
2058
  });
2003
2059
  }
2060
+ if (request.method.toUpperCase() === "GET" &&
2061
+ request.headers.get(FARM_DOCS_NAVIGATION_HEADER) === "1" &&
2062
+ isDocsPageRequest(runtimeConfig, request)) {
2063
+ try {
2064
+ const data = await docsServer.load({ pathname: url.pathname });
2065
+ return Response.json({ data }, {
2066
+ headers: {
2067
+ "Cache-Control": "no-store",
2068
+ Vary: FARM_DOCS_NAVIGATION_HEADER,
2069
+ },
2070
+ });
2071
+ }
2072
+ catch (error) {
2073
+ const status = error &&
2074
+ typeof error === "object" &&
2075
+ typeof error.status === "number"
2076
+ ? error.status
2077
+ : 500;
2078
+ if (status === 404) {
2079
+ return Response.json({ error: "Documentation page not found." }, { status });
2080
+ }
2081
+ throw error;
2082
+ }
2083
+ }
2004
2084
  if (!isHtmlDocsPageRequest(runtimeConfig, request)) {
2005
2085
  return docsServer.handle(request);
2006
2086
  }