@redocly/realm 0.137.0-next.9 → 0.138.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.md +248 -0
  2. package/dist/bin.js +1 -1
  3. package/dist/cli/build/index.js +1 -1
  4. package/dist/cli/eject/themeOperations.js +1 -1
  5. package/dist/cli/generate/generate-tag.js +1 -1
  6. package/dist/cli/generate/generate.js +1 -1
  7. package/dist/client/app/Sidebar/useActions.js +1 -1
  8. package/dist/client/app/Sidebar/useSidebarItems.js +1 -1
  9. package/dist/client/app/hooks/catalog/useCatalog.d.ts +1 -2
  10. package/dist/client/app/hooks/catalog/useCatalogClassic.js +1 -1
  11. package/dist/client/app/hooks/catalog/useCatalogFilter.js +1 -1
  12. package/dist/client/app/l10n/hooks/useL10n.js +1 -1
  13. package/dist/client/app/l10n/index.js +1 -1
  14. package/dist/client/app/search/sse-parser.d.ts +5 -2
  15. package/dist/client/app/search/sse-parser.js +2 -2
  16. package/dist/client/app/search/useFacets.js +1 -1
  17. package/dist/client/app/seo/SeoTags.js +1 -1
  18. package/dist/client/app/utils/loadAndNavigate.js +1 -1
  19. package/dist/client/constants/ai-search.d.ts +8 -0
  20. package/dist/client/constants/ai-search.js +1 -1
  21. package/dist/constants/l10n/langs/ar.js +1 -1
  22. package/dist/constants/l10n/langs/de.js +1 -1
  23. package/dist/constants/l10n/langs/en.js +1 -1
  24. package/dist/constants/l10n/langs/es.js +1 -1
  25. package/dist/constants/l10n/langs/fr.js +1 -1
  26. package/dist/constants/l10n/langs/hi.js +1 -1
  27. package/dist/constants/l10n/langs/it.js +1 -1
  28. package/dist/constants/l10n/langs/ja.js +1 -1
  29. package/dist/constants/l10n/langs/ko.js +1 -1
  30. package/dist/constants/l10n/langs/pl.js +1 -1
  31. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  32. package/dist/constants/l10n/langs/pt.js +1 -1
  33. package/dist/constants/l10n/langs/ru.js +1 -1
  34. package/dist/constants/l10n/langs/uk.js +1 -1
  35. package/dist/constants/l10n/langs/zh.js +1 -1
  36. package/dist/markdoc/helpers/guards/is-example-node.d.ts +2 -1
  37. package/dist/markdoc/helpers/guards/is-example-node.js +1 -1
  38. package/dist/markdoc/nodes/fence/index.js +1 -1
  39. package/dist/server/config/env-config.d.ts +2 -0
  40. package/dist/server/config/env-schema.d.ts +5 -0
  41. package/dist/server/config/env-schemas/feature-flags.d.ts +4 -0
  42. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  43. package/dist/server/constants/entitlements.d.ts +1 -2
  44. package/dist/server/constants/plugins/search.js +1 -1
  45. package/dist/server/esbuild/esbuild-logger.d.ts +2 -2
  46. package/dist/server/esbuild/esbuild-logger.js +3 -3
  47. package/dist/server/esbuild/plugins/styled-components-ssr.js +1 -1
  48. package/dist/server/esbuild/plugins/themes-resolver.js +2 -2
  49. package/dist/server/fs/cache.js +1 -1
  50. package/dist/server/fs/fs.js +1 -1
  51. package/dist/server/fs/utils/remove-l10n-prefix.js +1 -1
  52. package/dist/server/persistence/file-hashes/repositories/file-hashes-repository.d.ts +1 -2
  53. package/dist/server/persistence/file-hashes/services/file-hashes-service.d.ts +1 -2
  54. package/dist/server/plugins/analytics/rudderstack/browser-hooks.js +1 -1
  55. package/dist/server/plugins/analytics/segment/browser-hooks.js +1 -1
  56. package/dist/server/plugins/analytics/segment/build-script.js +3 -3
  57. package/dist/server/plugins/api-docs/loader.d.ts +2 -3
  58. package/dist/server/plugins/api-docs/options.d.ts +1 -1
  59. package/dist/server/plugins/api-docs/options.js +1 -1
  60. package/dist/server/plugins/api-docs/search/create-indexer.d.ts +5 -0
  61. package/dist/server/plugins/api-docs/search/create-indexer.js +1 -0
  62. package/dist/server/plugins/api-docs/search/get-ai-search-documents.d.ts +2 -2
  63. package/dist/server/plugins/api-docs/search/get-ai-search-documents.js +41 -41
  64. package/dist/server/plugins/api-docs/search/search-resolver.js +1 -1
  65. package/dist/server/plugins/api-docs/utils/decorators.js +1 -1
  66. package/dist/server/plugins/api-functions/index.js +1 -1
  67. package/dist/server/plugins/catalog-entities/database/repositories/common/revision-repository.js +1 -1
  68. package/dist/server/plugins/catalog-entities/database/repositories/common/version-repository.js +1 -1
  69. package/dist/server/plugins/catalog-entities/database/repositories/entities/entities-write-repository.js +1 -1
  70. package/dist/server/plugins/catalog-entities/database/repositories/utils/normalize-revision-flags.js +1 -1
  71. package/dist/server/plugins/catalog-entities/types/extractors.d.ts +1 -2
  72. package/dist/server/plugins/catalog-entities/utils/openapi-component-content-extractor.d.ts +1 -2
  73. package/dist/server/plugins/catalog-entities/utils/openapi-schema-resolver.d.ts +1 -2
  74. package/dist/server/plugins/config-parser/format-error.js +4 -4
  75. package/dist/server/plugins/config-parser/loaders/content-slugs-loader.js +1 -1
  76. package/dist/server/plugins/config-parser/loaders/versions-config-loader.js +1 -1
  77. package/dist/server/plugins/dev-onboarding/template/components/EditAppDescriptionDialog.js +1 -1
  78. package/dist/server/plugins/dev-onboarding/template/components/EditAppDialog.js +1 -1
  79. package/dist/server/plugins/dev-onboarding/template/components/EditCallbackUrlDialog.js +1 -1
  80. package/dist/server/plugins/l10n/index.js +1 -1
  81. package/dist/server/plugins/lifecycle.js +2 -2
  82. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.js +1 -1
  83. package/dist/server/plugins/markdown/attribute-resolvers/resolve-svg-content.js +1 -1
  84. package/dist/server/plugins/markdown/compiler.js +1 -1
  85. package/dist/server/plugins/markdown/errors.js +1 -1
  86. package/dist/server/plugins/markdown/is-partial.js +1 -1
  87. package/dist/server/plugins/markdown/markdoc/reparse-example-fences.d.ts +7 -0
  88. package/dist/server/plugins/markdown/markdoc/reparse-example-fences.js +1 -0
  89. package/dist/server/plugins/markdown/markdoc/resolve-raw-partials.js +1 -1
  90. package/dist/server/plugins/markdown/runtime-transform.js +1 -1
  91. package/dist/server/plugins/markdown/search/to-markdown.js +20 -20
  92. package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +5 -4
  93. package/dist/server/plugins/mcp/codemode/capabilities/types.d.ts +5 -2
  94. package/dist/server/plugins/mcp/codemode/capabilities/utils.js +4 -4
  95. package/dist/server/plugins/mcp/codemode/code-execution-audit.js +1 -1
  96. package/dist/server/plugins/mcp/codemode/code-execution-telemetry.d.ts +7 -2
  97. package/dist/server/plugins/mcp/codemode/code-execution-telemetry.js +1 -1
  98. package/dist/server/plugins/mcp/codemode/invoke-tool.js +1 -1
  99. package/dist/server/plugins/mcp/codemode/prompts.d.ts +1 -1
  100. package/dist/server/plugins/mcp/codemode/prompts.js +7 -6
  101. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.d.ts +2 -2
  102. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.js +1 -11
  103. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/console.d.ts +2 -0
  104. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/console.js +7 -0
  105. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.d.ts +1 -0
  106. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.js +2 -2
  107. package/dist/server/plugins/mcp/codemode/sandbox/tool-input-validator.d.ts +5 -0
  108. package/dist/server/plugins/mcp/codemode/sandbox/tool-input-validator.js +1 -0
  109. package/dist/server/plugins/mcp/codemode/sandbox/unknown-tool-hint.d.ts +12 -0
  110. package/dist/server/plugins/mcp/codemode/sandbox/unknown-tool-hint.js +2 -0
  111. package/dist/server/plugins/mcp/codemode/tools/describe-tools.js +3 -1
  112. package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
  113. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.d.ts +2 -2
  114. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -2
  115. package/dist/server/plugins/mcp/codemode/tools/tool-errors.d.ts +4 -0
  116. package/dist/server/plugins/mcp/codemode/tools/tool-errors.js +1 -1
  117. package/dist/server/plugins/mcp/constants.d.ts +1 -0
  118. package/dist/server/plugins/mcp/constants.js +1 -1
  119. package/dist/server/plugins/mcp/docs-mcp/tool-schemas.d.ts +1 -0
  120. package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
  121. package/dist/server/plugins/mcp/docs-mcp/tools/core/create-anonymous-session.js +1 -1
  122. package/dist/server/plugins/mcp/docs-mcp/tools/core/search.js +2 -2
  123. package/dist/server/plugins/mcp/docs-mcp/tools/core/submit-feedback.d.ts +7 -0
  124. package/dist/server/plugins/mcp/docs-mcp/tools/core/submit-feedback.js +3 -0
  125. package/dist/server/plugins/mcp/docs-mcp/tools/core/types.d.ts +8 -2
  126. package/dist/server/plugins/mcp/docs-mcp/tools/graphql/get-graphql-type.js +5 -5
  127. package/dist/server/plugins/mcp/docs-mcp/tools/graphql/types.d.ts +2 -1
  128. package/dist/server/plugins/mcp/docs-mcp/tools/index.d.ts +1 -0
  129. package/dist/server/plugins/mcp/docs-mcp/tools/index.js +1 -1
  130. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoint-info.js +1 -1
  131. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoints.js +1 -1
  132. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-full-api-description.js +1 -1
  133. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-security-schemes.js +1 -1
  134. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/list-apis.js +1 -1
  135. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.d.ts +17 -3
  136. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.js +1 -1
  137. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/types.d.ts +6 -2
  138. package/dist/server/plugins/mcp/docs-mcp/utils/api-name-matching.d.ts +6 -1
  139. package/dist/server/plugins/mcp/docs-mcp/utils/api-name-matching.js +1 -1
  140. package/dist/server/plugins/mcp/docs-mcp/utils/api-titles.d.ts +14 -0
  141. package/dist/server/plugins/mcp/docs-mcp/utils/api-titles.js +1 -0
  142. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +11 -0
  143. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -5
  144. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-guard.js +1 -1
  145. package/dist/server/plugins/mcp/gateway-mcp/fetch/url-placeholders.d.ts +5 -0
  146. package/dist/server/plugins/mcp/gateway-mcp/fetch/url-placeholders.js +1 -0
  147. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  148. package/dist/server/plugins/mcp/index.js +1 -1
  149. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  150. package/dist/server/plugins/mcp/servers/mcp-server.js +3 -3
  151. package/dist/server/plugins/mcp/types.d.ts +1 -0
  152. package/dist/server/plugins/mcp/utils/available-sandbox-tools.js +1 -1
  153. package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
  154. package/dist/server/plugins/nav-utils.js +1 -1
  155. package/dist/server/plugins/search/ai-indexer/prepare-semantic-documents.js +1 -1
  156. package/dist/server/plugins/search/engines/flexsearch/index.js +1 -1
  157. package/dist/server/plugins/search/engines/flexsearch/search-index.d.ts +8 -4
  158. package/dist/server/plugins/search/engines/flexsearch/search-index.js +1 -1
  159. package/dist/server/plugins/search/llmstxt/create-llms-txt-generator.js +1 -1
  160. package/dist/server/plugins/search/llmstxt/index.d.ts +3 -1
  161. package/dist/server/plugins/search/llmstxt/index.js +9 -9
  162. package/dist/server/plugins/search/llmstxt/is-included-in-llms-txt.d.ts +1 -2
  163. package/dist/server/sandbox/constants/host-functions.d.ts +3 -0
  164. package/dist/server/sandbox/constants/host-functions.js +1 -1
  165. package/dist/server/sandbox/constants/sandbox.d.ts +10 -0
  166. package/dist/server/sandbox/constants/sandbox.js +1 -1
  167. package/dist/server/sandbox/sandbox.js +3 -3
  168. package/dist/server/sandbox/types.d.ts +12 -0
  169. package/dist/server/sandbox/utils/execution-budget.js +1 -1
  170. package/dist/server/sandbox/utils/read-result.d.ts +8 -0
  171. package/dist/server/sandbox/utils/read-result.js +1 -0
  172. package/dist/server/sandbox/utils/return-last-expression.d.ts +6 -0
  173. package/dist/server/sandbox/utils/return-last-expression.js +1 -0
  174. package/dist/server/sandbox/utils/returns-from-top-level.d.ts +1 -2
  175. package/dist/server/sandbox/utils/returns-from-top-level.js +1 -1
  176. package/dist/server/sandbox/utils/setup-host-functions.d.ts +1 -0
  177. package/dist/server/sandbox/utils/setup-host-functions.js +3 -3
  178. package/dist/server/ssr/index.js +1 -1
  179. package/dist/server/ssr/render.js +1 -1
  180. package/dist/server/store.d.ts +1 -1
  181. package/dist/server/store.js +1 -1
  182. package/dist/server/tools/notifiers/helpers/privacy/remove-pii.js +1 -1
  183. package/dist/server/types/plugins/common.d.ts +1 -2
  184. package/dist/server/types/plugins/search.d.ts +7 -2
  185. package/dist/server/utils/globs.d.ts +4 -0
  186. package/dist/server/utils/globs.js +1 -1
  187. package/dist/server/utils/rbac.js +1 -1
  188. package/dist/server/utils/search/highlight-text-for-search.js +1 -1
  189. package/dist/server/web-server/middleware/ensureSearchData.js +1 -1
  190. package/dist/server/web-server/routes/catalog/helpers/create-entity-update-schema.d.ts +1 -1
  191. package/dist/server/web-server/routes/catalog/helpers/create-entity-update-schema.js +1 -1
  192. package/dist/server/web-server/routes/dynamic-route.js +1 -1
  193. package/dist/server/web-server/routes/feedback.d.ts +3 -1
  194. package/dist/server/web-server/routes/feedback.js +1 -1
  195. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  196. package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
  197. package/dist/server/web-server/routes/otel/otel.js +1 -1
  198. package/dist/server/web-server/utils/legacy-openapi-redirects.js +1 -1
  199. package/dist/types/global-data.d.ts +1 -2
  200. package/dist/utils/slugger.js +1 -1
  201. package/dist/utils/url/sanitize-redirect-pathname.js +1 -1
  202. package/dist/{server/plugins/catalog-entities/utils/version-compare.d.ts → utils/version/compare-versions.d.ts} +1 -16
  203. package/dist/utils/version/compare-versions.js +1 -0
  204. package/package.json +11 -10
  205. package/dist/server/plugins/api-docs/search/search-indexer.d.ts +0 -9
  206. package/dist/server/plugins/api-docs/search/search-indexer.js +0 -1
  207. package/dist/server/plugins/catalog-entities/utils/version-compare.js +0 -1
@@ -0,0 +1,14 @@
1
+ import type { ApiDescriptionInfo } from '../../types.js';
2
+ /** The form the API lookup compares titles in, so a collision here is a collision there. */
3
+ export declare function apiTitleKey(name: string): string;
4
+ /** How many APIs carry each title, keyed by `apiTitleKey`, so a shared title is detectable. */
5
+ export declare function countApiTitles(apis: ApiDescriptionInfo[]): Map<string, number>;
6
+ /** The highest version, then the shortest path, so the same catalog always yields the same pick. */
7
+ export declare function compareApiDescriptions(a: ApiDescriptionInfo, b: ApiDescriptionInfo): number;
8
+ /**
9
+ * Orders the APIs that share a title the way a name lookup picks, so code that takes the first
10
+ * matching item gets the same definition. Each title keeps the positions it already occupied,
11
+ * so no other API moves.
12
+ */
13
+ export declare function orderSharedTitles(apis: ApiDescriptionInfo[]): ApiDescriptionInfo[];
14
+ //# sourceMappingURL=api-titles.d.ts.map
@@ -0,0 +1 @@
1
+ import c from"semver";import{compareVersions as p}from"../../../../../utils/version/compare-versions.js";function a(e){return e.trim().toLowerCase()}function h(e){const t=new Map;for(const{name:n}of e){const o=a(n);t.set(o,(t.get(o)??0)+1)}return t}function l(e,t){return c.valid(e)&&c.valid(t)?c.rcompare(e,t):p(t,e)}function f(e,t){return l(e.version||"",t.version||"")||e.relativePath.length-t.relativePath.length||e.relativePath.localeCompare(t.relativePath)}function v(e){const t=new Map;e.forEach((o,i)=>{const r=a(o.name),s=t.get(r);s?s.push(i):t.set(r,[i])});const n=[...e];for(const o of t.values()){if(o.length===1)continue;const i=o.map(r=>e[r]).sort(f);o.forEach((r,s)=>{n[r]=i[s]})}return n}export{a as apiTitleKey,f as compareApiDescriptions,h as countApiTitles,v as orderSharedTitles};
@@ -31,6 +31,17 @@ type CredentialElicitationRequest = {
31
31
  hasExistingSecret: boolean;
32
32
  reason: CredentialElicitationReason;
33
33
  };
34
+ /** Concurrent prompts for one subject and scheme share the link the first one mints. */
35
+ export declare function dedupeElicitation(key: string, run: () => Promise<string>): Promise<string>;
36
+ export declare function supportsUrlElicitation(capabilities: {
37
+ elicitation?: {
38
+ url?: unknown;
39
+ };
40
+ } | undefined): boolean;
41
+ /** Only a 2026-07-28-era request carries `_meta` envelope keys, and it needs no session. */
42
+ export declare function isStatelessRequest(mcpReq: {
43
+ envelope?: unknown;
44
+ }): boolean;
34
45
  export declare function isCredentialElicitationDeclined(inputResponses: Record<string, unknown> | undefined): boolean;
35
46
  export declare function schemeLabelOf(schemes: Record<string, unknown>, scheme: string): string | undefined;
36
47
  /**
@@ -1,5 +1,5 @@
1
- import{inputRequired as h,inputResponse as w}from"@modelcontextprotocol/server";import{MANAGE_API_CREDENTIALS_TOOL_NAME as l}from"../constants.js";import{secretStore as d,ELICITATION_TTL_MS as y}from"./secret-store.js";import{operationsForScheme as I,orderedSchemesOf as A}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as u}from"./subject.js";const m="credentials";function R(e){const t=w(e,m);return t.kind==="elicit"&&(t.action==="decline"||t.action==="cancel")}function x(e,t){return Object.keys(e).length>1?t:void 0}async function F(e){const{url:t}=await f(e);return h({inputRequests:{[m]:h.elicitUrl({message:b(e),url:t})}})}async function L(e){const{url:t}=await f(e);return C(e,t,P(e.subject))}function U(e,t){const r=p(e);if(e.status===401&&!e.sentScheme){const[a,o]=e.callerHeader?[`Your code set the "${e.callerHeader}" header itself, so the stored credential was not sent.`,"Remove that header from the request and retry. Do not replace the stored credentials."]:["No stored credential was sent with this request, so this result says nothing about the stored credentials.","Do not replace them. Check whether this operation needs a different kind of credential or a different route."];return[`The "${e.name}" API returned ${r}. ${a}`,"",o].join(`
2
- `)}const n=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${r}).`:`The "${e.name}" API rejected the request with ${r}.`,i=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[n,"",`Do not retry the same call. Call \`${l}\` with ${i} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
3
- `)}function p({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}function H(e,t){const r=t.map(({name:n,label:i,description:a})=>`- ${n} \u2014 ${i}${a?`: ${a}`:""}`);return[`The "${e.name}" API rejected the request with ${p(e)} and this operation accepts more than one kind of credential:`,"",...r,"","Ask the user which one of these they have, and wait for the answer. Never guess and never reuse a value meant for another kind.",`Then call \`${l}\` with api: "${e.api}" and scheme set to the name they picked to get a link where they enter it.`].join(`
4
- `)}async function f({kv:e,subject:t,baseUrl:r,target:n,auth:i,apiAuth:a,hasExistingSecret:o}){const s=a?await k(e,t,n.api,a):[],c=await d.createElicitation(e,{subject:t,api:n.api,name:n.name,scheme:n.scheme,...n.schemeLabel?{schemeLabel:n.schemeLabel}:{},auth:i,hasExistingSecret:o,...s.length>1?{schemes:s}:{}});return{token:c,url:E(r,c)}}async function k(e,t,r,n){const a=(await d.getSecretsForSubject(e,t))[r]??{};return A(n.schemes).map(o=>{const s=I(n,o),c=n.descriptions?.[o];return{name:o,...c?{label:c}:{},auth:n.schemes[o],hasExistingSecret:!!a[o],...s.length>0?{operations:s}:{}}})}function P(e){return e.startsWith(u)?e.slice(u.length):void 0}function b({target:e,hasExistingSecret:t,reason:r}){const n=`The link works for ${$()} minutes.`,i=S(e),a=r.kind==="authFailure"&&r.detail?` The API answered: ${r.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${i}. They go straight to the API and never enter the chat. ${n}`:`${g(i)} needs credentials.${a} Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function S({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function g(e){return e.charAt(0).toUpperCase()+e.slice(1)}function $(){return Math.round(y/6e4)}function E(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const r=new URL("/mcp/credentials",e);return r.searchParams.set("token",t),r.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function C({target:e,reason:t,hasExistingSecret:r},n,i){const o=[`**Stop and show the user this link. They have to open it in a browser to ${r?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",n,"",`The link is valid ${$()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:r?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],i?`Once the user confirms they saved them, call this tool again with sessionHandle: "${i}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],s=t.kind==="authFailure"?t.otherApiNames??[]:[];if(s.length>0){const c=s.map(T=>`"${T}"`).join(", ");o.push("",`The remaining APIs (${c}) will prompt for credentials on subsequent calls.`)}return o.join(`
5
- `)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,U as buildCredentialsToolHint,H as buildSchemeChoiceHint,L as buildUpstreamCredentialFallback,F as buildUpstreamCredentialInputRequired,R as isCredentialElicitationDeclined,x as schemeLabelOf};
1
+ import{inputRequired as l,inputResponse as I}from"@modelcontextprotocol/server";import{MANAGE_API_CREDENTIALS_TOOL_NAME as d}from"../constants.js";import{secretStore as u,ELICITATION_TTL_MS as y}from"./secret-store.js";import{operationsForScheme as A,orderedSchemesOf as P}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as p}from"./subject.js";const m="credentials",h=new Map;function j(e,t){const r=h.get(e);if(r)return r;const n=t().finally(()=>{h.delete(e)});return h.set(e,n),n}function U(e){return!!e?.elicitation?.url}function F(e){return e.envelope!==void 0}function L(e){const t=I(e,m);return t.kind==="elicit"&&(t.action==="decline"||t.action==="cancel")}function M(e,t){return Object.keys(e).length>1?t:void 0}async function H(e){const{url:t}=await $(e);return l({inputRequests:{[m]:l.elicitUrl({message:k(e),url:t})}})}async function q(e){const{url:t}=await $(e);return v(e,t,g(e.subject))}function D(e,t){const r=f(e);if(e.status===401&&!e.sentScheme){const[o,s]=e.callerHeader?[`Your code set the "${e.callerHeader}" header itself, so the stored credential was not sent.`,"Remove that header from the request and retry. Do not replace the stored credentials."]:["No stored credential was sent with this request, so this result says nothing about the stored credentials.","Do not replace them. Check whether this operation needs a different kind of credential or a different route."];return[`The "${e.name}" API returned ${r}. ${o}`,"",s].join(`
2
+ `)}const n=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${r}).`:`The "${e.name}" API rejected the request with ${r}.`,i=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[n,"",`Do not retry the same call. Call \`${d}\` with ${i} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
3
+ `)}function f({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}function B(e,t){const r=t.map(({name:n,label:i,description:o})=>`- ${n} \u2014 ${i}${o?`: ${o}`:""}`);return[`The "${e.name}" API rejected the request with ${f(e)} and this operation accepts more than one kind of credential:`,"",...r,"","Ask the user which one of these they have, and wait for the answer. Never guess and never reuse a value meant for another kind.",`Then call \`${d}\` with api: "${e.api}" and scheme set to the name they picked to get a link where they enter it.`].join(`
4
+ `)}async function $({kv:e,subject:t,baseUrl:r,target:n,auth:i,apiAuth:o,hasExistingSecret:s}){const a=o?await b(e,t,n.api,o):[],c=await u.createElicitation(e,{subject:t,api:n.api,name:n.name,scheme:n.scheme,...n.schemeLabel?{schemeLabel:n.schemeLabel}:{},auth:i,hasExistingSecret:s,...a.length>1?{schemes:a}:{}});return{token:c,url:C(r,c)}}async function b(e,t,r,n){const o=(await u.getSecretsForSubject(e,t))[r]??{};return P(n.schemes).map(s=>{const a=A(n,s),c=n.descriptions?.[s];return{name:s,...c?{label:c}:{},auth:n.schemes[s],hasExistingSecret:!!o[s],...a.length>0?{operations:a}:{}}})}function g(e){return e.startsWith(p)?e.slice(p.length):void 0}function k({target:e,hasExistingSecret:t,reason:r}){const n=`The link works for ${T()} minutes.`,i=E(e),o=r.kind==="authFailure"&&r.detail?` The API answered: ${r.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${i}. They go straight to the API and never enter the chat. ${n}`:`${S(i)} needs credentials.${o} Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function E({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function S(e){return e.charAt(0).toUpperCase()+e.slice(1)}function T(){return Math.round(y/6e4)}function C(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const r=new URL("/mcp/credentials",e);return r.searchParams.set("token",t),r.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function v({target:e,reason:t,hasExistingSecret:r},n,i){const s=[`**Stop and show the user this link. They have to open it in a browser to ${r?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",n,"",`The link is valid ${T()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:r?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],i?`Once the user confirms they saved them, call this tool again with sessionHandle: "${i}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],a=t.kind==="authFailure"?t.otherApiNames??[]:[];if(a.length>0){const c=a.map(w=>`"${w}"`).join(", ");s.push("",`The remaining APIs (${c}) will prompt for credentials on subsequent calls.`)}return s.join(`
5
+ `)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,D as buildCredentialsToolHint,B as buildSchemeChoiceHint,q as buildUpstreamCredentialFallback,H as buildUpstreamCredentialInputRequired,j as dedupeElicitation,L as isCredentialElicitationDeclined,F as isStatelessRequest,M as schemeLabelOf,U as supportsUrlElicitation};
@@ -1 +1 @@
1
- import{isIP as l}from"node:net";import{isHostAllowed as i}from"./allowed-hosts.js";import{isBlockedIp as a}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as c,GatewayFetchUserError as s}from"./fetch-errors.js";function m(o,r){let t;try{t=new URL(o)}catch{throw new s(`Invalid URL: ${o}`)}if(t.protocol!=="http:"&&t.protocol!=="https:")throw new s(`Only http(s) URLs are allowed, got ${t.protocol}`);if(t.username||t.password)throw new s("URLs must not contain embedded credentials");const e=n(t.hostname);if(d(e)||!i(e,r))throw new c(`Host "${e}" is not allowed`,e);return t}function d(o){const r=n(o.toLowerCase()).split("%")[0];return r==="localhost"||r.endsWith(".localhost")?!0:l(r)!==0?a(r):!1}function n(o){return o.replace(/^\[|\]$/g,"")}export{m as assertUrlAllowed,d as isBlockedAddress};
1
+ import{isIP as d}from"node:net";import{isHostAllowed as h}from"./allowed-hosts.js";import{isBlockedIp as w}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as p,GatewayFetchUserError as r}from"./fetch-errors.js";import{findUnresolvedPlaceholders as a,unresolvedPlaceholderMessage as i}from"./url-placeholders.js";function v(o,t){let e;try{e=new URL(o)}catch{throw new r(`Invalid URL: ${o}`)}if(e.protocol!=="http:"&&e.protocol!=="https:")throw new r(`Only http(s) URLs are allowed, got ${e.protocol}`);if(e.username||e.password)throw new r("URLs must not contain embedded credentials");const n=a(e.hostname);if(n.length>0)throw new r(i(n));const s=c(e.hostname);if(f(s)||!h(s,t))throw new p(`Host "${s}" is not allowed`,s);const l=a(e.pathname);if(l.length>0)throw new r(i(l));return e}function f(o){const t=c(o.toLowerCase()).split("%")[0];return t==="localhost"||t.endsWith(".localhost")?!0:d(t)!==0?w(t):!1}function c(o){return o.replace(/^\[|\]$/g,"")}export{v as assertUrlAllowed,f as isBlockedAddress};
@@ -0,0 +1,5 @@
1
+ /** Collect the placeholder names one part of a URL still carries. */
2
+ export declare function findUnresolvedPlaceholders(urlPart: string): string[];
3
+ /** The refusal a caller reads when it left a placeholder in the URL. */
4
+ export declare function unresolvedPlaceholderMessage(names: readonly string[]): string;
5
+ //# sourceMappingURL=url-placeholders.d.ts.map
@@ -0,0 +1 @@
1
+ const o=/\{([^}/]+)\}/g,t=/^[A-Za-z_][A-Za-z0-9_]*$/;function n(e){const a=e.replace(/%7B/gi,"{").replace(/%7D/gi,"}"),l=new Set;for(const[,r]of a.matchAll(o))t.test(r)&&l.add(r);return[...l]}function s(e){const a=e.length>1?"placeholders":"placeholder",l=e.map(r=>`"${r}"`).join(", ");return`Unresolved ${a} ${l} in the URL. This is not a credentials problem. Replace every placeholder with a real value. Use the server variable default, or ask the user.`}export{n as findUnresolvedPlaceholders,s as unresolvedPlaceholderMessage};
@@ -1 +1 @@
1
- import{logger as u}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as N}from"../../../web-server/utils/feedback-ip.js";import{getForwardedOrigin as g,getInternalOrigin as F}from"../../../web-server/utils/get-request-origin.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as v}from"../../../../constants/common.js";import{isObject as k}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as B}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as L}from"../../../utils/skills/get-skill-url.js";import{createMcpHandler as H}from"@modelcontextprotocol/server";import{createDocsMcpServer as V}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as $,filterGraphqlDescriptionsByRbac as j}from"../utils.js";import{McpClientInputError as G,createMethodNotAllowedError as J,withErrorHandling as K}from"./errors.js";import{constructForbiddenResponse as Y,constructInvalidTokenResponse as z,constructUnauthorizedResponse as W,handleMcpAuth as X}from"../auth/auth-handlers.js";import{shouldHandleMcpAuth as Q}from"../../../utils/rbac/should-handle-mcp-auth.js";import{canAnonymousAccessMcpFeature as Z}from"../../../utils/rbac/can-anonymous-access-mcp-feature.js";import{extractTokenFromAuthHeader as q}from"../utils/jwt.js";import{resolveMcpClient as x}from"../utils/mcp-client.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as ee,SSE_KEEPALIVE_INTERVAL_MS as re}from"../constants.js";import{withPathPrefix as oe}from"@redocly/theme/core/utils";import{PUBLIC_MCP_ROUTE as se}from"@redocly/theme/core/constants/mcp";import{canAccessFeature as ne}from"../../../utils/rbac.js";import{ANONYMOUS_AUTH_DETAILS as ie}from"../../../constants/rbac.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as te,RbacFeatures as ce}from"../../../../constants/common.js";const h="X-Redocly-AI-Metadata";function ae(r){if(r)try{const e=JSON.parse(r);if(k(e))return e;u.warn(`Ignoring ${h} header: not a JSON object.`)}catch{u.warn(`Ignoring ${h} header: invalid JSON.`)}}const pe=async(r,e,t)=>{const n=!!e?.config?.access?.requiresLogin,i=e?.config?.access?.rbac||{},A=t?.props?.config?.mcpMode??"tools",T=t?.props?.config?.gatewayFetchBudgetPerExec??v.budgetPerExec,S=t?.props?.config?.searchIgnoredRoutes??[],s=t,b=!!s?.props?.config?.isPublicEndpoint;let p;const E=r.headers.get("Authorization");if(b)e.user={...ie,email:"",idpAccessToken:void 0,idpId:void 0};else if(Q(n,i)){const{isAuthenticated:d,isTokenValid:l,currentUser:a,accessToken:m}=await X(r,e);if(!d){const c=g(r),f=e.config.mcp?.docs?.publicEndpoint&&Z(i,n)?`${c}${oe(se)}`:void 0;return l===!1?z(c):W(c,f)}a&&(e.user=a),p=m}else E&&(p=q(E||"")??void 0);const I={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[te],claims:e.user?.claims??{}};return ne(ce.MCP,I,i,n)?await K(async()=>{const d=t?.props?.config?.elicitationEnabled??!1;if(r.method==="GET")return new Response(JSON.stringify({error:"Method Not Allowed",message:`In order to use this MCP server, you need to register it in your MCP client (VS Code, Cursor, Claude Code, etc.) using this URL: ${r.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(r.method!=="POST")return J();const l=s?.props?.config?.apiDescriptionsMap||{},a=s?.props?.config?.graphqlDescriptions||[],m=s?.props?.config?.mcpDocsServerName||ee,{config:{mcp:c={}}}=e,f=$(l,e.user,i,n),y=j(a,e.user,i,n),U=e.config.products?Object.values(e.config.products).map(o=>o?.name):[],w=c.docs?.name||m,C=ae(r.headers.get(h)),R=s?.props?.tools||[],_=s?.props?.skillsBaseUrl||g(r),D=B(s?.props?.skills||[],e.user,i,n).map(o=>({slug:o.slug,name:o.name,description:o.description,content:o.content,uri:L(_,o.slug)})),M=await de(r),O={name:w,tools:R,skills:D,context:{...e,accessToken:p,outdir:e.outdir||"",baseUrl:e.baseUrl||g(r),internalBaseUrl:F(r),apiDescriptionsMap:f,graphqlDescriptions:y,products:U,metadata:C,mcpMode:A,elicitationEnabled:d,gatewayFetchBudgetPerExec:T,searchIgnoredRoutes:S,clientIp:N(r),...x(r)}},P=await V({...O,getKv:e.getKv});return await H(async()=>P,{keepAliveMs:re,onerror:o=>u.warn(`MCP handler: ${o.message}`)}).fetch(r,M!==void 0?{parsedBody:M}:void 0)},A):Y()};async function de(r){try{return await r.json()}catch(e){throw new G(e instanceof Error?e.message:String(e),{cause:e})}}var Fe=pe;export{Fe as default};
1
+ import{logger as f}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as N}from"../../../web-server/utils/feedback-ip.js";import{getForwardedOrigin as g,getInternalOrigin as F}from"../../../web-server/utils/get-request-origin.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as v,DEFAULT_ANONYMOUS_VISITOR_TEAM as k,RbacFeatures as B}from"../../../../constants/common.js";import{isObject as L}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as H}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as V}from"../../../utils/skills/get-skill-url.js";import{createMcpHandler as $}from"@modelcontextprotocol/server";import{createDocsMcpServer as j}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as G,filterGraphqlDescriptionsByRbac as J}from"../utils.js";import{McpClientInputError as K,createMethodNotAllowedError as Y,withErrorHandling as z}from"./errors.js";import{constructForbiddenResponse as W,constructInvalidTokenResponse as X,constructUnauthorizedResponse as Q,handleMcpAuth as Z}from"../auth/auth-handlers.js";import{shouldHandleMcpAuth as q}from"../../../utils/rbac/should-handle-mcp-auth.js";import{canAnonymousAccessMcpFeature as x}from"../../../utils/rbac/can-anonymous-access-mcp-feature.js";import{extractTokenFromAuthHeader as ee}from"../utils/jwt.js";import{resolveMcpClient as re}from"../utils/mcp-client.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as oe,SSE_KEEPALIVE_INTERVAL_MS as se}from"../constants.js";import{withPathPrefix as ne}from"@redocly/theme/core/utils";import{PUBLIC_MCP_ROUTE as ie}from"@redocly/theme/core/constants/mcp";import{canAccessFeature as te}from"../../../utils/rbac.js";import{ANONYMOUS_AUTH_DETAILS as ce}from"../../../constants/rbac.js";const h="X-Redocly-AI-Metadata";function ae(r){if(r)try{const e=JSON.parse(r);if(L(e))return e;f.warn(`Ignoring ${h} header: not a JSON object.`)}catch{f.warn(`Ignoring ${h} header: invalid JSON.`)}}const pe=async(r,e,t)=>{const n=!!e?.config?.access?.requiresLogin,i=e?.config?.access?.rbac||{},A=t?.props?.config?.mcpMode??"tools",T=t?.props?.config?.gatewayFetchBudgetPerExec??v.budgetPerExec,S=t?.props?.config?.searchIgnoredRoutes??[],s=t,b=!!s?.props?.config?.isPublicEndpoint;let p;const E=r.headers.get("Authorization");if(b)e.user={...ce,email:"",idpAccessToken:void 0,idpId:void 0};else if(q(n,i)){const{isAuthenticated:d,isTokenValid:l,currentUser:a,accessToken:m}=await Z(r,e);if(!d){const c=g(r),u=e.config.mcp?.docs?.publicEndpoint&&x(i,n)?`${c}${ne(ie)}`:void 0;return l===!1?X(c):Q(c,u)}a&&(e.user=a),p=m}else E&&(p=ee(E||"")??void 0);const I={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[k],claims:e.user?.claims??{}};return te(B.MCP,I,i,n)?await z(async()=>{const d=t?.props?.config?.elicitationEnabled??!1;if(r.method==="GET")return new Response(JSON.stringify({error:"Method Not Allowed",message:`In order to use this MCP server, you need to register it in your MCP client (VS Code, Cursor, Claude Code, etc.) using this URL: ${r.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(r.method!=="POST")return Y();const l=s?.props?.config?.apiDescriptionsMap||{},a=s?.props?.config?.graphqlDescriptions||[],m=s?.props?.config?.mcpDocsServerName||oe,{config:{mcp:c={}}}=e,u=G(l,e.user,i,n),y=J(a,e.user,i,n),U=e.config.products?Object.values(e.config.products).map(o=>o?.name):[],w=c.docs?.name||m,C=ae(r.headers.get(h)),R=s?.props?.tools||[],_=s?.props?.skillsBaseUrl||g(r),D=H(s?.props?.skills||[],e.user,i,n).map(o=>({slug:o.slug,name:o.name,description:o.description,content:o.content,uri:V(_,o.slug)})),M=await de(r),O={name:w,tools:R,skills:D,context:{...e,accessToken:p,outdir:e.outdir||"",baseUrl:e.baseUrl||g(r),internalBaseUrl:F(r),apiDescriptionsMap:u,graphqlDescriptions:y,products:U,metadata:C,mcpMode:A,elicitationEnabled:d,gatewayFetchBudgetPerExec:T,searchIgnoredRoutes:S,clientIp:N(r),...re(r)}},P=await j({...O,getKv:e.getKv});return await $(async()=>P,{keepAliveMs:se,onerror:o=>f.warn(`MCP handler: ${o.message}`)}).fetch(r,M!==void 0?{parsedBody:M}:void 0)},A):W()};async function de(r){try{return await r.json()}catch(e){throw new K(e instanceof Error?e.message:String(e),{cause:e})}}var Ne=pe;export{Ne as default};
@@ -1 +1 @@
1
- import{fileURLToPath as F}from"node:url";import{dirname as $,join as M}from"node:path";import{MCP_ROUTE as k,PUBLIC_MCP_ROUTE as D}from"@redocly/theme/core/constants/mcp";import{logger as c}from"../../tools/notifiers/logger.js";import{reporter as B}from"../../tools/notifiers/reporter.js";import{isDefined as x}from"../../../utils/guards/is-defined.js";import{canAnonymousAccessMcpFeature as P}from"../../utils/rbac/can-anonymous-access-mcp-feature.js";import{shouldHandleMcpAuth as R}from"../../utils/rbac/should-handle-mcp-auth.js";import{MCP_CREDENTIALS_HANDLER_ID as _,MCP_CREDENTIALS_ROUTE as V,MCP_DOCS_SERVER_HANDLER_ID as T,MCP_NOT_ALLOWED_TEMPLATE_ID as J}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as W,bundledGraphqlDocumentsToMcpOpenApi as X,buildGraphqlDescriptions as z,collectIgnoredRoutes as K,filterIgnoredApiDescriptions as S,getCleanedUpApiDescriptions as Q,isMcpInRedirects as Y}from"./utils.js";import{getMcpMode as O,isMcpElicitationEnabled as Z,isMcpGatewayEnabled as ee}from"./utils/mode.js";import{findGatedToolNames as te}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as oe}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as ie}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as re,isGatewayEligible as se}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as ne}from"./gateway-mcp/rate-limit/limits.js";import{resetCustomMcpTools as ae,scanCustomMcpTools as ce}from"./custom-tools/scan-custom-tools.js";import{telemetryTraceStep as pe}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as w}from"./constants.js";const b=$(F(import.meta.url));async function le(){let o;const p={registeredToolNames:[]};let l=0,d=!1;return{id:"mcp",async processContent(e,n){await pe("com.redocly.build.plugin.mcp",async s=>{const i=await n.getConfig(),{mcp:r,redirects:g}=i;if(s?.setAttribute("config",JSON.stringify(r||{})),s?.setAttribute("mcpMode",O()),s?.setAttribute("mcpGateway",String(ee())),r?.hide||r?.docs?.hide){ae(e,p);return}Y(g)&&await B.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(J,"@redocly/theme/layouts/McpNotAllowed");const u=!!i?.access?.requiresLogin,a=i?.access?.rbac||{},E=O(),h=Z(),{budgetPerExec:f}=ne();if(!o){c.info("Configuring MCP servers...");const C=W(await e.loadApiDocuments(n)),L=X(await e.loadApiDocuments(n)),v=r?.docs?.ignore||[],q=Q(S(C,v)),I=re(r),A=new Map;for(const t of q){const j=t.definition.info?.title||"",U=t.definition.info?.version||"";A.set(t.relativePath,{name:j,description:typeof t.definition.info?.description=="string"?t.definition.info.description:"",version:U,servers:t.definition.servers||[],relativePath:t.relativePath,...I&&se(t.definition.info,t.relativePath,a)?{fetchEligible:!0}:{}})}const y=z(S(L,v)),G=Object.values(i.products||{}).map(t=>t?.name).filter(x),N=i.l10n?.locales?.map(t=>t.code)??n.fs.localeFolders,H=oe({products:G,locales:N,requiresLogin:u,rbac:a,hasGraphqlApis:y.length>0,elicitationEnabled:h,hasCallableApis:A.values().some(t=>t.fetchEligible)});e.addMcpTools(M(b,"./docs-mcp/tools/index.js"),H),E==="code"&&e.addMcpTools(M(b,"./codemode/tools/execute.js"),[ie]),o={apiDescriptionsMap:Object.fromEntries(A),graphqlDescriptions:y}}const m=await ce(e,n,p);s?.setAttribute("customToolsCount",String(m)),m!==l&&(l=m,m>0&&c.info(`Registered ${m} custom MCP tool${m===1?"":"s"} from @mcp folders`)),await me({actions:e,handlerPath:M(b,"./handlers/docs-mcp-handler.js"),apiDescriptionsMap:o.apiDescriptionsMap,graphqlDescriptions:o.graphqlDescriptions,mcpConfig:r,mcpMode:E,elicitationEnabled:h,gatewayFetchBudgetPerExec:f,requiresLogin:u,rbacConfig:a}),d||(c.info("Registered Docs MCP Server endpoint at /mcp"),d=!0)})},async afterRoutesCreated(e,n){const s=await n.getConfig(),{mcp:i}=s,r=!i?.hide&&!i?.docs?.hide;e.markGatedMcpTools(await te(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:r,name:i?.docs?.name||w,publicEndpoint:r&&de(i,s)}}})}}}var Le=le;function de(o,p){const l=!!p.access?.requiresLogin,d=p.access?.rbac||{};return!!o?.docs?.publicEndpoint&&R(l,d)&&P(d,l)}async function me({actions:o,handlerPath:p,apiDescriptionsMap:l,graphqlDescriptions:d,mcpConfig:e,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:i,requiresLogin:r,rbacConfig:g}){o.createRequestHandler(T,p);const u=a=>async(E,h)=>{const f=K(h.getAllRoutes(),e?.docs?.ignore||[]);return f.length>0&&c.verbose(`mcp.docs.ignore: hiding ${f.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:l,graphqlDescriptions:d,mcpDocsServerName:e?.docs?.name||w,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:i,searchIgnoredRoutes:f,...a?{isPublicEndpoint:a}:{}},serverOutDir:o.serverOutDir,tools:o.getMcpTools().map(({importPath:m,...C})=>C)}}};if(o.addApiRoute({slug:k,requestHandlerId:T,httpMethod:"all",getStaticData:u(!1)}),e?.docs?.publicEndpoint&&(R(r,g)?P(g,r)?(o.addApiRoute({slug:D,requestHandlerId:T,httpMethod:"all",getStaticData:u(!0)}),c.verbose(`Registered public Docs MCP Server endpoint at ${D}`)):c.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${D}.`):c.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${D}.`)),s){const a=M(b,"./handlers/mcp-credentials-handler.js");o.createRequestHandler(_,a),o.addApiRoute({slug:V,requestHandlerId:_,httpMethod:"all",getStaticData:async()=>({props:{}})}),c.verbose("Registered MCP credentials endpoint at /mcp/credentials")}}export{Le as default,le as mcpServerPlugin};
1
+ import{fileURLToPath as F}from"node:url";import{dirname as $,join as b}from"node:path";import{MCP_ROUTE as k,PUBLIC_MCP_ROUTE as M}from"@redocly/theme/core/constants/mcp";import{logger as c}from"../../tools/notifiers/logger.js";import{reporter as B}from"../../tools/notifiers/reporter.js";import{isDefined as x}from"../../../utils/guards/is-defined.js";import{canAnonymousAccessMcpFeature as P}from"../../utils/rbac/can-anonymous-access-mcp-feature.js";import{shouldHandleMcpAuth as R}from"../../utils/rbac/should-handle-mcp-auth.js";import{MCP_CREDENTIALS_HANDLER_ID as _,MCP_CREDENTIALS_ROUTE as V,MCP_DOCS_SERVER_HANDLER_ID as T,MCP_NOT_ALLOWED_TEMPLATE_ID as J}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as W,bundledGraphqlDocumentsToMcpOpenApi as X,buildGraphqlDescriptions as z,collectIgnoredRoutes as K,filterIgnoredApiDescriptions as S,getCleanedUpApiDescriptions as Q,isMcpInRedirects as Y}from"./utils.js";import{getMcpMode as O,isMcpElicitationEnabled as Z,isMcpGatewayEnabled as ee}from"./utils/mode.js";import{findGatedToolNames as te}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as oe}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as ie}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as re,isGatewayEligible as se}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as ne}from"./gateway-mcp/rate-limit/limits.js";import{resetCustomMcpTools as ae,scanCustomMcpTools as ce}from"./custom-tools/scan-custom-tools.js";import{telemetryTraceStep as pe}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as w}from"./constants.js";const D=$(F(import.meta.url));async function de(){let o;const p={registeredToolNames:[]};let d=0,l=!1;return{id:"mcp",async processContent(e,n){await pe("com.redocly.build.plugin.mcp",async s=>{const i=await n.getConfig(),{mcp:r,redirects:g}=i;if(s?.setAttribute("config",JSON.stringify(r||{})),s?.setAttribute("mcpMode",O()),s?.setAttribute("mcpGateway",String(ee())),r?.hide||r?.docs?.hide){ae(e,p);return}Y(g)&&await B.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(J,"@redocly/theme/layouts/McpNotAllowed");const u=!!i?.access?.requiresLogin,a=i?.access?.rbac||{},E=O(),h=Z(),{budgetPerExec:f}=ne();if(!o){c.info("Configuring MCP servers...");const C=W(await e.loadApiDocuments(n)),L=X(await e.loadApiDocuments(n)),v=r?.docs?.ignore||[],q=Q(S(C,v)),I=re(r),A=new Map;for(const t of q){const j=t.definition.info?.title||"",U=t.definition.info?.version||"";A.set(t.relativePath,{name:j,description:typeof t.definition.info?.description=="string"?t.definition.info.description:"",version:U,servers:t.definition.servers||[],relativePath:t.relativePath,...I&&se(t.definition.info,t.relativePath,a)?{fetchEligible:!0}:{}})}const y=z(S(L,v)),G=Object.values(i.products||{}).map(t=>t?.name).filter(x),N=i.l10n?.locales?.map(t=>t.code)??n.fs.localeFolders,H=oe({products:G,locales:N,requiresLogin:u,rbac:a,hasGraphqlApis:y.length>0,elicitationEnabled:h,hasCallableApis:A.values().some(t=>t.fetchEligible),feedbackHidden:i.feedback?.hide===!0});e.addMcpTools(b(D,"./docs-mcp/tools/index.js"),H),E==="code"&&e.addMcpTools(b(D,"./codemode/tools/execute.js"),[ie]),o={apiDescriptionsMap:Object.fromEntries(A),graphqlDescriptions:y}}const m=await ce(e,n,p);s?.setAttribute("customToolsCount",String(m)),m!==d&&(d=m,m>0&&c.info(`Registered ${m} custom MCP tool${m===1?"":"s"} from @mcp folders`)),await me({actions:e,handlerPath:b(D,"./handlers/docs-mcp-handler.js"),apiDescriptionsMap:o.apiDescriptionsMap,graphqlDescriptions:o.graphqlDescriptions,mcpConfig:r,mcpMode:E,elicitationEnabled:h,gatewayFetchBudgetPerExec:f,requiresLogin:u,rbacConfig:a}),l||(c.info("Registered Docs MCP Server endpoint at /mcp"),l=!0)})},async afterRoutesCreated(e,n){const s=await n.getConfig(),{mcp:i}=s,r=!i?.hide&&!i?.docs?.hide;e.markGatedMcpTools(await te(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:r,name:i?.docs?.name||w,publicEndpoint:r&&le(i,s)}}})}}}var Le=de;function le(o,p){const d=!!p.access?.requiresLogin,l=p.access?.rbac||{};return!!o?.docs?.publicEndpoint&&R(d,l)&&P(l,d)}async function me({actions:o,handlerPath:p,apiDescriptionsMap:d,graphqlDescriptions:l,mcpConfig:e,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:i,requiresLogin:r,rbacConfig:g}){o.createRequestHandler(T,p);const u=a=>async(E,h)=>{const f=K(h.getAllRoutes(),e?.docs?.ignore||[]);return f.length>0&&c.verbose(`mcp.docs.ignore: hiding ${f.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:d,graphqlDescriptions:l,mcpDocsServerName:e?.docs?.name||w,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:i,searchIgnoredRoutes:f,...a?{isPublicEndpoint:a}:{}},serverOutDir:o.serverOutDir,tools:o.getMcpTools().map(({importPath:m,...C})=>C)}}};if(o.addApiRoute({slug:k,requestHandlerId:T,httpMethod:"all",getStaticData:u(!1)}),e?.docs?.publicEndpoint&&(R(r,g)?P(g,r)?(o.addApiRoute({slug:M,requestHandlerId:T,httpMethod:"all",getStaticData:u(!0)}),c.verbose(`Registered public Docs MCP Server endpoint at ${M}`)):c.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${M}.`):c.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${M}.`)),s){const a=b(D,"./handlers/mcp-credentials-handler.js");o.createRequestHandler(_,a),o.addApiRoute({slug:V,requestHandlerId:_,httpMethod:"all",getStaticData:async()=>({props:{}})}),c.verbose("Registered MCP credentials endpoint at /mcp/credentials")}}export{Le as default,de as mcpServerPlugin};
@@ -1 +1 @@
1
- import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as M,MCP_TOOL_IS_AVAILABLE_KEY as v}from"../../../workers/mcp-tool-worker-pool.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as k}from"../constants.js";import{isAnonymousSessionToolAvailable as C}from"../docs-mcp/tools/core/create-anonymous-session.js";import{isManageApiCredentialsToolAvailable as L,manageApiCredentials as y,manageApiCredentialsSchema as D}from"../codemode/tools/manage-credentials.js";import{EXECUTE_TOOL_SCHEMA as A}from"../codemode/tools/execute-schema.js";import{EXECUTE_TOOL_NAME as w,CODE_MODE_TOOL_NAMES as N,FETCH_TOOL_NAME as x}from"../codemode/constants.js";import{buildCodeModeInstructions as H,buildExecuteDescription as I}from"../codemode/prompts.js";import{toToolListEntries as U}from"../codemode/build-tool-list.js";import{composeExecuteAnnotations as F,getActiveCapabilities as P}from"../codemode/capabilities/index.js";import{describeToolsSchema as R,describeTools as z}from"../codemode/tools/describe-tools.js";import{availableSandboxTools as W}from"../utils/available-sandbox-tools.js";import{toRegisterToolConfig as _}from"../utils/tool-registration.js";import{createMcpServer as X,createToolDispatch as Y,extractSerializableContext as $}from"./mcp-server.js";function g(c,r){const o=r.schema;if(C(c))return o;const{sessionHandle:f,...m}=o.properties;return{...o,properties:m}}async function ae({name:c,tools:r,context:o,skills:f=[],getKv:m}){const l=$(o),a=o.mcpMode==="code",s=a?P(o):[],T=s.some(e=>e.id===x),p=a?await W(r.filter(e=>!N.has(e.name)),l):[],b=U(p,s),O=a&&L(o),n=X(c,a?H(T,b):void 0),E={getClientCapabilities:()=>n.server.getClientCapabilities(),getKv:m},d=Y(l,E),u=async e=>{try{return await M.exec(v,[{toolName:e,context:l}],{timeout:1e4})}catch(i){return S.error(`Failed to check MCP tool availability for "${e}": ${i instanceof Error?i.message:String(i)}`),!1}};if(a){const e=[{tool:{...A,description:I(T,b),schema:g(o,A),annotations:F(s)},callback:d(w)},{tool:R(p,s),callback:z(p,u,s,o)}],i=r.find(t=>t.name===k);if(i&&C(o)&&e.push({tool:i,callback:d(i.name)}),O){const t=D(o);e.push({tool:{...t,schema:g(o,t)},callback:y(l,E)})}for(const{tool:t,callback:h}of e)n.registerTool(t.name,_(t),h)}else await Promise.all(r.map(async e=>{const i=n.registerTool(e.name,_(e),d(e.name));await u(e.name)||i.disable()}));for(const e of f)n.registerResource(e.slug,e.uri,{title:e.name,description:e.description,mimeType:"text/markdown"},async()=>({contents:[{uri:e.uri,mimeType:"text/markdown",text:e.content}]}));return n}export{ae as createDocsMcpServer};
1
+ import{logger as g}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as M,MCP_TOOL_IS_AVAILABLE_KEY as v}from"../../../workers/mcp-tool-worker-pool.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as k}from"../constants.js";import{isAnonymousSessionToolAvailable as C}from"../docs-mcp/tools/core/create-anonymous-session.js";import{isManageApiCredentialsToolAvailable as L,manageApiCredentials as y,manageApiCredentialsSchema as x}from"../codemode/tools/manage-credentials.js";import{EXECUTE_TOOL_SCHEMA as h}from"../codemode/tools/execute-schema.js";import{EXECUTE_TOOL_NAME as D,CODE_MODE_TOOL_NAMES as w,FETCH_TOOL_NAME as N}from"../codemode/constants.js";import{buildCodeModeInstructions as H,buildExecuteDescription as I}from"../codemode/prompts.js";import{toToolListEntries as U}from"../codemode/build-tool-list.js";import{composeExecuteAnnotations as F,getActiveCapabilities as P}from"../codemode/capabilities/index.js";import{describeToolsSchema as R,describeTools as z}from"../codemode/tools/describe-tools.js";import{availableSandboxTools as W}from"../utils/available-sandbox-tools.js";import{toRegisterToolConfig as A}from"../utils/tool-registration.js";import{createMcpServer as X,createToolDispatch as Y,extractSerializableContext as $}from"./mcp-server.js";function S(s,t){const o=t.schema;if(C(s))return o;const{sessionHandle:f,...p}=o.properties;return{...o,properties:p}}function j(s){return Object.fromEntries(s.map(t=>[t.name,t.schema]))}async function se({name:s,tools:t,context:o,skills:f=[],getKv:p}){const n=$(o),r=o.mcpMode==="code",c=r?P(o):[],T=c.some(e=>e.id===N),m=r?await W(t.filter(e=>!w.has(e.name)),n):[],b=U(m,c),O=r&&L(o),l=X(s,r?H(T,b):void 0),E={getClientCapabilities:()=>l.server.getClientCapabilities(),getKv:p},d=Y(r?{...n,sandboxToolSchemas:j(m)}:n,E),u=async e=>{try{return await M.exec(v,[{toolName:e,context:n}],{timeout:1e4})}catch(i){return g.error(`Failed to check MCP tool availability for "${e}": ${i instanceof Error?i.message:String(i)}`),!1}};if(r){const e=[{tool:{...h,description:I(T,b),schema:S(o,h),annotations:F(c)},callback:d(D)},{tool:R(m,c),callback:z(m,u,c,o)}],i=t.find(a=>a.name===k);if(i&&C(o)&&e.push({tool:i,callback:d(i.name)}),O){const a=x(o);e.push({tool:{...a,schema:S(o,a)},callback:y(n,E)})}for(const{tool:a,callback:_}of e)l.registerTool(a.name,A(a),_)}else await Promise.all(t.map(async e=>{const i=l.registerTool(e.name,A(e),d(e.name));await u(e.name)||i.disable()}));for(const e of f)l.registerResource(e.slug,e.uri,{title:e.name,description:e.description,mimeType:"text/markdown"},async()=>({contents:[{uri:e.uri,mimeType:"text/markdown",text:e.content}]}));return l}export{se as createDocsMcpServer};
@@ -1,5 +1,5 @@
1
- import{McpServer as j}from"@modelcontextprotocol/server";import{logger as b}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as D,MCP_TOOL_WORKER_KEY as F}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as K}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as L,stripAuthRequiredMeta as M,upstreamAuthKey as S}from"../gateway-mcp/upstream-auth.js";import{buildCredentialsToolHint as H,buildSchemeChoiceHint as B,buildUpstreamCredentialFallback as q,buildUpstreamCredentialInputRequired as P,isCredentialElicitationDeclined as $,schemeLabelOf as x}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as T,resolveUpstreamApiAuth as N,schemeChoicesOf as v}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as G,resolveSecretSubject as V}from"../gateway-mcp/subject.js";const W=6e4,E=new Map;function Y(e,t){const r=E.get(e);if(r)return r;const n=t().finally(()=>{E.delete(e)});return E.set(e,n),n}function pe(e,t){return new j({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}})}function de(e,t){return r=>async(n,s)=>{b.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,c=s.mcpReq.envelope!==void 0,a=i?V(e.user,n.sessionHandle):{kind:"anonymous"},o=a.kind==="resolved"?a.subject:void 0;if(a.kind==="invalid")return{content:[{type:"text",text:a.message}],isError:!0};const p=async()=>{if(!(!i||!o||!t?.getKv))try{const m=await t.getKv();return await K.getSecretsForSubject(m,o)}catch(m){b.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(m)}`);return}};let u;const l=async()=>{u=await p();const m={toolName:r,args:n,context:{...e,upstreamSecrets:u},extra:re(s)};return await D.exec(F,[m],{timeout:W})};let d=await l();const f=L(d);if(f.length>0&&i&&!o)return M({...d,content:[{type:"text",text:G}],isError:!0});if(f.length>0&&i&&o&&t?.getKv){b.info(`gateway mcp credentials prompt: era=${c?"2026-07-28":"2025"} clientElicitation=${Q(t)}`);const m=await t.getKv(),h=await X(e,f),k=f.map(_=>Z(_,h)),y=z(u),g=c?await ee(e,s,k,o,m,y,h,t):await te(e,k,o,m,y,h);switch(g.kind){case"inputRequired":return g.result;case"retry":d=await l();break;case"prependText":d={...d,content:[{type:"text",text:g.text},...d.content],isError:!0};break;case"none":break}}return M(d)}}function J(e){return!!e?.getClientCapabilities()?.elicitation?.url}function Q(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}async function X(e,t){const r=new Map;for(const n of new Set(t.map(s=>s.api)))r.set(n,await N(e,n));return r}function Z(e,t){const r=t.get(e.api);if(!r)return{...e,scheme:e.scheme??T};const n=e.scheme&&r.schemes[e.scheme]?e.scheme:r.defaultScheme;return{...e,scheme:n}}function z(e){const t=new Set;for(const[r,n]of Object.entries(e??{}))for(const s of Object.keys(n))t.add(S({api:r,scheme:s}));return t}function R(e,t){return e.status===401&&!t.has(S(e))}function A(e,t){const r=t.get(e.api),n=e.sentScheme??e.scheme;return r&&n?x(r.schemes,n):void 0}function I(e,t){const r=t.get(e.api);return!r||!e.schemes?.length?[]:v(r,e.schemes)}function C(e,t){return I(e,t).length>1}function w(e,t){return e.map(r=>H(r,A(r,t))).join(`
1
+ import{McpServer as _}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as j,MCP_TOOL_WORKER_KEY as D}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as F}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as K,stripAuthRequiredMeta as y,upstreamAuthKey as b}from"../gateway-mcp/upstream-auth.js";import{buildCredentialsToolHint as L,buildSchemeChoiceHint as H,buildUpstreamCredentialFallback as T,buildUpstreamCredentialInputRequired as P,dedupeElicitation as $,isCredentialElicitationDeclined as B,isStatelessRequest as x,schemeLabelOf as N,supportsUrlElicitation as v}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as q,resolveUpstreamApiAuth as G,schemeChoicesOf as V}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as W,resolveSecretSubject as Y}from"../gateway-mcp/subject.js";const J=6e4;function pe(e,t){return new _({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}})}function de(e,t){return r=>async(n,s)=>{S.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,c=x(s.mcpReq),a=i?Y(e.user,n.sessionHandle):{kind:"anonymous"},o=a.kind==="resolved"?a.subject:void 0;if(a.kind==="invalid")return{content:[{type:"text",text:a.message}],isError:!0};const p=async()=>{if(!(!i||!o||!t?.getKv))try{const m=await t.getKv();return await F.getSecretsForSubject(m,o)}catch(m){S.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(m)}`);return}};let u;const l=async()=>{u=await p();const m={toolName:r,args:n,context:{...e,upstreamSecrets:u},extra:re(s)};return await j.exec(D,[m],{timeout:J})};let d=await l();const f=K(d);if(f.length>0&&i&&!o)return y({...d,content:[{type:"text",text:W}],isError:!0});if(f.length>0&&i&&o&&t?.getKv){S.info(`gateway mcp credentials prompt: era=${c?"2026-07-28":"2025"} clientElicitation=${Q(t)}`);const m=await t.getKv(),h=await X(e,f),w=f.map(O=>Z(O,h)),E=z(u),g=c?await ee(e,s,w,o,m,E,h,t):await te(e,w,o,m,E,h);switch(g.kind){case"inputRequired":return g.result;case"retry":d=await l();break;case"prependText":d={...d,content:[{type:"text",text:g.text},...d.content],isError:!0};break;case"none":break}}return y(d)}}function Q(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}async function X(e,t){const r=new Map;for(const n of new Set(t.map(s=>s.api)))r.set(n,await G(e,n));return r}function Z(e,t){const r=t.get(e.api);if(!r)return{...e,scheme:e.scheme??q};const n=e.scheme&&r.schemes[e.scheme]?e.scheme:r.defaultScheme;return{...e,scheme:n}}function z(e){const t=new Set;for(const[r,n]of Object.entries(e??{}))for(const s of Object.keys(n))t.add(b({api:r,scheme:s}));return t}function R(e,t){return e.status===401&&!t.has(b(e))}function M(e,t){const r=t.get(e.api),n=e.sentScheme??e.scheme;return r&&n?N(r.schemes,n):void 0}function A(e,t){const r=t.get(e.api);return!r||!e.schemes?.length?[]:V(r,e.schemes)}function I(e,t){return A(e,t).length>1}function k(e,t){return e.map(r=>L(r,M(r,t))).join(`
2
2
 
3
- `)}function U(e,t,r){const n=e.map(s=>B(s,I(s,r)));return t.length>0&&n.push(w(t,r)),n.join(`
3
+ `)}function C(e,t,r){const n=e.map(s=>H(s,A(s,r)));return t.length>0&&n.push(k(t,r)),n.join(`
4
4
 
5
- `)}function O(e,t,r,n,s){const[i,...c]=t,a=s.get(i.api),o=i.scheme??a?.defaultScheme??T,p=A(i,s);return{subject:r,baseUrl:e.baseUrl??"",target:{api:i.api,name:i.name,scheme:o,...p?{schemeLabel:p}:{}},auth:a?.schemes[o]??{kind:"raw"},...a?{apiAuth:a}:{},kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:i.status,...i.detail?{detail:i.detail}:{},otherApiNames:c.map(u=>u.name)}}}async function ee(e,t,r,n,s,i,c,a){if($(t.mcpReq.inputResponses))return{kind:"none"};const o=r.filter(l=>R(l,i));if(o.length===0)return{kind:"prependText",text:w(r,c)};const p=o.filter(l=>C(l,c));if(p.length>0){const l=r.filter(d=>!p.includes(d));return{kind:"prependText",text:U(p,l,c)}}const u=O(e,o,n,s,c);return J(a)?{kind:"inputRequired",result:await P(u)}:{kind:"prependText",text:await q(u)}}async function te(e,t,r,n,s,i){const c=t.filter(u=>R(u,s));if(c.length===0)return{kind:"prependText",text:w(t,i)};const a=c.filter(u=>C(u,i));if(a.length>0){const u=t.filter(l=>!a.includes(l));return{kind:"prependText",text:U(a,u,i)}}const o=O(e,c,r,n,i);return{kind:"prependText",text:await Y(`${r}:${S(c[0])}`,()=>q(o))}}function re(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function me(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,client:e.client,clientVersion:e.clientVersion,upstreamSecrets:e.upstreamSecrets,searchIgnoredRoutes:e.searchIgnoredRoutes,internalBaseUrl:e.internalBaseUrl,fs:void 0}}export{pe as createMcpServer,de as createToolDispatch,me as extractSerializableContext};
5
+ `)}function U(e,t,r,n,s){const[i,...c]=t,a=s.get(i.api),o=i.scheme??a?.defaultScheme??q,p=M(i,s);return{subject:r,baseUrl:e.baseUrl??"",target:{api:i.api,name:i.name,scheme:o,...p?{schemeLabel:p}:{}},auth:a?.schemes[o]??{kind:"raw"},...a?{apiAuth:a}:{},kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:i.status,...i.detail?{detail:i.detail}:{},otherApiNames:c.map(u=>u.name)}}}async function ee(e,t,r,n,s,i,c,a){if(B(t.mcpReq.inputResponses))return{kind:"none"};const o=r.filter(l=>R(l,i));if(o.length===0)return{kind:"prependText",text:k(r,c)};const p=o.filter(l=>I(l,c));if(p.length>0){const l=r.filter(d=>!p.includes(d));return{kind:"prependText",text:C(p,l,c)}}const u=U(e,o,n,s,c);return v(a?.getClientCapabilities())?{kind:"inputRequired",result:await P(u)}:{kind:"prependText",text:await T(u)}}async function te(e,t,r,n,s,i){const c=t.filter(u=>R(u,s));if(c.length===0)return{kind:"prependText",text:k(t,i)};const a=c.filter(u=>I(u,i));if(a.length>0){const u=t.filter(l=>!a.includes(l));return{kind:"prependText",text:C(a,u,i)}}const o=U(e,c,r,n,i);return{kind:"prependText",text:await $(`${r}:${b(c[0])}`,()=>T(o))}}function re(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function me(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,client:e.client,clientVersion:e.clientVersion,upstreamSecrets:e.upstreamSecrets,searchIgnoredRoutes:e.searchIgnoredRoutes,internalBaseUrl:e.internalBaseUrl,sandboxToolSchemas:e.sandboxToolSchemas,fs:void 0}}export{pe as createMcpServer,de as createToolDispatch,me as extractSerializableContext};
@@ -107,6 +107,7 @@ export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | k
107
107
  upstreamSecrets?: UpstreamSecrets;
108
108
  searchIgnoredRoutes?: string[];
109
109
  internalBaseUrl?: string;
110
+ sandboxToolSchemas?: Record<string, Record<string, unknown>>;
110
111
  fs?: McpToolFileSystem;
111
112
  };
112
113
  export type McpToolHandler<TArgs extends ToolArgs = ToolArgs> = (args: TArgs, context: McpToolContext, extra: McpToolExtra) => Promise<McpToolWorkerResponse | McpToolJsonValue>;
@@ -1 +1 @@
1
- import{logger as i}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as a,MCP_TOOL_AVAILABILITY_KEY as m}from"../../../workers/mcp-tool-worker-pool.js";async function p(o,t){if(o.length===0)return o;const l=o.map(r=>r.name);let e;try{e=await a.exec(m,[{toolNames:l,context:t}],{timeout:1e4})}catch(r){return i.error(`Failed to resolve MCP tool availability: ${r instanceof Error?r.message:String(r)}`),o}return!e||typeof e!="object"?o:o.filter(r=>e[r.name]!==!1)}export{p as availableSandboxTools};
1
+ import{logger as a}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as n,MCP_TOOL_AVAILABILITY_KEY as m}from"../../../workers/mcp-tool-worker-pool.js";async function p(t,l){if(t.length===0)return t;const e=t.map(o=>o.name);let r;try{r=await n.exec(m,[{toolNames:e,context:l}],{timeout:1e4})}catch(o){return i(t,o instanceof Error?o.message:String(o))}return!r||typeof r!="object"?i(t,"the worker returned no availability map"):t.filter(o=>r[o.name]!==!1)}function i(t,l){const e=t.filter(r=>r.hasAvailabilityGate);return e.length===0?t:(a.warn(`MCP tool availability is unknown (${l}); the sandbox tool list drops the gated tools: ${e.map(r=>r.name).join(", ")}.`),t.filter(r=>!r.hasAvailabilityGate))}export{p as availableSandboxTools};
@@ -1 +1 @@
1
- import{telemetry as h}from"../../../telemetry/index.js";import{McpServerType as c}from"../constants.js";import{EXECUTE_TOOL_NAME as w}from"../codemode/constants.js";import{isPlainObject as f}from"../../../../utils/guards/is-plain-object.js";import{logMcpToolCallAudit as g,reportMcpToolCalled as _,reportMcpToolError as u}from"./mcp-tool-telemetry.js";import{attachToolFileSystem as E}from"./tool-file-system.js";import{importMcpToolHandlers as S}from"./import-mcp-tool-handlers.js";function A(e){return e.content.map(t=>t.type==="text"?t.text:"")}async function J(e){h.initialize();const{toolName:t,args:o,extra:s}=e,r=E(e.context),n=r?.mcpMode??"tools",a={client:r?.client,clientVersion:r?.clientVersion},d=performance.now();try{const i=await M(t);if(!i)throw new Error(`MCP tool "${t}" is not registered or its module does not export a handler for "${t}". Expected \`export default { '${t}': handler }\` or a single tool definition with \`description\` and \`execute\` as the default export.`);const C=typeof i=="function"?i:i.execute,l=P(t,await C(o,r,s),n),m=Math.round(performance.now()-d),{errorClass:y}=l;delete l.errorClass;const p=l.isError?y??(t===w?"sandbox_error":"tool_error"):void 0,v=t===w&&y==="tool_error",x=A(l),T=x.join("");return g({tool:t,mode:n,serverType:c.Docs,args:o,result:T,errorClass:p,...a}),p?v||u({tool:t,message:x.join(" "),mode:n,serverType:c.Docs,durationMs:m,errorClass:p,...a}):_({tool:t,mode:n,serverType:c.Docs,durationMs:m,outputLength:T.length,...a}),l}catch(i){throw g({tool:t,mode:n,serverType:c.Docs,args:o,errorClass:"server_error",...a}),u({tool:t,message:i instanceof Error?i.message:String(i),mode:n,serverType:c.Docs,durationMs:Math.round(performance.now()-d),stack:i instanceof Error&&i.stack||"",errorClass:"server_error",...a}),i}}function b(e){return"execute"in e&&typeof e.execute=="function"&&"description"in e&&typeof e.description=="string"}async function M(e){const t=(await S())[e];if(!t)return;const{default:o}=await t();return O(o,e)}function O(e,t){return b(e)?e:e[t]}function P(e,t,o){if(L(t))return t;if(typeof t=="string")return{content:[{type:"text",text:t}]};let s;try{s=JSON.stringify(t)}catch(n){throw new Error(`MCP tool "${e}" returned a value that cannot be serialized to JSON: `+(n instanceof Error?n.message:String(n)))}if(s===void 0)throw new Error(`MCP tool "${e}" returned an invalid result.`);const r=o==="code"?JSON.parse(s):void 0;return f(r)?{content:[{type:"text",text:s}],structuredContent:r}:{content:[{type:"text",text:s}]}}const k=new Set(["text","image","audio","resource","resource_link"]);function L(e){if(!f(e))return!1;const{content:t}=e;return Array.isArray(t)&&t.every(o=>f(o)&&k.has(o.type))}async function j(e){h.initialize();const{toolName:t}=e,o=E(e.context),s=performance.now();try{const r=await M(t);return r?typeof r=="object"&&typeof r.isAvailable=="function"?await r.isAvailable(o):!0:!1}catch(r){throw u({tool:t,message:"Failed to check if MCP tool is available: "+(r instanceof Error?r.message:String(r)),mode:o?.mcpMode??"tools",serverType:c.Docs,durationMs:Math.round(performance.now()-s),stack:r instanceof Error&&r.stack||"",errorClass:"server_error",client:o?.client,clientVersion:o?.clientVersion}),r}}async function F(e){const{toolNames:t,context:o}=e,s=await Promise.all(t.map(r=>j({toolName:r,context:o}).catch(()=>!1)));return Object.fromEntries(t.map((r,n)=>[r,s[n]]))}export{J as executeMcpTool,j as isMcpToolAvailable,F as mcpToolAvailability,O as resolveToolHandler};
1
+ import{telemetry as h}from"../../../telemetry/index.js";import{McpServerType as c}from"../constants.js";import{EXECUTE_TOOL_NAME as w}from"../codemode/constants.js";import{isPlainObject as p}from"../../../../utils/guards/is-plain-object.js";import{logMcpToolCallAudit as g,reportMcpToolCalled as _,reportMcpToolError as u}from"./mcp-tool-telemetry.js";import{attachToolFileSystem as E}from"./tool-file-system.js";import{importMcpToolHandlers as b}from"./import-mcp-tool-handlers.js";function A(t){return t.content.map(e=>e.type==="text"?e.text:"")}async function D(t){h.initialize();const{toolName:e,args:o,extra:s}=t,r=E(t.context),n=r?.mcpMode??"tools",a={client:r?.client,clientVersion:r?.clientVersion},d=performance.now();try{const i=await M(e);if(!i)throw new Error(`MCP tool "${e}" is not registered or its module does not export a handler for "${e}". Expected \`export default { '${e}': handler }\` or a single tool definition with \`description\` and \`execute\` as the default export.`);const C=typeof i=="function"?i:i.execute,l=P(e,await C(o,r,s),n),m=Math.round(performance.now()-d),{errorClass:y}=l;delete l.errorClass;const f=l.isError?y??(e===w?"sandbox_error":"tool_error"):void 0,v=e===w&&y==="tool_error",x=A(l),T=x.join("");return g({tool:e,mode:n,serverType:c.Docs,args:o,result:T,errorClass:f,...a}),f?v||u({tool:e,message:x.join(" "),mode:n,serverType:c.Docs,durationMs:m,errorClass:f,...a}):_({tool:e,mode:n,serverType:c.Docs,durationMs:m,outputLength:T.length,...a}),l}catch(i){throw g({tool:e,mode:n,serverType:c.Docs,args:o,errorClass:"server_error",...a}),u({tool:e,message:i instanceof Error?i.message:String(i),mode:n,serverType:c.Docs,durationMs:Math.round(performance.now()-d),stack:i instanceof Error&&i.stack||"",errorClass:"server_error",...a}),i}}function S(t){return"execute"in t&&typeof t.execute=="function"&&"description"in t&&typeof t.description=="string"}async function M(t){const e=(await b())[t];if(!e)return;const{default:o}=await e();return O(o,t)}function O(t,e){return S(t)?t:t[e]}function P(t,e,o){if(L(e))return e;if(typeof e=="string")return{content:[{type:"text",text:e}]};let s;try{s=JSON.stringify(e)}catch(n){throw new Error(`MCP tool "${t}" returned a value that cannot be serialized to JSON: `+(n instanceof Error?n.message:String(n)))}if(s===void 0)throw new Error(`MCP tool "${t}" returned an invalid result.`);const r=o==="code"?JSON.parse(s):void 0;return p(r)?{content:[{type:"text",text:s}],structuredContent:r}:{content:[{type:"text",text:s}]}}const k=new Set(["text","image","audio","resource","resource_link"]);function L(t){if(!p(t))return!1;const{content:e}=t;return Array.isArray(e)&&e.every(o=>p(o)&&k.has(o.type))}async function j(t){h.initialize();const{toolName:e}=t;if(N(e,t.context.metadata))return!1;const o=E(t.context),s=performance.now();try{const r=await M(e);return r?typeof r=="object"&&typeof r.isAvailable=="function"?await r.isAvailable(o):!0:!1}catch(r){throw u({tool:e,message:"Failed to check if MCP tool is available: "+(r instanceof Error?r.message:String(r)),mode:o?.mcpMode??"tools",serverType:c.Docs,durationMs:Math.round(performance.now()-s),stack:r instanceof Error&&r.stack||"",errorClass:"server_error",client:o?.client,clientVersion:o?.clientVersion}),r}}function N(t,e){const o=e?.disabledTools;return Array.isArray(o)&&o.includes(t)}async function F(t){const{toolNames:e,context:o}=t,s=await Promise.all(e.map(r=>j({toolName:r,context:o}).catch(()=>!1)));return Object.fromEntries(e.map((r,n)=>[r,s[n]]))}export{D as executeMcpTool,j as isMcpToolAvailable,F as mcpToolAvailability,O as resolveToolHandler};
@@ -1 +1 @@
1
- import*as v from"path";import{REDOCLY_ROUTE_RBAC as b,REDOCLY_TEAMS_RBAC as p}from"@redocly/config";import{VERSION_SEPARATOR as M}from"../constants/common.js";import{DEFAULT_LOCALE_PLACEHOLDER as _}from"../../constants/common.js";import{removeTrailingSlash as j}from"../../utils/url/remove-trailing-slash.js";import{isPrimitive as W}from"../../utils/guards/is-primitive.js";import{slash as w}from"../../utils/path/slash.js";import{isAbsoluteUrl as z}from"../../utils/url/is-absolute-url.js";import{normalizeRouteSlug as U}from"../../utils/path/normalize-route-slug.js";import{parsePathVersions as R}from"../../utils/path/parse-path-versions.js";import{logger as A}from"../tools/notifiers/logger.js";import{reporter as S}from"../tools/notifiers/reporter.js";import{shaDirPathShort as C}from"../utils/crypto/sha-dir-path-short.js";import{copyStaticFile as H,FileNotFoundError as E}from"../utils/fs.js";import{isIconPath as J,resolveAssetPath as Y}from"../utils/index.js";import{resolveSrcSet as G}from"../utils/resolve-src-set.js";import{isL10nPath as B}from"../fs/utils/is-l10n-path.js";import{getLocaleFromRelativePath as q}from"../fs/utils/get-locale-from-relative-path.js";async function D(e,s,n,t,a){if(!e)return;const f={...a,ignoreCustomSidebar:!0};if(W(e)){if(typeof e=="string"){const r=F(n.contentDir,s,a,e,t.fs);if(t.fs.exists(r)){if(n.getRouteByFsPath(r))return K({page:e},s,n,t,f);{const g=t.fs.getFileInfo(r);return g?H(n.contentDir,g.realRelativePath,n.outdir):void 0}}}return e}return Array.isArray(e)?I(N(e),s,n,t,f):Object.fromEntries(await Promise.all(Object.entries(e).map(async([r,l])=>[r,await D(l,s,n,t,a)])))}function N(e){return e.map(s=>({...s,items:s.items?N(s.items):void 0}))}async function I(e,s,n,t,a){if(Array.isArray(e))return(await Promise.all(e.map(f=>K(f,s,n,t,a)))).flatMap(f=>f)}async function P(e,s,n,t){let a,f;if(typeof e.icon=="object"){if("srcSet"in e.icon)try{a=await G(e.icon.srcSet,n.fs,{fromFileRelativePath:t.navFile,contentDir:s.contentDir,outdir:s.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`):await S.panicOnBuild(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`)}}else if(typeof e.icon=="string")if(z(e.icon)||J(e.icon))try{f=await Y(e.icon,n.fs,{fromFileRelativePath:t.navFile,contentDir:s.contentDir,outdir:s.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon" from ${t.navFile}: file ${f} does not exist`):await S.panicOnBuild(`Cannot resolve "item.icon" from ${t.navFile}: ${d.message}`)}else f=e.icon;return{icon:f,srcSet:a}}function Q(e){return s=>{if(s.type==="link"||s.type==="group"){const n=s.metadata;return n?Object.entries(e).every(([t,a])=>Array.isArray(a)?a.some(f=>n[t]===f):n[t]===a):!1}return!0}}async function K(e,s,n,t,a){if(e?.directory&&e.items?.length)return{type:"error",label:`Can't have both "directory" and "items" in the nav item: ${JSON.stringify(e)}`};e?.directory&&e?.group&&(e={...e,directory:void 0,items:[{directory:e.directory}]});const f=e?.directory?F(n.contentDir,s,a,e.directory,t.fs):void 0;if(f!==void 0){const i=f===""?"":f+"/",{locale:u,ignoredRoutes:o}=a;let h=n.getAllRoutesForLocale(u).filter(c=>!o?.has(c.baseSlug||"")&&c.fsPath.startsWith(i)&&!c.excludeFromSidebar).sort((c,y)=>c.fsPath===y.fsPath?0:c.baseSlug.localeCompare(y.baseSlug,void 0,{numeric:!0}));const m=e.includeByMetadata?Q(e.includeByMetadata):Boolean;if(!h.length)return[];const $=(await Promise.all(h.map(async c=>{const y=c.versions?.find(x=>x.active),L=y&&{version:y.version,isDefault:y.default,versionFolderId:y.folderId},V=ee(v.posix.relative(f,c.fsPath)),O=c.getSidebar?.(c);return!a.ignoreCustomSidebar&&O?.length?{type:"group",fsPath:c.fsPath,metadata:c.metadata,link:c.slug,routeSlug:c.slug,label:await c.getNavText?.()||c.slug,[p]:c[p],[b]:c[b],sidebarItems:k(O,L,c[p],c[b]),...L,relativePathFromDirectory:v.relative(f,c.fsPath).replace(/@[^\/]+\//,"")}:{type:"link",fsPath:c.fsPath,metadata:c.metadata,[p]:c[p],[b]:c[b],label:await c.getNavText?.()||c.slug,link:c.slug,routeSlug:c.slug,...L,relativePathFromDirectory:V}}))).filter(m);return e.flatten?T($,a):Z($,a)}let d=typeof e=="string"?e:e?.page,r,l;if(d){if(d.includes("#")){const[i,u]=d.split("#");u?l=u:A.warn(`Invalid heading anchor format in sidebar: "${u}". Heading anchors should contain only alphanumeric characters, hyphens, and underscores.`),d=i}if(d=j(d),a.locale&&a.locale!==_){const i=U(v.join(a.locale.toLowerCase(),d));r=n.getRouteBySlug(i)}r||(r=n.getRouteBySlug(d)),r||(r=n.getRouteByFsPath(F(n.contentDir,s,a,d,t.fs)))}const g=r?.metadata;if(e?.$ref){let i=F(n.contentDir,s,a,e.$ref,t.fs);if(a.ref!==void 0&&(i=v.posix.join(a.ref,e.$ref)),!t.fs.exists(i))return await S.panicOnBuildContentError(`Failed to load ${i} file. Make sure sidebar $ref path is correct.`),[];const u=await t.cache.load(i,"yaml"),o=v.dirname(i);return await I(u.data,s,n,t,{...a,navFile:i,ref:o})||[]}if(!r){const i=a.excludedFromLinkCheckerPatterns?.catalog.some(o=>o.test(d||"")),u=a.excludedFromLinkCheckerPatterns?.apiFunctions.some(o=>o.test(d||e.href||""));(u||i)&&(e={...e,href:d||e.href,page:void 0,target:e.target??(u?"_blank":void 0)}),await X(e,a,t,s)}if(r&&!e.disconnect&&!a.ignoreCustomSidebar&&(l||r?.getSidebar)){const i=r.versions?.find(o=>o.active),u=i&&{version:i.version,isDefault:i.default,versionFolderId:i.folderId};if(l){let o=[];if(r.getSidebar&&(o=r.getSidebar(r,{...e,...await P(e,n,t,a)}),o&&o.length>0)){const h=o[0];h&&h.link&&(h.link=`${h.link}#${l}`)}if(!o||o.length===0){let h=e.group||e.label||l;o=[{type:"link",fsPath:r.fsPath,metadata:r.metadata,link:`${r.slug}#${l}`,routeSlug:r.slug,label:h,labelTranslationKey:e.labelTranslationKey,...await P(e,n,t,a),[p]:r[p],[b]:{slug:r.slug,fsPath:r.fsPath},...u}]}return k(o,u,r[p],r[b])}if(r?.getSidebar){const o=r.getSidebar(r,{...e,...await P(e,n,t,a)}),h=e.group&&o?.[0]?.routeSlug===r.slug?o.slice(1):o;return k(a.groupCustomSidebars||e.group?[{type:"group",fsPath:r.fsPath,link:r.slug,routeSlug:r.slug,label:e.group||e.label||await r.getNavText?.()||r.slug,items:h,labelTranslationKey:e.labelTranslationKey,metadata:g,badges:e.badges,...await P(e,n,t,a)}]:o,u,r[p],r[b])}}if(r&&!e.group){const i=l?`${r.slug}#${l}`:r.slug;let u=e.label||l||"";u||(u=await r.getNavText?.()||r.slug);const o=r.versions?.find(m=>m.active),h=o&&{version:o.version,isDefault:o.default,versionFolderId:o.folderId};return{type:"link",fsPath:r.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,[p]:e?.rbac||r[p],[b]:{slug:r.slug,fsPath:r.fsPath},label:u,labelTranslationKey:e.labelTranslationKey,link:i,items:await I(e.items,s,n,t,a),separatorLine:e.separatorLine,linePosition:e.linePosition,routeSlug:e.disconnect?void 0:i,external:e.external,target:e.target,badges:e.badges,...await P(e,n,t,a),...h,additionalProps:e.additionalProps}}else if(e?.group){const i=r?.versions?.find(o=>o.active),u=R(a?.navFile);return{type:"group",fsPath:r?.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,version:i?.version||u?.versionName,versionFolderId:i?.folderId||(u?.versionFolderPath?C(u.versionFolderPath):void 0),label:e.group,link:r?.slug,routeSlug:r?.slug,badges:e.badges,labelTranslationKey:e.groupTranslationKey,items:await I(e.items,s,n,t,a),expanded:e.expanded?e.expanded.toString():void 0,selectFirstItemOnExpand:e.selectFirstItemOnExpand,menuStyle:e.menuStyle,separatorLine:e.separatorLine,linePosition:e.linePosition,...await P(e,n,t,a),[p]:e?.rbac,[b]:{slug:r?.slug||"",fsPath:r?.fsPath||""},additionalProps:e.additionalProps}}else{if(e?.href||e?.label)return{type:"link",metadata:g,link:e.href||"",label:e?.label||e?.href||"",labelTranslationKey:e.labelTranslationKey,external:e.external,target:e.target,separatorLine:e.separatorLine,linePosition:e.linePosition,badges:e.badges,...await P(e,n,t,a),[p]:e?.rbac,additionalProps:e.additionalProps};if(e?.separator!=null||e?.separatorLine!=null){const i=R(a?.navFile);return{type:"separator",metadata:g,version:i?.versionName,versionFolderId:i?.versionFolderPath?C(i.versionFolderPath):void 0,label:e.separator,labelTranslationKey:e.separatorTranslationKey,separatorLine:e.separatorLine,linePosition:e.linePosition,...await P(e,n,t,a),[p]:e?.rbac,additionalProps:e.additionalProps}}else{const i=R(a?.navFile);return{type:"error",label:`Can't resolve page: ${JSON.stringify(e)}`,version:i?.versionName,versionFolderId:i?.versionFolderPath?C(i.versionFolderPath):void 0}}}}async function X(e,s,n,t){const{href:a,page:f}=e;if(a||!f)return;const d=F(n.fs.cwd,t,s,f,n.fs),r=n.fs.exists(d),l=await n.isPathIgnored(d),g=(r?"The page is ignored: ":"Can't resolve page: ")+f;(!r||l)&&S.reportBrokenLink({type:"BROKEN_LINK",brokenLinkType:"LINK",title:e.label||e.group||"",link:d,rawLink:f,message:g,sourceFileRelativePath:s.navFile,sourceFileLocation:{line:0}})}function F(e,s,n,t="",a){if(n.ref!==void 0)return w(v.relative(e,v.resolve(e,v.join(n.ref,t))));if(t.startsWith("/")){const f=B(s)?q(s):void 0,d=f?`${a.localizationFolder}/${f}/`:"",r=t.slice(1);return B(r)?r:d+w(r)}return w(v.relative(e,v.resolve(e,s,t)))}function Z(e,s){const n={};for(const r of e){const l=v.dirname(r.relativePathFromDirectory),g=r.version?M+r.version:"",i=v.basename(r.relativePathFromDirectory)+g;d(l,r,i)}const t=f(a(n));return T(t,s);function a(r){const l=[];for(const g of Object.keys(r)){const i=r[g];l.push({...i,items:i.items?a(i.items):void 0})}return l}function f(r){return r.sort((l,g)=>{const i=typeof l=="string"?l.toLowerCase():l.relativePathFromDirectory||l.label||"",u=typeof g=="string"?g.toLowerCase():g.relativePathFromDirectory||g.label||"";return i.startsWith("index.")?-1:u.startsWith("index.")?1:i.localeCompare(u,void 0,{numeric:!0})})}function d(r,l,g){r==="."&&(r="");const i=r.split("/").filter(Boolean);let u=n;for(const o of i){const h=o+(l?.version||"");u[h]||(u[h]={type:"group",version:l?.version,label:o,isDefault:l?.isDefault,versionFolderId:l.versionFolderId,items:{}}),u=u[h].items}u[g]=l}}function T(e,s){return e.flatMap(n=>n?.sidebarItems?s.groupCustomSidebars?{...n,items:n.sidebarItems,sidebarItems:void 0,relativePathFromDirectory:void 0}:n.sidebarItems:{...n,items:n.items?T(n.items,s):void 0})}function k(e,s,n,t){return e.map(a=>({...a,...s,[p]:a[p]||n,[b]:a[b]||t,items:a.type==="group"&&a.items?k(a.items,s,n):a.items}))}function ee(e){return e.replace(/@[^\/]+\//,"")}async function Pe(e,s,n,t){const a=(e?.items||[]).filter(r=>r.linkedSidebars?.length).map(r=>({...r,items:void 0})),f=await D(a,s.contentDir,s,n,t),d=new Map;for(const r of f)if(r.linkedSidebars?.length)for(const l of r.linkedSidebars){if(d.has(l)){A.warn(`Only one navbar item can belong to sidebar. "${l}" on 'linkedSidebars' property on "${r.label}" will be ignored.`);continue}d.set(l,{label:r.label,link:r.link})}return d}export{Pe as collectItemsLinkedToSidebars,N as normalizeItems,K as resolveItem,I as resolveItems,D as resolveLinksFromConfig};
1
+ import*as v from"path";import{REDOCLY_ROUTE_RBAC as b,REDOCLY_TEAMS_RBAC as p}from"@redocly/config";import{VERSION_SEPARATOR as M}from"../constants/common.js";import{DEFAULT_LOCALE_PLACEHOLDER as _}from"../../constants/common.js";import{removeTrailingSlash as j}from"../../utils/url/remove-trailing-slash.js";import{isPrimitive as W}from"../../utils/guards/is-primitive.js";import{slash as w}from"../../utils/path/slash.js";import{isAbsoluteUrl as z}from"../../utils/url/is-absolute-url.js";import{normalizeRouteSlug as U}from"../../utils/path/normalize-route-slug.js";import{parsePathVersions as R}from"../../utils/path/parse-path-versions.js";import{logger as A}from"../tools/notifiers/logger.js";import{reporter as S}from"../tools/notifiers/reporter.js";import{shaDirPathShort as C}from"../utils/crypto/sha-dir-path-short.js";import{copyStaticFile as H,FileNotFoundError as E}from"../utils/fs.js";import{isIconPath as J,resolveAssetPath as Y}from"../utils/index.js";import{resolveSrcSet as G}from"../utils/resolve-src-set.js";import{isL10nPath as B}from"../fs/utils/is-l10n-path.js";import{getLocaleFromRelativePath as q}from"../fs/utils/get-locale-from-relative-path.js";async function D(e,s,n,t,a){if(!e)return;const f={...a,ignoreCustomSidebar:!0};if(W(e)){if(typeof e=="string"){const r=F(n.contentDir,s,a,e,t.fs);if(t.fs.exists(r)){if(n.getRouteByFsPath(r))return K({page:e},s,n,t,f);{const g=t.fs.getFileInfo(r);return g?H(n.contentDir,g.realRelativePath,n.outdir):void 0}}}return e}return Array.isArray(e)?I(N(e),s,n,t,f):Object.fromEntries(await Promise.all(Object.entries(e).map(async([r,l])=>[r,await D(l,s,n,t,a)])))}function N(e){return e.map(s=>({...s,items:s.items?N(s.items):void 0}))}async function I(e,s,n,t,a){if(Array.isArray(e))return(await Promise.all(e.map(f=>K(f,s,n,t,a)))).flatMap(f=>f)}async function P(e,s,n,t){let a,f;if(typeof e.icon=="object"){if("srcSet"in e.icon)try{a=await G(e.icon.srcSet,n.fs,{fromFileRelativePath:t.navFile,contentDir:s.contentDir,outdir:s.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`):await S.panicOnBuild(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`)}}else if(typeof e.icon=="string")if(z(e.icon)||J(e.icon))try{f=await Y(e.icon,n.fs,{fromFileRelativePath:t.navFile,contentDir:s.contentDir,outdir:s.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon" from ${t.navFile}: file ${f} does not exist`):await S.panicOnBuild(`Cannot resolve "item.icon" from ${t.navFile}: ${d.message}`)}else f=e.icon;return{icon:f,srcSet:a}}function Q(e){return s=>{if(s.type==="link"||s.type==="group"){const n=s.metadata;return n?Object.entries(e).every(([t,a])=>Array.isArray(a)?a.some(f=>n[t]===f):n[t]===a):!1}return!0}}async function K(e,s,n,t,a){if(e?.directory&&e.items?.length)return{type:"error",label:`Can't have both "directory" and "items" in the nav item: ${JSON.stringify(e)}`};e?.directory&&e?.group&&(e={...e,directory:void 0,items:[{directory:e.directory}]});const f=e?.directory?F(n.contentDir,s,a,e.directory,t.fs):void 0;if(f!==void 0){const i=f===""?"":f+"/",{locale:u,ignoredRoutes:o}=a;let h=n.getAllRoutesForLocale(u).filter(c=>!o?.has(c.baseSlug||"")&&c.fsPath.startsWith(i)&&!c.excludeFromSidebar).sort((c,y)=>c.fsPath===y.fsPath?0:c.baseSlug.localeCompare(y.baseSlug,void 0,{numeric:!0}));const m=e.includeByMetadata?Q(e.includeByMetadata):Boolean;if(!h.length)return[];const $=(await Promise.all(h.map(async c=>{const y=c.versions?.find(x=>x.active),L=y&&{version:y.version,isDefault:y.default,versionFolderId:y.folderId},V=ee(v.posix.relative(f,c.fsPath)),O=c.getSidebar?.(c);return!a.ignoreCustomSidebar&&O?.length?{type:"group",fsPath:c.fsPath,metadata:c.metadata,link:c.slug,routeSlug:c.slug,label:await c.getNavText?.()||c.slug,[p]:c[p],[b]:c[b],sidebarItems:k(O,L,c[p],c[b]),...L,relativePathFromDirectory:v.relative(f,c.fsPath).replace(/@[^/]+\//,"")}:{type:"link",fsPath:c.fsPath,metadata:c.metadata,[p]:c[p],[b]:c[b],label:await c.getNavText?.()||c.slug,link:c.slug,routeSlug:c.slug,...L,relativePathFromDirectory:V}}))).filter(m);return e.flatten?T($,a):Z($,a)}let d=typeof e=="string"?e:e?.page,r,l;if(d){if(d.includes("#")){const[i,u]=d.split("#");u?l=u:A.warn(`Invalid heading anchor format in sidebar: "${u}". Heading anchors should contain only alphanumeric characters, hyphens, and underscores.`),d=i}if(d=j(d),a.locale&&a.locale!==_){const i=U(v.join(a.locale.toLowerCase(),d));r=n.getRouteBySlug(i)}r||(r=n.getRouteBySlug(d)),r||(r=n.getRouteByFsPath(F(n.contentDir,s,a,d,t.fs)))}const g=r?.metadata;if(e?.$ref){let i=F(n.contentDir,s,a,e.$ref,t.fs);if(a.ref!==void 0&&(i=v.posix.join(a.ref,e.$ref)),!t.fs.exists(i))return await S.panicOnBuildContentError(`Failed to load ${i} file. Make sure sidebar $ref path is correct.`),[];const u=await t.cache.load(i,"yaml"),o=v.dirname(i);return await I(u.data,s,n,t,{...a,navFile:i,ref:o})||[]}if(!r){const i=a.excludedFromLinkCheckerPatterns?.catalog.some(o=>o.test(d||"")),u=a.excludedFromLinkCheckerPatterns?.apiFunctions.some(o=>o.test(d||e.href||""));(u||i)&&(e={...e,href:d||e.href,page:void 0,target:e.target??(u?"_blank":void 0)}),await X(e,a,t,s)}if(r&&!e.disconnect&&!a.ignoreCustomSidebar&&(l||r?.getSidebar)){const i=r.versions?.find(o=>o.active),u=i&&{version:i.version,isDefault:i.default,versionFolderId:i.folderId};if(l){let o=[];if(r.getSidebar&&(o=r.getSidebar(r,{...e,...await P(e,n,t,a)}),o&&o.length>0)){const h=o[0];h&&h.link&&(h.link=`${h.link}#${l}`)}if(!o||o.length===0){let h=e.group||e.label||l;o=[{type:"link",fsPath:r.fsPath,metadata:r.metadata,link:`${r.slug}#${l}`,routeSlug:r.slug,label:h,labelTranslationKey:e.labelTranslationKey,...await P(e,n,t,a),[p]:r[p],[b]:{slug:r.slug,fsPath:r.fsPath},...u}]}return k(o,u,r[p],r[b])}if(r?.getSidebar){const o=r.getSidebar(r,{...e,...await P(e,n,t,a)}),h=e.group&&o?.[0]?.routeSlug===r.slug?o.slice(1):o;return k(a.groupCustomSidebars||e.group?[{type:"group",fsPath:r.fsPath,link:r.slug,routeSlug:r.slug,label:e.group||e.label||await r.getNavText?.()||r.slug,items:h,labelTranslationKey:e.labelTranslationKey,metadata:g,badges:e.badges,...await P(e,n,t,a)}]:o,u,r[p],r[b])}}if(r&&!e.group){const i=l?`${r.slug}#${l}`:r.slug;let u=e.label||l||"";u||(u=await r.getNavText?.()||r.slug);const o=r.versions?.find(m=>m.active),h=o&&{version:o.version,isDefault:o.default,versionFolderId:o.folderId};return{type:"link",fsPath:r.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,[p]:e?.rbac||r[p],[b]:{slug:r.slug,fsPath:r.fsPath},label:u,labelTranslationKey:e.labelTranslationKey,link:i,items:await I(e.items,s,n,t,a),separatorLine:e.separatorLine,linePosition:e.linePosition,routeSlug:e.disconnect?void 0:i,external:e.external,target:e.target,badges:e.badges,...await P(e,n,t,a),...h,additionalProps:e.additionalProps}}else if(e?.group){const i=r?.versions?.find(o=>o.active),u=R(a?.navFile);return{type:"group",fsPath:r?.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,version:i?.version||u?.versionName,versionFolderId:i?.folderId||(u?.versionFolderPath?C(u.versionFolderPath):void 0),label:e.group,link:r?.slug,routeSlug:r?.slug,badges:e.badges,labelTranslationKey:e.groupTranslationKey,items:await I(e.items,s,n,t,a),expanded:e.expanded?e.expanded.toString():void 0,selectFirstItemOnExpand:e.selectFirstItemOnExpand,menuStyle:e.menuStyle,separatorLine:e.separatorLine,linePosition:e.linePosition,...await P(e,n,t,a),[p]:e?.rbac,[b]:{slug:r?.slug||"",fsPath:r?.fsPath||""},additionalProps:e.additionalProps}}else{if(e?.href||e?.label)return{type:"link",metadata:g,link:e.href||"",label:e?.label||e?.href||"",labelTranslationKey:e.labelTranslationKey,external:e.external,target:e.target,separatorLine:e.separatorLine,linePosition:e.linePosition,badges:e.badges,...await P(e,n,t,a),[p]:e?.rbac,additionalProps:e.additionalProps};if(e?.separator!=null||e?.separatorLine!=null){const i=R(a?.navFile);return{type:"separator",metadata:g,version:i?.versionName,versionFolderId:i?.versionFolderPath?C(i.versionFolderPath):void 0,label:e.separator,labelTranslationKey:e.separatorTranslationKey,separatorLine:e.separatorLine,linePosition:e.linePosition,...await P(e,n,t,a),[p]:e?.rbac,additionalProps:e.additionalProps}}else{const i=R(a?.navFile);return{type:"error",label:`Can't resolve page: ${JSON.stringify(e)}`,version:i?.versionName,versionFolderId:i?.versionFolderPath?C(i.versionFolderPath):void 0}}}}async function X(e,s,n,t){const{href:a,page:f}=e;if(a||!f)return;const d=F(n.fs.cwd,t,s,f,n.fs),r=n.fs.exists(d),l=await n.isPathIgnored(d),g=(r?"The page is ignored: ":"Can't resolve page: ")+f;(!r||l)&&S.reportBrokenLink({type:"BROKEN_LINK",brokenLinkType:"LINK",title:e.label||e.group||"",link:d,rawLink:f,message:g,sourceFileRelativePath:s.navFile,sourceFileLocation:{line:0}})}function F(e,s,n,t="",a){if(n.ref!==void 0)return w(v.relative(e,v.resolve(e,v.join(n.ref,t))));if(t.startsWith("/")){const f=B(s)?q(s):void 0,d=f?`${a.localizationFolder}/${f}/`:"",r=t.slice(1);return B(r)?r:d+w(r)}return w(v.relative(e,v.resolve(e,s,t)))}function Z(e,s){const n={};for(const r of e){const l=v.dirname(r.relativePathFromDirectory),g=r.version?M+r.version:"",i=v.basename(r.relativePathFromDirectory)+g;d(l,r,i)}const t=f(a(n));return T(t,s);function a(r){const l=[];for(const g of Object.keys(r)){const i=r[g];l.push({...i,items:i.items?a(i.items):void 0})}return l}function f(r){return r.sort((l,g)=>{const i=typeof l=="string"?l.toLowerCase():l.relativePathFromDirectory||l.label||"",u=typeof g=="string"?g.toLowerCase():g.relativePathFromDirectory||g.label||"";return i.startsWith("index.")?-1:u.startsWith("index.")?1:i.localeCompare(u,void 0,{numeric:!0})})}function d(r,l,g){r==="."&&(r="");const i=r.split("/").filter(Boolean);let u=n;for(const o of i){const h=o+(l?.version||"");u[h]||(u[h]={type:"group",version:l?.version,label:o,isDefault:l?.isDefault,versionFolderId:l.versionFolderId,items:{}}),u=u[h].items}u[g]=l}}function T(e,s){return e.flatMap(n=>n?.sidebarItems?s.groupCustomSidebars?{...n,items:n.sidebarItems,sidebarItems:void 0,relativePathFromDirectory:void 0}:n.sidebarItems:{...n,items:n.items?T(n.items,s):void 0})}function k(e,s,n,t){return e.map(a=>({...a,...s,[p]:a[p]||n,[b]:a[b]||t,items:a.type==="group"&&a.items?k(a.items,s,n):a.items}))}function ee(e){return e.replace(/@[^/]+\//,"")}async function Pe(e,s,n,t){const a=(e?.items||[]).filter(r=>r.linkedSidebars?.length).map(r=>({...r,items:void 0})),f=await D(a,s.contentDir,s,n,t),d=new Map;for(const r of f)if(r.linkedSidebars?.length)for(const l of r.linkedSidebars){if(d.has(l)){A.warn(`Only one navbar item can belong to sidebar. "${l}" on 'linkedSidebars' property on "${r.label}" will be ignored.`);continue}d.set(l,{label:r.label,link:r.link})}return d}export{Pe as collectItemsLinkedToSidebars,N as normalizeItems,K as resolveItem,I as resolveItems,D as resolveLinksFromConfig};
@@ -1 +1 @@
1
- import p from"node:path";import{existsSync as O}from"node:fs";import{writeFile as h,access as y,rm as v,constants as b}from"fs/promises";import{REDOCLY_TEAMS_RBAC as N}from"@redocly/config";import{DEFAULT_LOCALE_PLACEHOLDER as P}from"../../../../constants/common.js";import{AI_INDEX_EXPORT_FOLDER as X}from"../../../constants/plugins/search.js";import{LLMS_TXT_FILE_NAME as $}from"../../../constants/common.js";import{envConfig as j}from"../../../config/env-config.js";import{logger as n}from"../../../tools/notifiers/logger.js";import{shaHexShort as B}from"../../../utils/crypto/sha-hex-short.js";import{promiseMapLimit as D}from"../../../utils/async/promise-map-limit.js";import{getLlmsTxtMdPathBySlug as G}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{validateLLMsTxtConfig as H,generateLLMsTxt as k}from"../llmstxt/index.js";import{buildPageLlmsTxtMd as J}from"../llmstxt/build-page-llms-txt-md.js";import{getLlmsTxtExcludeMatcher as K}from"../llmstxt/get-llms-txt-exclude-matcher.js";import{isIncludedInLlmsTxt as U}from"../llmstxt/is-included-in-llms-txt.js";import{getRouteStaticData as V}from"../../../utils/get-route-static-data.js";import{getRouteAiDocumentsStore as W}from"../../../utils/get-route-ai-documents-store.js";import{ensureDir as R}from"../../../utils/fs.js";import{extractTeamsFromScopeItems as Y,getRbacTeamsListForResource as q}from"../../../utils/rbac.js";const _=20,S="llms.txt:";async function ht(t,m,{embeddingsEnabled:e,writeLlmstxtFiles:l}){const c=p.join(t.outdir,$);if(!l){try{await y(c,b.W_OK),await v(c)}catch{}if(!e)return}e&&n.info("Preparing semantic documents..."),l&&n.info(`${S} Generating llms.txt files...`);const g=n.startTiming(),r=n.startTiming(),s=t.getGlobalConfig("seo"),f=s?.llmstxt,T=p.resolve(t.outdir,X),u=t.getConfig(),A=u.access?.rbac??{},M=t.getAllRoutes(),C=u?.l10n?.defaultLocale||u?.i18n?.defaultLocale||P,E=K(s),L=[];if(await D(M,_,async o=>{if(o.excludeFromSearch)return;const w=await V(o,t),x=await W(o,w,m,t);if(x){if(l)for(const i of await x.getLLMsTxts()){if(E(i.fsPath))continue;const d=G(i.slug),a=p.join(t.outdir,d);R(a),await h(a,J(i,s)),U(i,o,u)&&L.push(i)}if(e&&j.isBuildMode){const i=await x.getSearchDocuments();if(!i.length)return;const d=z(o,A),a=o.versions?.find(({active:F})=>F),I=a&&{folder:a.folderId,label:a.label,default:a.default};await Q(i,o.fsPath,d,T,C,I)}}}),e&&n.infoTime(r,"Semantic search documents prepared"),L.length)try{H(f);const o=await k(L,f,{title:s?.title,description:s?.description},m);R(c),await h(c,o),n.infoTime(g,`${S} files generated`)}catch(o){n.error(`${S} Failed to generate llms.txt file. ${o.message}`)}}function z(t,m){const e=Y(t?.[N]);return e?.length?e:q(t,m)}async function Q(t,m,e,l,c,g){await D(t,_,async r=>{const s=`${B(m+r.content)}.json`,f=R(p.join(l,s)),T=r.locale===P?c:r.locale;O(f)||await h(f,JSON.stringify({...r,rbacTeams:r.rbacTeams??e??[],version:g,locale:T}),"utf-8")})}export{ht as prepareSemanticDocuments};
1
+ import p from"node:path";import{existsSync as O}from"node:fs";import{writeFile as h,access as y,rm as v,constants as b}from"fs/promises";import{REDOCLY_TEAMS_RBAC as N}from"@redocly/config";import{DEFAULT_LOCALE_PLACEHOLDER as P}from"../../../../constants/common.js";import{AI_INDEX_EXPORT_FOLDER as X}from"../../../constants/plugins/search.js";import{LLMS_TXT_FILE_NAME as $}from"../../../constants/common.js";import{envConfig as j}from"../../../config/env-config.js";import{logger as n}from"../../../tools/notifiers/logger.js";import{shaHexShort as B}from"../../../utils/crypto/sha-hex-short.js";import{promiseMapLimit as D}from"../../../utils/async/promise-map-limit.js";import{getLlmsTxtMdPathBySlug as G}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{validateLLMsTxtConfig as U,generateLLMsTxt as H}from"../llmstxt/index.js";import{buildPageLlmsTxtMd as k}from"../llmstxt/build-page-llms-txt-md.js";import{getLlmsTxtExcludeMatcher as J}from"../llmstxt/get-llms-txt-exclude-matcher.js";import{isIncludedInLlmsTxt as K}from"../llmstxt/is-included-in-llms-txt.js";import{getRouteStaticData as V}from"../../../utils/get-route-static-data.js";import{getRouteAiDocumentsStore as W}from"../../../utils/get-route-ai-documents-store.js";import{ensureDir as R}from"../../../utils/fs.js";import{extractTeamsFromScopeItems as Y,getRbacTeamsListForResource as q}from"../../../utils/rbac.js";const _=20,S="llms.txt:";async function ht(t,m,{embeddingsEnabled:e,writeLlmstxtFiles:l}){const c=p.join(t.outdir,$);if(!l){try{await y(c,b.W_OK),await v(c)}catch{}if(!e)return}e&&n.info("Preparing semantic documents..."),l&&n.info(`${S} Generating llms.txt files...`);const g=n.startTiming(),s=n.startTiming(),i=t.getGlobalConfig("seo"),f=i?.llmstxt,T=p.resolve(t.outdir,X),u=t.getConfig(),A=u.access?.rbac??{},M=t.getAllRoutes(),C=u?.l10n?.defaultLocale||u?.i18n?.defaultLocale||P,E=J(i),L=[];if(await D(M,_,async o=>{if(o.excludeFromSearch)return;const w=await V(o,t),x=await W(o,w,m,t);if(x){if(l)for(const r of await x.getLLMsTxts()){if(E(r.fsPath))continue;const d=G(r.slug),a=p.join(t.outdir,d);R(a),await h(a,k(r,i)),K(r,o,u)&&L.push(r)}if(e&&j.isBuildMode){const r=await x.getSearchDocuments();if(!r.length)return;const d=z(o,A),a=o.versions?.find(({active:F})=>F),I=a&&{folder:a.folderId,label:a.label,default:a.default};await Q(r,o.fsPath,d,T,C,I)}}}),e&&n.infoTime(s,"Semantic search documents prepared"),L.length)try{U(f);const o=await H(L,f,{title:i?.title,description:i?.description,siteUrl:i?.siteUrl},m);R(c),await h(c,o),n.infoTime(g,`${S} files generated`)}catch(o){n.error(`${S} Failed to generate llms.txt file. ${o.message}`)}}function z(t,m){const e=Y(t?.[N]);return e?.length?e:q(t,m)}async function Q(t,m,e,l,c,g){await D(t,_,async s=>{const i=`${B(m+s.content)}.json`,f=R(p.join(l,i)),T=s.locale===P?c:s.locale;O(f)||await h(f,JSON.stringify({...s,rbacTeams:s.rbacTeams??e??[],version:g,locale:T}),"utf-8")})}export{ht as prepareSemanticDocuments};
@@ -1 +1 @@
1
- import{readFile as R,readdir as E,appendFile as F}from"node:fs/promises";import m from"node:path";import{existsSync as I}from"node:fs";import{SEARCH_GROUP_FACET_FIELD as S,SEARCH_PRODUCT_FIELD as w,SEARCH_VERSION_FIELD as C}from"../../../../../constants/common.js";import{SEARCH_INDEX_FIELDS as $,SEARCH_DATA_EXPORT_FOLDER as g,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as O}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as v}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as j}from"../../../../config/env-config.js";import{ensureDir as h}from"../../../../utils/index.js";import{FlexSearchIndex as N}from"./search-index.js";class B{#e=new Map;#s;#t=0;async initIndexSchema(e){this.#s={document:{id:"id",tag:"tags",index:$},worker:!1,tokenize:"forward",context:{depth:2,resolution:9}}}#o(e,o){const i=this.#e.get(e);if(i)return i.find(t=>t.id===o)}#i(e,o){let i=this.#o(e,o);return i||(i=new N(o,this.#s),this.#e.set(e,[...this.#e.get(e)??[],i])),i}async addDocuments(e,o){if(e.length){const{group:i,locale:t,outDir:c}=o,n=this.#i(t,i);for(const r of e)n.add(r),j.isBuildMode&&(this.#t++,this.#t>=O&&await this.exportDocuments(c))}}getIndexesSize(){return this.#e.size}async search(e,o,i){return await v("com.redocly.search.performed",async t=>{t?.setAttribute("id","searchQueryId"),t?.setAttribute("object","searchQuery"),t?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:c,locale:n,filter:r,loadMore:a}=e,l=this.#e.get(n)??[];let f=[],d="",x=["v:default"];if(t?.setAttribute("locale",n),r)for(const s of r)s.field===S?f=[...f,...s.values]:s.field===w?d=`p:${s.values[0]}`:s.field===C&&s.values.length&&s.values.length===2&&(x=[...x,`v:${s.values[0]}:${s.values[1]}`]);const D={auth:o,query:c,offset:0,SEARCH_GROUP_FACET_FIELD:S,product:d,versions:x},p=[];let u={facets:{},documents:{}};if(a){const s=this.#o(n,a.groupKey);s&&p.push(s.search({...D,offset:a.offset}))}else for(const s of l)f&&f.length?f.includes(s.id)&&p.push(s.search(D)):p.push(s.search(D));const A=await Promise.all(p);for(const s of A){u.documents={...u.documents,...s.documents};for(const[y,_]of Object.entries(s.facets))u.facets[y]?u.facets[y]=[...u.facets[y],..._]:u.facets[y]=_}return u})}async exportDocuments(e){const o='{"documents":[',i=h(m.join(e,g));for(const[t,c]of this.#e){const n=h(m.join(i,t));for(const r of c){const a=Array.from(r.documents);if(a.length===0)continue;const l=h(m.join(n,`${r.id}.json`)),f=!I(l),d=JSON.stringify(a).substring(1).slice(0,-1),x=f?o+d:","+d;await F(l,x,{encoding:"utf8"}),r.clearDocuments()}}this.#t=0}async exportIndexes(e){const o=h(m.join(e,g));for(const[i,t]of this.#e){const c=h(m.join(o,i));for(const n of t){const r=h(m.join(c,`${n.id}.json`)),a={};await n.export((f,d)=>{a[f]=d});const l=`],"index":${JSON.stringify(a)}}`;await F(r,l,{encoding:"utf8"})}}this.#e.clear()}async import(e){const o=`${e}/${g}`;if(!I(o))return;const i=await E(o);for(const t of i){const c=await E(`${o}/${t}`);for(const n of c){const r=`${o}/${t}/${n}`,a=JSON.parse(await R(r,"utf-8")),l=m.parse(n).name;await this.#i(t,l).import(a)}}}countFacets(e,o,i){const t={};for(const[c,n]of i)t[c]=n.values.map(r=>({value:r,count:0,isCounterVisible:!1}));return t}cleanupFacetValues(e){}}export{B as FlexSearch};
1
+ import{readFile as R,readdir as F,appendFile as I}from"node:fs/promises";import h from"node:path";import{existsSync as C}from"node:fs";import{SEARCH_GROUP_FACET_FIELD as S,SEARCH_PRODUCT_FIELD as v,SEARCH_VERSION_FIELD as w}from"../../../../../constants/common.js";import{SEARCH_INDEX_FIELDS as $,SEARCH_DATA_EXPORT_FOLDER as D,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as O}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as j}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as N}from"../../../../config/env-config.js";import{ensureDir as m}from"../../../../utils/index.js";import{FlexSearchIndex as b}from"./search-index.js";class J{#e=new Map;#s;#t=0;async initIndexSchema(t){this.#s={document:{id:"id",tag:"tags",index:$},worker:!1,tokenize:"forward",context:{depth:2,resolution:9}}}#o(t,o){const i=this.#e.get(t);if(i)return i.find(s=>s.id===o)}#i(t,o){let i=this.#o(t,o);return i||(i=new b(o,this.#s),this.#e.set(t,[...this.#e.get(t)??[],i])),i}async addDocuments(t,o){if(t.length){const{group:i,locale:s,outDir:c}=o,n=this.#i(s,i);for(const r of t)n.add(r),N.isBuildMode&&(this.#t++,this.#t>=O&&await this.exportDocuments(c))}}getIndexesSize(){return this.#e.size}async search(t,o,i){return await j("com.redocly.search.performed",async s=>{s?.setAttribute("id","searchQueryId"),s?.setAttribute("object","searchQuery"),s?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:c,locale:n,filter:r,loadMore:a}=t,l=this.#e.get(n)??[];let f=[],d="",x=["v:default"];if(s?.setAttribute("locale",n),r)for(const e of r)e.field===S?f=[...f,...e.values]:e.field===v?d=`p:${e.values[0]}`:e.field===w&&e.values.length&&e.values.length===2&&(x=[...x,`v:${e.values[0]}:${e.values[1]}`]);const g={auth:o,query:c,offset:0,SEARCH_GROUP_FACET_FIELD:S,product:d,versions:x},p=[];let u={facets:{},documents:{}};if(a){const e=this.#o(n,a.groupKey);e&&p.push(e.search({...g,offset:a.offset}))}else for(const e of l)f&&f.length?f.includes(e.id)&&p.push(e.search(g)):p.push(e.search(g));const _=await Promise.all(p),A=_.some(e=>e.hasCoveringHit);for(const e of _)if(!(A&&!e.hasCoveringHit)){u.documents={...u.documents,...e.documents};for(const[y,E]of Object.entries(e.facets))u.facets[y]?u.facets[y]=[...u.facets[y],...E]:u.facets[y]=E}return u})}async exportDocuments(t){const o='{"documents":[',i=m(h.join(t,D));for(const[s,c]of this.#e){const n=m(h.join(i,s));for(const r of c){const a=Array.from(r.documents);if(a.length===0)continue;const l=m(h.join(n,`${r.id}.json`)),f=!C(l),d=JSON.stringify(a).substring(1).slice(0,-1),x=f?o+d:","+d;await I(l,x,{encoding:"utf8"}),r.clearDocuments()}}this.#t=0}async exportIndexes(t){const o=m(h.join(t,D));for(const[i,s]of this.#e){const c=m(h.join(o,i));for(const n of s){const r=m(h.join(c,`${n.id}.json`)),a={};await n.export((f,d)=>{a[f]=d});const l=`],"index":${JSON.stringify(a)}}`;await I(r,l,{encoding:"utf8"})}}this.#e.clear()}async import(t){const o=`${t}/${D}`;if(!C(o))return;const i=await F(o);for(const s of i){const c=await F(`${o}/${s}`);for(const n of c){const r=`${o}/${s}/${n}`,a=JSON.parse(await R(r,"utf-8")),l=h.parse(n).name;await this.#i(s,l).import(a)}}}countFacets(t,o,i){const s={};for(const[c,n]of i)s[c]=n.values.map(r=>({value:r,count:0,isCounterVisible:!1}));return s}cleanupFacetValues(t){}}export{J as FlexSearch};
@@ -1,16 +1,20 @@
1
1
  import type { ExportHandler, IndexOptionsForDocumentSearch } from 'flexsearch';
2
- import type { FlexSearchData, SearchIndex, SearchParameters, SearchDocument } from '../../../../types';
2
+ import type { FlexSearchData, IndexedSearchDocument, SearchIndex, SearchParameters, SearchDocument } from '../../../../types';
3
3
  import type { SearchResponse } from '../../../../../types/index.js';
4
+ /** One group's results plus whether any of them holds every query word in a single value. */
5
+ export type IndexSearchResponse = SearchResponse & {
6
+ hasCoveringHit: boolean;
7
+ };
4
8
  export declare class FlexSearchIndex implements SearchIndex {
5
9
  #private;
6
10
  id: string;
7
11
  constructor(id: string, indexConfig: IndexOptionsForDocumentSearch<string>);
8
- get documents(): MapIterator<[number, SearchDocument]>;
12
+ get documents(): MapIterator<[number, IndexedSearchDocument]>;
9
13
  get documentsCount(): number;
10
14
  clearDocuments(): void;
11
- export(handler: ExportHandler<string | SearchDocument>): Promise<void>;
15
+ export(handler: ExportHandler<string | IndexedSearchDocument>): Promise<void>;
12
16
  import(searchData: FlexSearchData): Promise<void>;
13
17
  add(document: SearchDocument): void;
14
- search(params: SearchParameters): Promise<SearchResponse>;
18
+ search(params: SearchParameters): Promise<IndexSearchResponse>;
15
19
  }
16
20
  //# sourceMappingURL=search-index.d.ts.map
@@ -1 +1 @@
1
- import S from"flexsearch";import{REDOCLY_TEAMS_RBAC as I}from"@redocly/config";import{DISABLE_DEEP_LINK_IF_FIELDS_EXIST as w,SEARCH_LIMIT as y}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as A}from"../../../../telemetry/helpers/trace-step.js";import{highlightTextForSearch as u}from"../../../../utils/search/highlight-text-for-search.js";class C{#e;#t=new Map;#s=1;id;constructor(e,t){this.id=e,this.#e=new S.Document(t)}get documents(){return this.#t.entries()}get documentsCount(){return this.#s}clearDocuments(){this.#t.clear()}async export(e){await this.#e.export(e)}async import(e){const{documents:t,index:s}=e;this.#t.clear();for(const[r,i]of t)this.#t.set(r,i);for(const[r,i]of Object.entries(s))await this.#e.import(r,i)}add(e){e.tags||(e.tags=[]),this.#t.set(this.#s,e[I]?e:{...e,[I]:void 0}),this.#e.add(this.#s,e),this.#s++}async search(e){return await A("com.redocly.search.performed",async t=>{t?.setAttribute("id","searchQueryId"),t?.setAttribute("object","searchQuery"),t?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:s,offset:r,auth:i,product:o,versions:a}=e,n=[],c=i.teams,l={limit:y,offset:r};let m=new Map,d=await this.#e.searchAsync(s,{...l,tag:c});if(o){const h=await this.#e.searchAsync(s,{...l,tag:o});d=this.#i(d,h),t?.setAttribute("product",o)}if(a&&a.length){const h=await this.#e.searchAsync(s,{...l,tag:a});d=this.#i(d,h),t?.setAttribute("versions",a.join(","))}for(const h of d)for(const p of h.result){const f=m.get(p)?.fields||[];m.set(p,{fields:h.field?[...f,h.field]:[...f]})}let g=0;for(const[h,p]of m.entries()){if(g>=y)break;const f=this.#t.get(h);f&&!this.#n(f,a)&&(n.push({document:this.#o(f,s,p.fields),highlight:this.#a(f,s,p.fields)}),g++)}return{documents:{[this.id]:n},facets:{}}})}#o(e,t,s){return this.#c(e,t,s)}#a(e,t,s){const r={};let i=!1;for(const o of s)if(o==="path")r.path=e.path?e.path.map(a=>u(t,a)):[];else if(o.includes("parameters")){if(!i){const a=o.split(":")[1],n=e.parameters?.find(c=>{const l=c[a];return this.#r(t,typeof l=="boolean"?l.toString():l)});n&&(r.parameters=[{name:u(t,n.name),description:u(t,n.description),place:u(t,n.place),path:n.path?n.path.map(c=>u(t,c)):[]}],i=!0)}}else r[o]=u(t,e[o]);return r}#r=(e,t)=>{if(t){const s=e.split(/\s+/g);return Array.isArray(t)?t.some(r=>s.some(i=>r.toLowerCase().includes(i.toLowerCase()))):s.some(r=>t.toLowerCase().includes(r.toLowerCase()))}else return!1};#i(e,t){const s=[];for(const r of e){const i=[];for(const o of t)for(const a of r.result){const n=o.result.find(c=>c===a);n&&i.push(n)}i.length&&s.push({result:i,field:r.field})}return s}#n(e,t){return!!(t&&t.length&&e.isDefaultVersion&&e.versionFolderId&&e.version&&t.some(s=>s.includes(e.versionFolderId??""))&&!t.some(s=>s.includes(e.version??"")))}#c(e,t,s){for(const i of w)if(s.some(o=>o===i))return e;let r;for(const i of s)if(i.includes("parameters")){const o=i.split(":")[1],a=e.parameters?.find(n=>{const c=n[o];return this.#r(t,typeof c=="boolean"?c.toString():c)});if(a){r=a.deepLink;break}}if(r){const i=r.split("#")[1];return{...e,url:`${e.url}#${i}`}}return e}}export{C as FlexSearchIndex};
1
+ import L from"flexsearch";import{REDOCLY_TEAMS_RBAC as A}from"@redocly/config";import{DISABLE_DEEP_LINK_IF_FIELDS_EXIST as R,SEARCH_LIMIT as D}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as b}from"../../../../telemetry/helpers/trace-step.js";import{highlightTextForSearch as u}from"../../../../utils/search/highlight-text-for-search.js";function v(S){const{name:e,description:t,place:r,path:o,deepLink:s}=S,i=s?.indexOf("#")??-1;return{name:e,description:t,place:r,...o&&{path:o},...s&&{deepLink:i>=0?s.slice(i+1):s}}}class C{#e;#t=new Map;#s=1;id;constructor(e,t){this.id=e,this.#e=new L.Document(t)}get documents(){return this.#t.entries()}get documentsCount(){return this.#s}clearDocuments(){this.#t.clear()}async export(e){await this.#e.export(e)}async import(e){const{documents:t,index:r}=e;this.#t.clear();for(const[o,s]of t)this.#t.set(o,s);for(const[o,s]of Object.entries(r))await this.#e.import(o,s)}add(e){const t={...e,tags:e.tags??[],[A]:e[A],...e.parameters&&{parameters:e.parameters.map(v)}};this.#t.set(this.#s,t),this.#e.add(this.#s,t),this.#s++}async search(e){return await b("com.redocly.search.performed",async t=>{t?.setAttribute("id","searchQueryId"),t?.setAttribute("object","searchQuery"),t?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:r,offset:o,auth:s,product:i,versions:n}=e,c=[],l=s.teams,p={limit:D,offset:o};let h=new Map,m=await this.#e.searchAsync(r,{...p,tag:l});if(i){const a=await this.#e.searchAsync(r,{...p,tag:i});m=this.#a(m,a),t?.setAttribute("product",i)}if(n&&n.length){const a=await this.#e.searchAsync(r,{...p,tag:n});m=this.#a(m,a),t?.setAttribute("versions",n.join(","))}for(const a of m)for(const d of a.result){const f=h.get(d)?.fields||[];h.set(d,{fields:a.field?[...f,a.field]:[...f]})}const x=this.#r(r),g=[];for(const[a,d]of h.entries()){const f=this.#t.get(a);f&&!this.#h(f,n)&&g.push({document:f,fields:d.fields,score:this.#l(x,f)})}const I=g.filter(a=>a.score>=x.length),y=I.length?I:g;y.sort((a,d)=>d.score-a.score);for(const a of y.slice(0,D))c.push({document:this.#n(a.document,r,a.fields),highlight:this.#c(a.document,r,a.fields)});return{documents:{[this.id]:c},facets:{},hasCoveringHit:I.length>0}})}#n(e,t,r){return this.#f(e,t,r)}#c(e,t,r){const o={};let s=!1;for(const i of r)if(i==="path")o.path=e.path?e.path.map(n=>u(t,n)):[];else if(i.includes("parameters")){if(!s){const n=i.split(":")[1],c=this.#o(t,n,e.parameters);c&&(o.parameters=[{name:u(t,c.name),description:u(t,c.description),place:u(t,c.place),path:c.path?c.path.map(l=>u(t,l)):[]}],s=!0)}}else o[i]=u(t,e[i]);return o}#r(e){return[...new Set(e.toLowerCase().split(/\s+/g).filter(Boolean))]}#i(e,t){const r=t.toLowerCase().replace(/\s+/g," "),o=e.filter(i=>r.includes(i)).length;let s=0;for(let i=1;i<e.length;i++)r.includes(`${e[i-1]} ${e[i]}`)&&s++;return o+s/e.length}#l(e,t){let r=0;const o=s=>{if(s==null)return;const i=this.#i(e,String(s));i>r&&(r=i)};o(t.title),o(t.text),o(t.httpPath),o(t.path?.join(" "));for(const s of t.parameters??[])o([s.name,s.description,s.place].filter(Boolean).join(" "));return r}#o(e,t,r){const o=this.#r(e);let s,i=0;for(const n of r??[]){const c=n[t],l=(Array.isArray(c)?c:[c]).filter(h=>h!=null).map(h=>String(h)).join(" "),p=this.#i(o,l);p>i&&(i=p,s=n)}return s}#a(e,t){const r=[];for(const o of e){const s=[];for(const i of t)for(const n of o.result){const c=i.result.find(l=>l===n);c&&s.push(c)}s.length&&r.push({result:s,field:o.field})}return r}#h(e,t){return!!(t&&t.length&&e.isDefaultVersion&&e.versionFolderId&&e.version&&t.some(r=>r.includes(e.versionFolderId??""))&&!t.some(r=>r.includes(e.version??"")))}#f(e,t,r){for(const s of R)if(r.some(i=>i===s))return e;let o;for(const s of r)if(s.includes("parameters")){const i=s.split(":")[1],n=this.#o(t,i,e.parameters);if(n){o=n.deepLink;break}}return o?{...e,url:`${e.url}#${o}`}:e}}export{C as FlexSearchIndex};
@@ -1 +1 @@
1
- import p from"node:path";import{logger as f}from"../../../tools/notifiers/logger.js";import{promiseMapLimit as x}from"../../../utils/async/promise-map-limit.js";import{getLlmsTxtMdPathBySlug as L}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{getRouteStaticData as T}from"../../../utils/get-route-static-data.js";import{getRouteAiDocumentsStore as w}from"../../../utils/get-route-ai-documents-store.js";import{generateLLMsTxt as h,validateLLMsTxtConfig as M}from"./index.js";import{buildPageLlmsTxtMd as S}from"./build-page-llms-txt-md.js";import{getLlmsTxtExcludeMatcher as m}from"./get-llms-txt-exclude-matcher.js";import{isIncludedInLlmsTxt as R}from"./is-included-in-llms-txt.js";import{EntitlementsProvider as C}from"../../../entitlements/entitlements-provider.js";const A=20;function v(i,c){const a=new WeakMap,l=()=>{const t=i.getGlobalConfig("seo");return C.instance().canAccessFeature("seo")===!0&&t?.llmstxt?.hide!==!0},d=async t=>{if(t.excludeFromSearch)return[];const e=i,n=await T(t,e);return await(await w(t,n,c,e))?.getLLMsTxts()??[]},u=t=>{const e=a.get(t);if(e)return e;const n=d(t).catch(o=>(a.delete(t),f.warn(`Failed to generate Markdown for LLMs of "${t.slug}". ${o}`),[]));return a.set(t,n),n},g=t=>{const e=t==="/index.html.md"?"/":t.replace(/\.md$/,"");for(let n=e;n.startsWith("/");){const o=i.getRouteBySlug(n,{followRedirect:!1});if(o?.getAiDocumentsStore)return o;const r=p.posix.dirname(n);if(r===n)return;n=r}};return{async getPageMarkdown(t){if(!l())return;const e=g(t);if(!e)return;const n=i.getGlobalConfig("seo"),o=m(n),r=(await u(e)).find(s=>L(s.slug)===t&&!o(s.fsPath));if(r)return{route:e,content:S(r,n)}},async getLlmsTxt(){if(!l())return;const t=i.getConfig(),e=i.getGlobalConfig("seo"),n=m(e),o=[];if(await x(i.getAllRoutes(),A,async r=>{for(const s of await u(r))!n(s.fsPath)&&R(s,r,t)&&o.push(s)}),!!o.length)try{return M(e?.llmstxt),await h(o,e?.llmstxt,{title:e?.title,description:e?.description},c)}catch(r){f.error(`Failed to generate llms.txt. ${r}`);return}}}}export{v as createLlmsTxtGenerator};
1
+ import p from"node:path";import{logger as f}from"../../../tools/notifiers/logger.js";import{promiseMapLimit as x}from"../../../utils/async/promise-map-limit.js";import{getLlmsTxtMdPathBySlug as L}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{getRouteStaticData as T}from"../../../utils/get-route-static-data.js";import{getRouteAiDocumentsStore as w}from"../../../utils/get-route-ai-documents-store.js";import{generateLLMsTxt as h,validateLLMsTxtConfig as M}from"./index.js";import{buildPageLlmsTxtMd as S}from"./build-page-llms-txt-md.js";import{getLlmsTxtExcludeMatcher as m}from"./get-llms-txt-exclude-matcher.js";import{isIncludedInLlmsTxt as R}from"./is-included-in-llms-txt.js";import{EntitlementsProvider as C}from"../../../entitlements/entitlements-provider.js";const A=20;function v(i,c){const a=new WeakMap,l=()=>{const t=i.getGlobalConfig("seo");return C.instance().canAccessFeature("seo")===!0&&t?.llmstxt?.hide!==!0},d=async t=>{if(t.excludeFromSearch)return[];const e=i,n=await T(t,e);return await(await w(t,n,c,e))?.getLLMsTxts()??[]},u=t=>{const e=a.get(t);if(e)return e;const n=d(t).catch(o=>(a.delete(t),f.warn(`Failed to generate Markdown for LLMs of "${t.slug}". ${o}`),[]));return a.set(t,n),n},g=t=>{const e=t==="/index.html.md"?"/":t.replace(/\.md$/,"");for(let n=e;n.startsWith("/");){const o=i.getRouteBySlug(n,{followRedirect:!1});if(o?.getAiDocumentsStore)return o;const r=p.posix.dirname(n);if(r===n)return;n=r}};return{async getPageMarkdown(t){if(!l())return;const e=g(t);if(!e)return;const n=i.getGlobalConfig("seo"),o=m(n),r=(await u(e)).find(s=>L(s.slug)===t&&!o(s.fsPath));if(r)return{route:e,content:S(r,n)}},async getLlmsTxt(){if(!l())return;const t=i.getConfig(),e=i.getGlobalConfig("seo"),n=m(e),o=[];if(await x(i.getAllRoutes(),A,async r=>{for(const s of await u(r))!n(s.fsPath)&&R(s,r,t)&&o.push(s)}),!!o.length)try{return M(e?.llmstxt),await h(o,e?.llmstxt,{title:e?.title,description:e?.description,siteUrl:e?.siteUrl},c)}catch(r){f.error(`Failed to generate llms.txt. ${r}`);return}}}}export{v as createLlmsTxtGenerator};
@@ -4,11 +4,13 @@ import type { LLMsTxt } from './types';
4
4
  export declare function generateLLMsTxt(llmstxts: LLMsTxt[], llmstxtConfig: SeoConfig['llmstxt'] | undefined, fallbackConfig: {
5
5
  title?: string;
6
6
  description?: string;
7
+ siteUrl: string | undefined;
7
8
  } | undefined, context: LifecycleContext): Promise<string>;
8
- export declare function llmsTxtLink({ title, description, slug, }: {
9
+ export declare function llmsTxtLink({ title, description, slug, siteUrl, }: {
9
10
  title: LLMsTxt['title'];
10
11
  description: LLMsTxt['description'];
11
12
  slug: LLMsTxt['slug'];
13
+ siteUrl?: string;
12
14
  }): string;
13
15
  export declare function validateLLMsTxtConfig(llmstxtConfig: SeoConfig['llmstxt']): void;
14
16
  //# sourceMappingURL=index.d.ts.map
@@ -1,13 +1,13 @@
1
- import d from"picomatch";import{dirname as T,resolve as x}from"node:path";import{access as $,readFile as E,constants as F}from"fs/promises";import{removeTrailingSlash as w}from"../../../../utils/url/remove-trailing-slash.js";import{logger as g}from"../../../tools/notifiers/logger.js";import{withPathPrefix as y}from"@redocly/theme/core/utils";import{envConfig as I}from"../../../config/env-config.js";import{getLlmsTxtMdPathBySlug as v}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";const a="llms.txt",u="Table of contents";async function D(t,e,n={title:a,description:void 0},o){if(!e)return{title:n.title||a,description:n.description,details:void 0,sections:[{title:u,description:void 0,llmstxts:t}]};const s=e.title||n.title||a,r=e.description||n.description,i=await M(e,o),c=e.sections?.map(l=>{const{title:f,description:h,excludeFiles:m,includeFiles:L}=l,P=p(t,{excludeFiles:m,includeFiles:L});return{title:f,description:h,llmstxts:P}});return{title:s,description:r,details:i,sections:c||[{title:u,description:void 0,llmstxts:p(t,{includeFiles:["**/*"],excludeFiles:[]})}]}}async function M(t,e){const n=await e.getConfig();if(t?.details?.path&&n.configPath)try{const o=e.fs.getFileInfo(n.configPath);if(!o)throw new Error(`Config file ${n.configPath} not found`);const s=x(e.fs.cwd,T(o.relativePath),t.details.path);return await $(s,F.R_OK),E(s,"utf-8")}catch{throw new Error(`${t.details.path} is not accessible`)}if(t?.details?.content)return t.details.content}function p(t,e){const{excludeFiles:n,includeFiles:o}=e,s=n?.map(i=>d(i)),r=o?.map(i=>d(i));return t.filter(i=>s?.some(l=>l(i.fsPath))?!1:r?.some(l=>l(i.fsPath)))}async function j(t,e,n={title:a,description:void 0},o){const s=await D(t,e,n,o),r=[`# ${s.title}
1
+ import d from"picomatch";import{dirname as T,resolve as $}from"node:path";import{access as x,readFile as E,constants as F}from"fs/promises";import{removeTrailingSlash as w}from"../../../../utils/url/remove-trailing-slash.js";import{logger as g}from"../../../tools/notifiers/logger.js";import{withPathPrefix as U}from"@redocly/theme/core/utils";import{envConfig as y}from"../../../config/env-config.js";import{getLlmsTxtMdPathBySlug as I}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";const a="llms.txt",u="Table of contents";async function v(e,i,n={title:a,description:void 0},r){if(!i)return{title:n.title||a,description:n.description,details:void 0,sections:[{title:u,description:void 0,llmstxts:e}]};const s=i.title||n.title||a,l=i.description||n.description,t=await D(i,r),o=i.sections?.map(c=>{const{title:f,description:h,excludeFiles:m,includeFiles:L}=c,P=p(e,{excludeFiles:m,includeFiles:L});return{title:f,description:h,llmstxts:P}});return{title:s,description:l,details:t,sections:o||[{title:u,description:void 0,llmstxts:p(e,{includeFiles:["**/*"],excludeFiles:[]})}]}}async function D(e,i){const n=await i.getConfig();if(e?.details?.path&&n.configPath)try{const r=i.fs.getFileInfo(n.configPath);if(!r)throw new Error(`Config file ${n.configPath} not found`);const s=$(i.fs.cwd,T(r.relativePath),e.details.path);return await x(s,F.R_OK),E(s,"utf-8")}catch{throw new Error(`${e.details.path} is not accessible`)}if(e?.details?.content)return e.details.content}function p(e,i){const{excludeFiles:n,includeFiles:r}=i,s=n?.map(t=>d(t)),l=r?.map(t=>d(t));return e.filter(t=>s?.some(c=>c(t.fsPath))?!1:l?.some(c=>c(t.fsPath)))}async function b(e,i,n={title:a,description:void 0,siteUrl:void 0},r){const s=await v(e,i,n,r),l=n.siteUrl,t=[`# ${s.title}
2
2
 
3
- `];return s.description&&r.push(`> ${s.description}
3
+ `];return s.description&&t.push(`> ${s.description}
4
4
 
5
- `),s.details&&r.push(`${s.details}
5
+ `),s.details&&t.push(`${s.details}
6
6
 
7
- `),s.sections.forEach(i=>{if(!i.llmstxts.length){g.warn(`llms.txt: No links found for section "${i.title}". Please check the includeFiles/excludeFiles patterns.`);return}r.push(`## ${i.title}
8
- `),i.description?r.push(`${i.description.replace(/\n+$/,"")}
7
+ `),s.sections.forEach(o=>{if(!o.llmstxts.length){g.warn(`llms.txt: No links found for section "${o.title}". Please check the includeFiles/excludeFiles patterns.`);return}t.push(`## ${o.title}
8
+ `),o.description?t.push(`${o.description.replace(/\n+$/,"")}
9
9
 
10
- `):r.push(`
11
- `),i.llmstxts.forEach(c=>{r.push(` - ${_({title:c.title,description:c.description,slug:v(c.slug)})}`)}),r.push(`
12
- `)}),r.join("")}function _({title:t,description:e,slug:n}){const o=e?.replace(/\s+/g," ").trim();return`[${t}](${w(I.REDOCLY_PUBLIC_URL||"")}${encodeURI(y(n))})${o?`: ${o}`:""}
13
- `}function K(t){if(t?.details?.path&&t?.details?.content)throw new Error('"details.path" and "details.content" are mutually exclusive. Please use only one of them.')}export{j as generateLLMsTxt,_ as llmsTxtLink,K as validateLLMsTxtConfig};
10
+ `):t.push(`
11
+ `),o.llmstxts.forEach(c=>{t.push(` - ${M({title:c.title,description:c.description,slug:I(c.slug),siteUrl:l})}`)}),t.push(`
12
+ `)}),t.join("")}function M({title:e,description:i,slug:n,siteUrl:r}){const l=`${w(r?.trim()||y.REDOCLY_PUBLIC_URL||"")}${encodeURI(U(n))}`,t=i?.replace(/\s+/g," ").trim();return`[${e}](${l})${t?`: ${t}`:""}
13
+ `}function j(e){if(e?.details?.path&&e?.details?.content)throw new Error('"details.path" and "details.content" are mutually exclusive. Please use only one of them.')}export{b as generateLLMsTxt,M as llmsTxtLink,j as validateLLMsTxtConfig};
@@ -1,5 +1,4 @@
1
- import type { RedoclyConfig } from '@redocly/config';
2
- import type { PageProps, PageStaticData } from '@redocly/config';
1
+ import type { RedoclyConfig, PageProps, PageStaticData } from '@redocly/config';
3
2
  import type { PageRouteDetails } from '../../../types';
4
3
  import type { LLMsTxt } from './types.js';
5
4
  export declare function isIncludedInLlmsTxt(llmsTxt: LLMsTxt, route: PageRouteDetails<PageStaticData, PageProps>, config: RedoclyConfig): boolean;
@@ -1,3 +1,6 @@
1
1
  export declare const MAX_HOST_OUTPUT_BYTES: number;
2
2
  export declare const DEFAULT_GLOBAL_NAME = "hostFunctions";
3
+ /** Guest error names for host-side failures, so the sandbox can tell them from a guest throw. */
4
+ export declare const HOST_FUNCTION_ERROR_NAME = "HostFunctionError";
5
+ export declare const HOST_OUTPUT_TOO_LARGE_ERROR_NAME = "HostOutputTooLargeError";
3
6
  //# sourceMappingURL=host-functions.d.ts.map
@@ -1 +1 @@
1
- const o=1048576,t="hostFunctions";export{t as DEFAULT_GLOBAL_NAME,o as MAX_HOST_OUTPUT_BYTES};
1
+ const o=1048576,t="hostFunctions",_="HostFunctionError",O="HostOutputTooLargeError";export{t as DEFAULT_GLOBAL_NAME,_ as HOST_FUNCTION_ERROR_NAME,O as HOST_OUTPUT_TOO_LARGE_ERROR_NAME,o as MAX_HOST_OUTPUT_BYTES};
@@ -6,4 +6,14 @@ export declare const MAX_ALLOCATION_BYTES: number;
6
6
  export declare const MAX_TOTAL_MEMORY_BYTES: number;
7
7
  export declare const INITIAL_TOTAL_MEMORY_BYTES: number;
8
8
  export declare const WASM_PAGE_BYTES: number;
9
+ /** Why a run failed. */
10
+ export declare const SANDBOX_FAILURE: {
11
+ readonly NoReturn: 'no_return';
12
+ readonly OutputTooLarge: 'output_too_large';
13
+ readonly Timeout: 'timeout';
14
+ readonly MemoryLimit: 'memory_limit';
15
+ readonly SyntaxError: 'syntax_error';
16
+ readonly HostError: 'host_error';
17
+ readonly RuntimeError: 'runtime_error';
18
+ };
9
19
  //# sourceMappingURL=sandbox.d.ts.map
@@ -1 +1 @@
1
- const _=1e4,T=6e5,o=262144,t=1048576,E=67108864,A=134217728,M=16777216,S=65536;export{T as DEFAULT_MAX_LIFETIME_MS,_ as DEFAULT_TIMEOUT_MS,M as INITIAL_TOTAL_MEMORY_BYTES,E as MAX_ALLOCATION_BYTES,t as MAX_RESULT_BYTES,o as MAX_STACK_SIZE_BYTES,A as MAX_TOTAL_MEMORY_BYTES,S as WASM_PAGE_BYTES};
1
+ const o=1e4,t=6e5,r=262144,_=1048576,e=67108864,T=134217728,E=16777216,A=65536,n={NoReturn:"no_return",OutputTooLarge:"output_too_large",Timeout:"timeout",MemoryLimit:"memory_limit",SyntaxError:"syntax_error",HostError:"host_error",RuntimeError:"runtime_error"};export{t as DEFAULT_MAX_LIFETIME_MS,o as DEFAULT_TIMEOUT_MS,E as INITIAL_TOTAL_MEMORY_BYTES,e as MAX_ALLOCATION_BYTES,_ as MAX_RESULT_BYTES,r as MAX_STACK_SIZE_BYTES,T as MAX_TOTAL_MEMORY_BYTES,n as SANDBOX_FAILURE,A as WASM_PAGE_BYTES};
@@ -1,3 +1,3 @@
1
- import p from"@jitl/quickjs-singlefile-mjs-release-sync";import{newQuickJSWASMModuleFromVariant as _,newVariant as E}from"quickjs-emscripten-core";import{createExecutionBudget as S}from"./utils/execution-budget.js";import{returnsFromTopLevel as A}from"./utils/returns-from-top-level.js";import{DEFAULT_MAX_LIFETIME_MS as y,DEFAULT_TIMEOUT_MS as b,INITIAL_TOTAL_MEMORY_BYTES as h,MAX_ALLOCATION_BYTES as L,MAX_RESULT_BYTES as g,MAX_STACK_SIZE_BYTES as w,MAX_TOTAL_MEMORY_BYTES as x,WASM_PAGE_BYTES as d}from"./constants/sandbox.js";let c;function I(){return c??=_(E(p,{wasmMemory:new WebAssembly.Memory({initial:h/d,maximum:x/d})}))}async function P(t,l={}){const{timeoutMs:m=b,maxLifetimeMs:f=y,setupContext:M}=l,e=(await I()).newContext();let n,i,r;try{e.runtime.setMemoryLimit(L),e.runtime.setMaxStackSize(w),n=S(e,m,f),i=M?.(e,n)??void 0,r=e.unwrapResult(e.evalCode(`(async () => {
2
- ${t}
3
- })()`));const o=e.resolvePromise(r);e.runtime.executePendingJobs();const T=await Promise.race([o,n.aborted]),u=e.unwrapResult(T),s=e.dump(u);if(u.dispose(),s===void 0)return{error:A(e,t)?"The code returned undefined: the `return` ran but produced no value. Read fields off the declared return types \u2014 a field name that does not exist reads as undefined \u2014 and do not `return;` or `return undefined`.":"The code returned undefined because nothing was returned. Add a top-level `return`: a value computed inside a function or IIFE is discarded unless you return it, and a promise chain has to be returned to be awaited."};const a=JSON.stringify(s??null).length;return a>g?{error:`Result is too large (${a} bytes); return less data`}:{result:s}}catch(o){return{error:v(o)}}finally{n?.dispose(),r?.dispose();try{i?.()}catch{}try{e.dispose()}catch{c=void 0}}}function v(t){return t instanceof Error?t.message:String(t)}export{P as runInSandbox};
1
+ import A from"@jitl/quickjs-singlefile-mjs-release-sync";import{newQuickJSWASMModuleFromVariant as S,newVariant as y}from"quickjs-emscripten-core";import{createExecutionBudget as O}from"./utils/execution-budget.js";import{readResult as x}from"./utils/read-result.js";import{returnsFromTopLevel as R}from"./utils/returns-from-top-level.js";import{HOST_FUNCTION_ERROR_NAME as g,HOST_OUTPUT_TOO_LARGE_ERROR_NAME as L}from"./constants/host-functions.js";import{DEFAULT_MAX_LIFETIME_MS as b,DEFAULT_TIMEOUT_MS as I,INITIAL_TOTAL_MEMORY_BYTES as h,MAX_ALLOCATION_BYTES as N,MAX_STACK_SIZE_BYTES as w,MAX_TOTAL_MEMORY_BYTES as v,SANDBOX_FAILURE as n,WASM_PAGE_BYTES as E}from"./constants/sandbox.js";let p;function C(){return p??=S(y(A,{wasmMemory:new WebAssembly.Memory({initial:h/E,maximum:v/E})}))}async function Q(e,t={}){const{timeoutMs:o=I,maxLifetimeMs:T=b,setupContext:M,authoredCode:_=e}=t,r=(await C()).newContext();let u,l,d,f;try{r.runtime.setMemoryLimit(N),r.runtime.setMaxStackSize(w),u=O(r,o,T),f=r.unwrapResult(r.evalCode("JSON.stringify")),l=M?.(r,u)??void 0,d=r.unwrapResult(r.evalCode(`(async () => {
2
+ ${e}
3
+ })()`,U));const a=r.resolvePromise(d);r.runtime.executePendingJobs();const c=await Promise.race([a,u.aborted]),i=r.unwrapResult(c);if(r.typeof(i)==="undefined")return i.dispose(),{error:R(_)?"The code returned undefined: the `return` ran but produced no value. Read fields off the declared return types \u2014 a field name that does not exist reads as undefined \u2014 and do not `return;` or `return undefined`.":"The code returned undefined because nothing was returned. Add a top-level `return` of the value to send back.",failure:n.NoReturn};const s=x(r,f,i);if(s.error===void 0)return s;const m=u.expiredMessage();return m===void 0?s:{error:m,failure:n.Timeout}}catch(a){const c=u?.expiredMessage();if(c!==void 0)return{error:c,failure:n.Timeout};const i=P(a),s=j(i.stack);return{error:F(a),failure:Y(i,d!==void 0),...s?{errorPosition:s}:{}}}finally{u?.dispose(),d?.dispose(),f?.dispose();try{l?.()}catch{}try{r.dispose()}catch{p=void 0}}}function F(e){return e instanceof Error?e.message:String(e)}const U="sandbox-code.js",B=/sandbox-code\.js:(\d+):(\d+)/;function P(e){const{cause:t}=e;return typeof t=="object"&&t!==null?t:{}}function Y({name:e,message:t},o){return e==="InternalError"&&t==="out of memory"?n.MemoryLimit:e==="SyntaxError"&&!o?n.SyntaxError:e===L?n.OutputTooLarge:e===g?n.HostError:n.RuntimeError}function j(e){const t=typeof e=="string"?B.exec(e):null;if(!t)return;const o=Number(t[1])-1;return o>=1?{line:o,column:Number(t[2])}:void 0}export{Q as runInSandbox};