@wix/mcp 1.0.94 → 1.0.95

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 (79) hide show
  1. package/README.md +2 -2
  2. package/build/bin-standalone.js +1116 -321
  3. package/build/cjs/index.cjs +2813 -1147
  4. package/build/cjs/toolkit.cjs +1788 -362
  5. package/build/dts/api-call/headless-release.d.ts +56 -0
  6. package/build/dts/api-call/headless-release.js +135 -0
  7. package/build/dts/api-call/index.d.ts +9 -1
  8. package/build/dts/api-call/index.js +13 -7
  9. package/build/dts/api-call/site-files.d.ts +14 -0
  10. package/build/dts/api-call/site-files.js +106 -10
  11. package/build/dts/api-call/upload-headless-website-files.d.ts +88 -5
  12. package/build/dts/api-call/upload-headless-website-files.js +473 -20
  13. package/build/dts/api-call/upload-image-tool.d.ts +2 -0
  14. package/build/dts/api-call/upload-image-tool.js +8 -4
  15. package/build/dts/bin.d.ts +1 -1
  16. package/build/dts/bin.js +3 -1
  17. package/build/dts/code-mode/index.d.ts +6 -1
  18. package/build/dts/code-mode/index.js +30 -10
  19. package/build/dts/code-mode/truncate-response.js +5 -1
  20. package/build/dts/config/default-config.d.ts +35 -5
  21. package/build/dts/config/default-config.js +334 -178
  22. package/build/dts/config/file-params.d.ts +22 -0
  23. package/build/dts/config/file-params.js +24 -0
  24. package/build/dts/config/param-descriptions.d.ts +14 -2
  25. package/build/dts/config/param-descriptions.js +33 -14
  26. package/build/dts/constants.d.ts +1 -0
  27. package/build/dts/constants.js +1 -0
  28. package/build/dts/docs/docs.d.ts +12 -1
  29. package/build/dts/docs/docs.js +210 -42
  30. package/build/dts/docs/fetch-article.d.ts +21 -0
  31. package/build/dts/docs/fetch-article.js +66 -4
  32. package/build/dts/docs/get-to-know.d.ts +7 -2
  33. package/build/dts/docs/get-to-know.js +36 -14
  34. package/build/dts/docs/mintlify-docs.d.ts +121 -0
  35. package/build/dts/docs/mintlify-docs.js +391 -0
  36. package/build/dts/docs/semanticSearch.d.ts +2 -1
  37. package/build/dts/docs/semanticSearch.js +14 -7
  38. package/build/dts/docs/start-deploy-website.d.ts +7 -6
  39. package/build/dts/docs/start-deploy-website.js +13 -8
  40. package/build/dts/docs/start-headless-build.d.ts +8 -10
  41. package/build/dts/docs/start-headless-build.js +14 -28
  42. package/build/dts/index.d.ts +2 -2
  43. package/build/dts/index.js +2 -2
  44. package/build/dts/infra/redact-personal-data.d.ts +4 -0
  45. package/build/dts/infra/redact-personal-data.js +79 -0
  46. package/build/dts/site-widget-tools/constants.d.ts +49 -1
  47. package/build/dts/site-widget-tools/constants.js +64 -7
  48. package/build/dts/site-widget-tools/index.d.ts +1 -0
  49. package/build/dts/site-widget-tools/index.js +1 -0
  50. package/build/dts/site-widget-tools/pick-images-tool/index.d.ts +14 -0
  51. package/build/dts/site-widget-tools/pick-images-tool/index.js +138 -0
  52. package/build/dts/site-widget-tools/search-site-templates-tool/index.js +16 -2
  53. package/build/dts/site-widget-tools/site-builder-tool/index.js +22 -2
  54. package/build/dts/site-widget-tools/site-creation-tool/index.d.ts +7 -1
  55. package/build/dts/site-widget-tools/site-creation-tool/index.js +22 -51
  56. package/build/dts/site-widget-tools/suggested-domain-tool/index.js +6 -3
  57. package/build/dts/support/index.d.ts +4 -0
  58. package/build/dts/support/index.js +13 -7
  59. package/build/dts/toolkit-entry.d.cts +15 -10
  60. package/build/dts/toolkit-entry.d.ts +15 -10
  61. package/build/dts/toolkit.d.ts +2 -2
  62. package/build/dts/toolkit.js +14 -7
  63. package/build/dts/wix-mcp-server.d.ts +10 -1
  64. package/build/dts/wix-mcp-server.js +25 -7
  65. package/build/dts/wixel/constants.d.ts +8 -1
  66. package/build/dts/wixel/constants.js +8 -1
  67. package/build/dts/wixel/index.d.ts +1 -1
  68. package/build/dts/wixel/index.js +2 -2
  69. package/build/dts/wixel/wixel-a2a-client.d.ts +83 -0
  70. package/build/dts/wixel/wixel-a2a-client.js +166 -0
  71. package/build/dts/wixel/wixel-bi.d.ts +2 -1
  72. package/build/dts/wixel/wixel-bi.js +2 -1
  73. package/build/dts/wixel/{external-agent-api.d.ts → wixel-session-entries.d.ts} +11 -33
  74. package/build/dts/wixel/{external-agent-api.js → wixel-session-entries.js} +33 -54
  75. package/build/dts/wixel/wixel-session-tools.d.ts +21 -26
  76. package/build/dts/wixel/wixel-session-tools.js +494 -599
  77. package/build/esm/index.js +2824 -1170
  78. package/build/esm/toolkit.js +1786 -360
  79. package/package.json +4 -4
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import dedent from 'dedent';
3
- import { fetchArticleContent, RECIPES_INDEX_URL } from './fetch-article.js';
3
+ import { fetchArticleContent, fetchSourceContent, RECIPES_INDEX_URL } from './fetch-article.js';
4
4
  import { getSkillsByPortal } from './fetch-skills.js';
5
5
  import { REST_PORTAL_ID } from '../constants.js';
6
6
  // import { generateDocsNavigationTree } from './normalize-menu-content.js';
@@ -8,8 +8,13 @@ import { rethrowIfUnauthorized } from '../auth/index.js';
8
8
  import { defaultToolDescriptions, defaultReadmeDocs } from '../config/default-config.js';
9
9
  import { getDynamicContext } from '@wix/dynamic-context-enricher';
10
10
  import { desc } from '../config/param-descriptions.js';
11
+ import { isMintlifyDocsSite, rewriteDocsLinks } from './mintlify-docs.js';
11
12
  export { getDynamicContext };
12
- export function addGetSiteContextTool({ server, authStrategy, toolDescriptions }) {
13
+ // The management index as authored in ax-skills, which also holds skills its
14
+ // public mirror (and so the published article) leaves out.
15
+ const AX_SKILLS_REPO = 'wix-private/ax-skills';
16
+ const AX_SKILLS_INDEX_PATH = 'skills/wix-manage/SKILL.md';
17
+ export function addGetSiteContextTool({ server, authStrategy, includeCompanionApp = false, toolDescriptions }) {
13
18
  const d = desc('GetSiteContext');
14
19
  server.tool('GetSiteContext', toolDescriptions?.['GetSiteContext'] ??
15
20
  defaultToolDescriptions.GetSiteContext, {
@@ -35,7 +40,8 @@ export function addGetSiteContextTool({ server, authStrategy, toolDescriptions }
35
40
  try {
36
41
  context = await getDynamicContext(authStrategy, httpClient, filter, {
37
42
  partnerContextEnabled: false,
38
- includeAccountContext: false
43
+ includeAccountContext: false,
44
+ includeCompanionApp
39
45
  });
40
46
  }
41
47
  catch (err) {
@@ -61,14 +67,19 @@ export function addGetSiteContextTool({ server, authStrategy, toolDescriptions }
61
67
  };
62
68
  });
63
69
  }
64
- export async function addGetToKnowTools({ server, disableTools = [], toolDescriptions, readmeDocs, authStrategy, skillsPortalUrl, dynamicContextEnabled, repo, commitHash, skillsTags }) {
70
+ export async function addGetToKnowTools({ server, disableTools = [], toolDescriptions, readmeDocs, authStrategy, skillsPortalUrl, dynamicContextEnabled, repo, commitHash, skillsTags, readmeRecipesIndexEnabled = true, docsSite }) {
65
71
  if (disableTools.includes('WixREADME')) {
66
72
  return;
67
73
  }
68
74
  // Use provided docs or fall back to defaults. The Recipes block is appended
69
75
  // at the end so the hand-authored wix-usage-guide stays on top.
70
76
  const baseDocs = readmeDocs ?? defaultReadmeDocs;
71
- const baseDocsWithoutRecipes = baseDocs.filter((doc) => doc.url !== RECIPES_INDEX_URL);
77
+ // The list's page and its .md form are the same document.
78
+ const isRecipesIndex = (doc) => doc.url?.replace(/\.md$/, '') === RECIPES_INDEX_URL;
79
+ const baseDocsWithoutRecipes = baseDocs.filter((doc) => !isRecipesIndex(doc));
80
+ const recipesIndexDocs = readmeRecipesIndexEnabled
81
+ ? baseDocs.filter(isRecipesIndex)
82
+ : [];
72
83
  server.tool('WixREADME', toolDescriptions?.['WixREADME'] ?? defaultToolDescriptions.WixREADME, {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (args, { httpClient }) => {
73
84
  // The schema is intentionally argless so the LLM always calls WixREADME
74
85
  // account-wide. A specific site can still be targeted programmatically
@@ -130,6 +141,15 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
130
141
  if (!url) {
131
142
  throw new Error('URL is required for "include" action');
132
143
  }
144
+ const readsAxSkills = isRecipesIndex({ action, url }) &&
145
+ !skillsPortalUrl &&
146
+ (repo ? repo === AX_SKILLS_REPO : !commitHash);
147
+ if (readsAxSkills) {
148
+ return {
149
+ type: 'text',
150
+ text: await fetchSourceContent(httpClient, AX_SKILLS_REPO, AX_SKILLS_INDEX_PATH, repo === AX_SKILLS_REPO ? commitHash : undefined)
151
+ };
152
+ }
133
153
  const fetchedContent = await fetchArticleContent(httpClient, url, 'article', true, skillsPortalUrl, repo, commitHash);
134
154
  return {
135
155
  type: 'text',
@@ -169,9 +189,6 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
169
189
  // return '';
170
190
  // });
171
191
  const baseContentsPromise = Promise.all(baseDocsWithoutRecipes.map(fetchDoc));
172
- // Filtered lists still come from the portal. The full management index is
173
- // authored alongside the recipes and is fetched like any other article,
174
- // using its commit version when a PR override is configured.
175
192
  const skillsContentPromise = skillsTags?.length
176
193
  ? getSkillsByPortal(httpClient, {
177
194
  portalId: REST_PORTAL_ID,
@@ -182,7 +199,9 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
182
199
  console.error('[WixREADME] Failed to fetch skills by portal:', err);
183
200
  return null;
184
201
  })
185
- : fetchDoc({ action: 'include', url: RECIPES_INDEX_URL }).then((doc) => doc?.text ?? null);
202
+ : readmeRecipesIndexEnabled
203
+ ? fetchDoc({ action: 'include', url: RECIPES_INDEX_URL }).then((doc) => doc?.text ?? null)
204
+ : Promise.resolve(null);
186
205
  // Auto-inject site context. With no site targeted this is the account-wide
187
206
  // view: single-site accounts "just work", and accounts with more than the
188
207
  // cap show the first N sites plus a note to use GetSiteContext for the rest.
@@ -207,7 +226,7 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
207
226
  const recipesIntro = dedent `
208
227
  Reusable how-to guides for common ${isVisitorFlow ? 'Wix site visitor' : 'Wix management'} tasks, grouped by product area. Each recipe is a Wix docs article that bundles the right endpoints, request shapes, field names, and step ordering for one real-world use case.
209
228
 
210
- Scan the index below and open any relevant article with \`ReadFullDocsArticle\`. If the article contains a code example that fits the user's intent — a **complete** example (not a truncated \`... N more lines ...\` snippet) that matches your exact case, same variant/shape — call the API directly. If the closest example is truncated or omits a field your case needs, do NOT call the API off it — continue discovery first: \`ReadFullDocsArticle\` for the complete example, or \`SearchWixAPISpec\` for the exact schema. On query and search methods an example proves only the fields and operators it uses; the endpoint's own declared list covers the rest. Otherwise (no fitting recipe, no usable code example, or persistent errors after retrying), fall back to docs discovery: \`SearchWixRESTDocumentation\` or \`BrowseWixRESTDocsMenu\` to find the right method, then \`SearchWixAPISpec\` for the exact schema.
229
+ Open the recipe that matches the task with \`ReadFullDocsArticle\` and follow it. When its example for a call matches your case (the same variant and shape), call from it; when it doesn't, or leaves out a field your case needs, read the method's schema with \`SearchWixAPISpec\` first. On query and search methods an example proves only the fields and operators it uses; the method's own declared list covers the rest. When no recipe fits, find the method with \`SearchWixRESTDocumentation\` or \`BrowseWixRESTDocsMenu\`, then \`SearchWixAPISpec\` for its schema.
211
230
  `;
212
231
  const recipesContents = skillsContent !== null
213
232
  ? [
@@ -217,9 +236,7 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
217
236
  content: `${recipesIntro}\n\n${skillsContent}`
218
237
  })
219
238
  ]
220
- : await Promise.all(baseDocs
221
- .filter((doc) => doc.url === RECIPES_INDEX_URL)
222
- .map(fetchDoc));
239
+ : await Promise.all(recipesIndexDocs.map(fetchDoc));
223
240
  // Cross-recipe steering that applies to a family of recipes rather than
224
241
  // any single article; rendered right after the index. Gated on the note's
225
242
  // target family actually appearing in the rendered index — this also
@@ -244,7 +261,12 @@ export async function addGetToKnowTools({ server, disableTools = [], toolDescrip
244
261
  .map((item) => item.text),
245
262
  ...(dynamicContext ? [dynamicContext] : [])
246
263
  ];
247
- const text = parts.join('\n\n');
264
+ const joined = parts.join('\n\n');
265
+ // Mintlify docs: the README links docs pages on dev.wix.com; resolve
266
+ // them to their Mintlify pages, as the docs tools do.
267
+ const text = isMintlifyDocsSite(docsSite)
268
+ ? (await rewriteDocsLinks(joined, { label: 'readme WixREADME' })).text
269
+ : joined;
248
270
  return {
249
271
  content: [{ type: 'text', text }]
250
272
  };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Mintlify docs, `mintlify-links` mode (wix-private/docs:
3
+ * documentation/mintlify-migration/tasks/mcp-pipeline.md).
4
+ *
5
+ * The KBs and every backend stay as they are. With `docsSite: 'mintlify-links'`,
6
+ * the docs tools resolve each dev.wix.com docs link in their output to its page
7
+ * on the Mintlify site, and ReadFullDocsArticle reads pages from Mintlify's
8
+ * `.md`. Off unless a caller passes the option.
9
+ */
10
+ export declare const MINTLIFY_LINKS_DOCS_SITE = "mintlify-links";
11
+ /** Stage 2: the same, with search on the Mintlify KBs indexed from the Mintlify site. */
12
+ export declare const MINTLIFY_DOCS_SITE = "mintlify";
13
+ export type DocsSite = typeof MINTLIFY_LINKS_DOCS_SITE | typeof MINTLIFY_DOCS_SITE;
14
+ export declare const MINTLIFY_HOST = "https://wix-internal.mintlify.site";
15
+ export declare const MAX_CHECK_ENTRIES = 5000;
16
+ export declare function isMintlifyDocsSite(docsSite: string | undefined): boolean;
17
+ /** The Mintlify document types a search tool uses in stage 2, or undefined to search as today. */
18
+ export declare function mintlifyDocumentTypes(docsSite: string | undefined, toolName: string): string[] | undefined;
19
+ export declare function isMintlifyUrl(url: string): boolean;
20
+ /** The URL prefixes the docs tools accept in Mintlify mode, besides dev.wix.com. */
21
+ export declare const MINTLIFY_URL_PREFIXES: string[];
22
+ export interface MintlifyCandidate {
23
+ /** Canonical Mintlify page URL: host + path, no query, anchor or `.md`. */
24
+ url: string;
25
+ path: string;
26
+ /** `sdk` when the source link asked for the SDK view (`?apiView=SDK`). */
27
+ flavour: 'rest' | 'sdk';
28
+ }
29
+ /**
30
+ * Maps a dev.wix.com docs URL, or a Mintlify URL on any alias host, to its
31
+ * Mintlify page by rule: drop `/docs`, map the portal slug to its tab, drop
32
+ * the query, the anchor and `.md`. Returns null for anything else.
33
+ */
34
+ export declare function toMintlifyCandidate(rawUrl: string): MintlifyCandidate | null;
35
+ /**
36
+ * Maps a Mintlify URL back to its dev.wix.com docs URL, keeping `?apiView=SDK`.
37
+ * For the REST portal the paths are the same, so the backends that only know
38
+ * dev.wix.com (ingvar, the docs menu) can serve it.
39
+ */
40
+ export declare function toDevWixUrl(rawUrl: string): string;
41
+ export type ResolveOutcome = 'identity' | 'redirect' | 'missing' | 'other-portal' | 'not-docs' | 'error';
42
+ export interface ResolvedLink {
43
+ outcome: ResolveOutcome;
44
+ /** The URL to hand out: the Mintlify page when resolved, else the input. */
45
+ url: string;
46
+ }
47
+ interface PageCheck {
48
+ outcome: 'identity' | 'redirect' | 'missing' | 'error';
49
+ /** Final page path on the Mintlify site, when the page exists. */
50
+ path?: string;
51
+ }
52
+ /** For tests. */
53
+ export declare function mintlifyCheckCacheSize(): number;
54
+ /** For tests. */
55
+ export declare function clearMintlifyCheckCache(): void;
56
+ /**
57
+ * Asks the Mintlify site whether a page exists, following its own redirects
58
+ * (moves, and containers that send you to their first page) without
59
+ * following them blindly: a page is a 200 at the end of at most 3 hops.
60
+ */
61
+ export declare function checkMintlifyPage(path: string): Promise<PageCheck>;
62
+ /**
63
+ * Resolves one docs link to its Mintlify page. With `verify: false` the
64
+ * mapping is by rule only, which suits menu links (the menu and the Mintlify
65
+ * navigation come from the same Portal Service tree).
66
+ */
67
+ export declare function resolveDocsLink(rawUrl: string, { verify }?: {
68
+ verify?: boolean;
69
+ }): Promise<ResolvedLink>;
70
+ /**
71
+ * Maps every Mintlify page link in `text` back to its dev.wix.com docs URL, keeping
72
+ * `?apiView=SDK` and the anchor. Run before {@link rewriteDocsLinks} on text whose
73
+ * Mintlify links came by rule rather than from a checked page (SearchWixAPISpec's
74
+ * index), so a page the site doesn't have ends up on dev.wix.com, not a dead link.
75
+ */
76
+ export declare function toDevWixHostText(text: string): string;
77
+ export interface RewriteStats {
78
+ links: number;
79
+ byOutcome: Partial<Record<ResolveOutcome, number>>;
80
+ misses: string[];
81
+ }
82
+ /**
83
+ * Rewrites every docs link in `text` to its Mintlify page. Links with no
84
+ * Mintlify page stay as they are, so the agent can still read them through
85
+ * today's backends. Logs one coverage line per call.
86
+ */
87
+ export declare function rewriteDocsLinks(text: string, { verify, label }?: {
88
+ verify?: boolean;
89
+ label?: string;
90
+ }): Promise<{
91
+ text: string;
92
+ stats: RewriteStats;
93
+ }>;
94
+ /**
95
+ * Light cleanup of a method page's .md: drop inlined snippet source, and keep
96
+ * only the tab the reader asked for (REST, or SDK for `?apiView=SDK`). A
97
+ * method page carries both, which roughly doubles its size. Pages without
98
+ * exactly one REST and one SDK tab are left as they are.
99
+ */
100
+ export declare function cleanMintlifyMarkdown(markdown: string, flavour: 'rest' | 'sdk'): string;
101
+ /**
102
+ * Tool and parameter descriptions with their docs URLs on the Mintlify host. The
103
+ * model copies the examples' host when it cites a page, so with dev.wix.com in
104
+ * the descriptions it rewrites Mintlify links back to dev.wix.com.
105
+ */
106
+ export declare function toMintlifyHostText(text: string): string;
107
+ /**
108
+ * Mintlify's .md links the site's own pages by path (`[Order Object](/api-reference/…)`).
109
+ * Make them absolute, so the model doesn't have to guess the host (it picks dev.wix.com).
110
+ */
111
+ export declare function absolutizeSiteLinks(markdown: string): string;
112
+ /**
113
+ * Reads a page from the Mintlify site as Markdown. Returns null when the site
114
+ * has no page for the URL, so the caller can fall back to today's backend.
115
+ */
116
+ export declare function readMintlifyMarkdown(rawUrl: string): Promise<{
117
+ url: string;
118
+ markdown: string;
119
+ } | null>;
120
+ export {};
121
+ //# sourceMappingURL=mintlify-docs.d.ts.map
@@ -0,0 +1,391 @@
1
+ import { logger } from '../infra/logger.js';
2
+ /**
3
+ * Mintlify docs, `mintlify-links` mode (wix-private/docs:
4
+ * documentation/mintlify-migration/tasks/mcp-pipeline.md).
5
+ *
6
+ * The KBs and every backend stay as they are. With `docsSite: 'mintlify-links'`,
7
+ * the docs tools resolve each dev.wix.com docs link in their output to its page
8
+ * on the Mintlify site, and ReadFullDocsArticle reads pages from Mintlify's
9
+ * `.md`. Off unless a caller passes the option.
10
+ */
11
+ export const MINTLIFY_LINKS_DOCS_SITE = 'mintlify-links';
12
+ /** Stage 2: the same, with search on the Mintlify KBs indexed from the Mintlify site. */
13
+ export const MINTLIFY_DOCS_SITE = 'mintlify';
14
+ export const MINTLIFY_HOST = 'https://wix-internal.mintlify.site';
15
+ const MINTLIFY_HOSTNAMES = new Set([
16
+ 'wix-internal.mintlify.site',
17
+ 'wix-internal.mintlify.app'
18
+ ]);
19
+ const DEV_WIX_DOCS = 'https://dev.wix.com/docs';
20
+ // dev.wix.com portal slug -> Mintlify tab base, where the two differ.
21
+ const PORTAL_SLUG_TO_TAB = {
22
+ rest: 'api-reference',
23
+ 'mintlify-sandbox': 'sandbox'
24
+ };
25
+ const TAB_TO_PORTAL_SLUG = {
26
+ sandbox: 'mintlify-sandbox'
27
+ };
28
+ // The tabs the Mintlify site serves. A dev.wix.com link into any other portal
29
+ // (sdk, velo, build-apps, ...) has no page there yet.
30
+ const MINTLIFY_TABS = new Set(['api-reference', 'sandbox', 'kb']);
31
+ // The skills registry is served on dev.wix.com/docs/skills and is not part of
32
+ // the docs site, so its links are left alone.
33
+ const NON_DOCS_SLUGS = new Set(['skills', 'kb-only', 'api']);
34
+ const CHECK_TTL_MS = 10 * 60 * 1000;
35
+ // Bounds the page-check cache, so many distinct paths can't grow it without limit.
36
+ export const MAX_CHECK_ENTRIES = 5000;
37
+ const CHECK_TIMEOUT_MS = 5000;
38
+ const MAX_REDIRECTS = 3;
39
+ const CHECK_CONCURRENCY = 8;
40
+ export function isMintlifyDocsSite(docsSite) {
41
+ return (docsSite === MINTLIFY_LINKS_DOCS_SITE || docsSite === MINTLIFY_DOCS_SITE);
42
+ }
43
+ // docs-search-server document types that read the Mintlify KBs instead of today's.
44
+ const MINTLIFY_DOCUMENT_TYPES = {
45
+ REST: ['MINTLIFY_REST'], // includes the skills, as REST + SKILLS does today
46
+ SDK: ['MINTLIFY_SDK']
47
+ };
48
+ /** The Mintlify document types a search tool uses in stage 2, or undefined to search as today. */
49
+ export function mintlifyDocumentTypes(docsSite, toolName) {
50
+ return docsSite === MINTLIFY_DOCS_SITE
51
+ ? MINTLIFY_DOCUMENT_TYPES[toolName]
52
+ : undefined;
53
+ }
54
+ export function isMintlifyUrl(url) {
55
+ try {
56
+ return MINTLIFY_HOSTNAMES.has(new URL(url).hostname);
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ /** The URL prefixes the docs tools accept in Mintlify mode, besides dev.wix.com. */
63
+ export const MINTLIFY_URL_PREFIXES = [...MINTLIFY_HOSTNAMES].map((host) => `https://${host}/`);
64
+ function cleanPath(pathname) {
65
+ const path = pathname.replace(/\.md$/, '').replace(/\/+$/, '');
66
+ return path === '' ? '/' : path;
67
+ }
68
+ function flavourOf(url) {
69
+ return url.searchParams.get('apiView')?.toUpperCase() === 'SDK'
70
+ ? 'sdk'
71
+ : 'rest';
72
+ }
73
+ /**
74
+ * Maps a dev.wix.com docs URL, or a Mintlify URL on any alias host, to its
75
+ * Mintlify page by rule: drop `/docs`, map the portal slug to its tab, drop
76
+ * the query, the anchor and `.md`. Returns null for anything else.
77
+ */
78
+ export function toMintlifyCandidate(rawUrl) {
79
+ let url;
80
+ try {
81
+ url = new URL(rawUrl);
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ if (MINTLIFY_HOSTNAMES.has(url.hostname)) {
87
+ const path = cleanPath(url.pathname);
88
+ return { url: `${MINTLIFY_HOST}${path}`, path, flavour: flavourOf(url) };
89
+ }
90
+ if (url.hostname !== 'dev.wix.com' || !url.pathname.startsWith('/docs/')) {
91
+ return null;
92
+ }
93
+ const segments = cleanPath(url.pathname.slice('/docs'.length))
94
+ .split('/')
95
+ .filter(Boolean);
96
+ if (segments.length === 0 || NON_DOCS_SLUGS.has(segments[0])) {
97
+ return null;
98
+ }
99
+ segments[0] = PORTAL_SLUG_TO_TAB[segments[0]] ?? segments[0];
100
+ const path = `/${segments.join('/')}`;
101
+ return { url: `${MINTLIFY_HOST}${path}`, path, flavour: flavourOf(url) };
102
+ }
103
+ /**
104
+ * Maps a Mintlify URL back to its dev.wix.com docs URL, keeping `?apiView=SDK`.
105
+ * For the REST portal the paths are the same, so the backends that only know
106
+ * dev.wix.com (ingvar, the docs menu) can serve it.
107
+ */
108
+ export function toDevWixUrl(rawUrl) {
109
+ const url = new URL(rawUrl);
110
+ const segments = cleanPath(url.pathname).split('/').filter(Boolean);
111
+ if (segments.length > 0) {
112
+ segments[0] = TAB_TO_PORTAL_SLUG[segments[0]] ?? segments[0];
113
+ }
114
+ const query = flavourOf(url) === 'sdk' ? '?apiView=SDK' : '';
115
+ return `${DEV_WIX_DOCS}/${segments.join('/')}${query}`;
116
+ }
117
+ const checkCache = new Map();
118
+ /** Caches a page check, dropping expired entries, then the oldest, once full. */
119
+ function rememberCheck(path, result) {
120
+ const now = Date.now();
121
+ checkCache.delete(path);
122
+ if (checkCache.size >= MAX_CHECK_ENTRIES) {
123
+ for (const [key, entry] of checkCache) {
124
+ if (entry.expires <= now)
125
+ checkCache.delete(key);
126
+ }
127
+ }
128
+ while (checkCache.size >= MAX_CHECK_ENTRIES) {
129
+ const oldest = checkCache.keys().next().value;
130
+ if (oldest === undefined)
131
+ break;
132
+ checkCache.delete(oldest);
133
+ }
134
+ checkCache.set(path, { result, expires: now + CHECK_TTL_MS });
135
+ }
136
+ /** For tests. */
137
+ export function mintlifyCheckCacheSize() {
138
+ return checkCache.size;
139
+ }
140
+ /** For tests. */
141
+ export function clearMintlifyCheckCache() {
142
+ checkCache.clear();
143
+ }
144
+ async function headOnce(url) {
145
+ return fetch(url, {
146
+ method: 'HEAD',
147
+ redirect: 'manual',
148
+ signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
149
+ });
150
+ }
151
+ /**
152
+ * Asks the Mintlify site whether a page exists, following its own redirects
153
+ * (moves, and containers that send you to their first page) without
154
+ * following them blindly: a page is a 200 at the end of at most 3 hops.
155
+ */
156
+ export async function checkMintlifyPage(path) {
157
+ const cached = checkCache.get(path);
158
+ if (cached && cached.expires > Date.now()) {
159
+ return cached.result;
160
+ }
161
+ if (cached)
162
+ checkCache.delete(path);
163
+ let result;
164
+ try {
165
+ let current = path;
166
+ let hops = 0;
167
+ for (;;) {
168
+ const response = await headOnce(`${MINTLIFY_HOST}${current}`);
169
+ if (response.status === 200) {
170
+ result = {
171
+ outcome: hops === 0 ? 'identity' : 'redirect',
172
+ path: current
173
+ };
174
+ break;
175
+ }
176
+ const location = response.headers.get('location');
177
+ if (response.status >= 300 &&
178
+ response.status < 400 &&
179
+ location &&
180
+ hops < MAX_REDIRECTS) {
181
+ current = cleanPath(new URL(location, MINTLIFY_HOST).pathname);
182
+ hops++;
183
+ continue;
184
+ }
185
+ result = { outcome: response.status === 404 ? 'missing' : 'error' };
186
+ break;
187
+ }
188
+ }
189
+ catch (error) {
190
+ logger.error(`[mintlify] page check failed for ${path}: ${error}`);
191
+ result = { outcome: 'error' };
192
+ }
193
+ rememberCheck(path, result);
194
+ return result;
195
+ }
196
+ /**
197
+ * Resolves one docs link to its Mintlify page. With `verify: false` the
198
+ * mapping is by rule only, which suits menu links (the menu and the Mintlify
199
+ * navigation come from the same Portal Service tree).
200
+ */
201
+ export async function resolveDocsLink(rawUrl, { verify = true } = {}) {
202
+ const candidate = toMintlifyCandidate(rawUrl);
203
+ if (!candidate) {
204
+ return { outcome: 'not-docs', url: rawUrl };
205
+ }
206
+ const tab = candidate.path.split('/')[1];
207
+ if (!MINTLIFY_TABS.has(tab)) {
208
+ return { outcome: 'other-portal', url: rawUrl };
209
+ }
210
+ const query = candidate.flavour === 'sdk' ? '?apiView=SDK' : '';
211
+ if (!verify) {
212
+ return { outcome: 'identity', url: `${candidate.url}${query}` };
213
+ }
214
+ const check = await checkMintlifyPage(candidate.path);
215
+ if (check.path) {
216
+ return {
217
+ outcome: check.outcome,
218
+ url: `${MINTLIFY_HOST}${check.path}${query}`
219
+ };
220
+ }
221
+ return { outcome: check.outcome, url: rawUrl };
222
+ }
223
+ // A docs link inside text: markdown targets, bare URLs and "fallback::" forms.
224
+ // Stops at whitespace and markdown/HTML delimiters.
225
+ const DOCS_LINK_PATTERN = /https:\/\/(?:dev\.wix\.com\/docs|wix-internal\.mintlify\.(?:site|app))\/[^\s)\]"'<>`]*/g;
226
+ function trimTrailingPunctuation(url) {
227
+ return url.replace(/[.,;:!?*]+$/, '');
228
+ }
229
+ // A Mintlify page link inside text, with the same delimiters as DOCS_LINK_PATTERN.
230
+ const MINTLIFY_LINK_PATTERN = /https:\/\/wix-internal\.mintlify\.(?:site|app)\/[^\s)\]"'<>`]*/g;
231
+ /**
232
+ * Maps every Mintlify page link in `text` back to its dev.wix.com docs URL, keeping
233
+ * `?apiView=SDK` and the anchor. Run before {@link rewriteDocsLinks} on text whose
234
+ * Mintlify links came by rule rather than from a checked page (SearchWixAPISpec's
235
+ * index), so a page the site doesn't have ends up on dev.wix.com, not a dead link.
236
+ */
237
+ export function toDevWixHostText(text) {
238
+ return text.replace(MINTLIFY_LINK_PATTERN, (match) => {
239
+ const link = trimTrailingPunctuation(match);
240
+ let url;
241
+ try {
242
+ url = new URL(link);
243
+ }
244
+ catch {
245
+ return match;
246
+ }
247
+ // Only docs pages: other paths on the host (llms.txt, assets) have no
248
+ // dev.wix.com twin, and rewriteDocsLinks leaves them as they are.
249
+ if (!MINTLIFY_TABS.has(url.pathname.split('/')[1] ?? ''))
250
+ return match;
251
+ return toDevWixUrl(link) + url.hash + match.slice(link.length);
252
+ });
253
+ }
254
+ async function mapWithConcurrency(items, limit, fn) {
255
+ const results = new Array(items.length);
256
+ let next = 0;
257
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
258
+ while (next < items.length) {
259
+ const index = next++;
260
+ results[index] = await fn(items[index]);
261
+ }
262
+ });
263
+ await Promise.all(workers);
264
+ return results;
265
+ }
266
+ /**
267
+ * Rewrites every docs link in `text` to its Mintlify page. Links with no
268
+ * Mintlify page stay as they are, so the agent can still read them through
269
+ * today's backends. Logs one coverage line per call.
270
+ */
271
+ export async function rewriteDocsLinks(text, { verify = true, label = 'text' } = {}) {
272
+ const found = [
273
+ ...new Set((text.match(DOCS_LINK_PATTERN) ?? []).map(trimTrailingPunctuation))
274
+ ];
275
+ const stats = {
276
+ links: found.length,
277
+ byOutcome: {},
278
+ misses: []
279
+ };
280
+ if (found.length === 0) {
281
+ return { text, stats };
282
+ }
283
+ const resolved = await mapWithConcurrency(found, CHECK_CONCURRENCY, (url) => resolveDocsLink(url, { verify }));
284
+ const replacements = new Map();
285
+ found.forEach((url, i) => {
286
+ const { outcome, url: target } = resolved[i];
287
+ stats.byOutcome[outcome] = (stats.byOutcome[outcome] ?? 0) + 1;
288
+ if (outcome === 'identity' || outcome === 'redirect') {
289
+ if (target !== url)
290
+ replacements.set(url, target);
291
+ }
292
+ else if (outcome !== 'not-docs') {
293
+ stats.misses.push(`${outcome} ${url}`);
294
+ }
295
+ });
296
+ // Rewrite each match in place, so a link never changes through a shorter
297
+ // link it starts with.
298
+ const rewritten = text.replace(DOCS_LINK_PATTERN, (match) => {
299
+ const url = trimTrailingPunctuation(match);
300
+ const target = replacements.get(url);
301
+ return target ? target + match.slice(url.length) : match;
302
+ });
303
+ logger.log(`[mintlify] ${label}: ${stats.links} links ${JSON.stringify(stats.byOutcome)}` +
304
+ (stats.misses.length
305
+ ? ` misses: ${stats.misses.slice(0, 10).join(' | ')}`
306
+ : ''));
307
+ return { text: rewritten, stats };
308
+ }
309
+ // The index banner Mintlify puts at the top of every .md page.
310
+ const INDEX_BANNER = /^> ## Documentation Index\n(?:>.*\n)*\n?/;
311
+ // Mintlify inlines the source of every imported snippet component
312
+ // (`export const WixEndpoint = (...) => { ... };`): code, not docs.
313
+ const INLINED_SNIPPET = /^export const \w+ = [\s\S]*?^};\n/gm;
314
+ const REST_TAB = /<Tab title="REST">([\s\S]*?)<\/Tab>/;
315
+ const SDK_TAB = /<Tab title="SDK">([\s\S]*?)<\/Tab>/;
316
+ /**
317
+ * Light cleanup of a method page's .md: drop inlined snippet source, and keep
318
+ * only the tab the reader asked for (REST, or SDK for `?apiView=SDK`). A
319
+ * method page carries both, which roughly doubles its size. Pages without
320
+ * exactly one REST and one SDK tab are left as they are.
321
+ */
322
+ export function cleanMintlifyMarkdown(markdown, flavour) {
323
+ const text = markdown.replace(INLINED_SNIPPET, '');
324
+ const rest = text.match(REST_TAB);
325
+ const sdk = text.match(SDK_TAB);
326
+ if (!rest || !sdk || (text.match(/<Tab title=/g) ?? []).length !== 2) {
327
+ return text;
328
+ }
329
+ const start = text.lastIndexOf('<Tabs>', rest.index);
330
+ const end = text.indexOf('</Tabs>', sdk.index);
331
+ if (start === -1 || end === -1) {
332
+ return text;
333
+ }
334
+ const kept = (flavour === 'sdk' ? sdk : rest)[1];
335
+ const note = flavour === 'sdk'
336
+ ? '_This is the SDK view. Drop `?apiView=SDK` from the URL for the REST view._'
337
+ : '_This is the REST view. Add `?apiView=SDK` to the URL for the JavaScript SDK view._';
338
+ return `${text.slice(0, start)}${note}\n\n${kept.trim()}\n${text.slice(end + '</Tabs>'.length)}`;
339
+ }
340
+ // A docs URL in tool text that the Mintlify site has: API Reference, or a `…` placeholder.
341
+ // Skills URLs (dev.wix.com/docs/skills) are the skills registry and stay.
342
+ const DOCS_URL_IN_TEXT = /https:\/\/dev\.wix\.com\/docs\/(?=api-reference\b|\.\.\.)/g;
343
+ /**
344
+ * Tool and parameter descriptions with their docs URLs on the Mintlify host. The
345
+ * model copies the examples' host when it cites a page, so with dev.wix.com in
346
+ * the descriptions it rewrites Mintlify links back to dev.wix.com.
347
+ */
348
+ export function toMintlifyHostText(text) {
349
+ return text.replace(DOCS_URL_IN_TEXT, `${MINTLIFY_HOST}/`);
350
+ }
351
+ // A Markdown link or an href to a path on the same site: `](/…` or `href="/…`.
352
+ const SITE_RELATIVE_LINK = /(\]\(|href=["'])\/(?!\/)/g;
353
+ /**
354
+ * Mintlify's .md links the site's own pages by path (`[Order Object](/api-reference/…)`).
355
+ * Make them absolute, so the model doesn't have to guess the host (it picks dev.wix.com).
356
+ */
357
+ export function absolutizeSiteLinks(markdown) {
358
+ return markdown.replace(SITE_RELATIVE_LINK, (_, open) => `${open}${MINTLIFY_HOST}/`);
359
+ }
360
+ /**
361
+ * Reads a page from the Mintlify site as Markdown. Returns null when the site
362
+ * has no page for the URL, so the caller can fall back to today's backend.
363
+ */
364
+ export async function readMintlifyMarkdown(rawUrl) {
365
+ const resolved = await resolveDocsLink(rawUrl);
366
+ if (resolved.outcome !== 'identity' && resolved.outcome !== 'redirect') {
367
+ return null;
368
+ }
369
+ const pageUrl = new URL(resolved.url);
370
+ const response = await fetch(`${MINTLIFY_HOST}${pageUrl.pathname}.md`, {
371
+ headers: { accept: 'text/markdown' },
372
+ redirect: 'follow',
373
+ signal: AbortSignal.timeout(15000)
374
+ });
375
+ if (response.status === 404) {
376
+ return null;
377
+ }
378
+ if (!response.ok) {
379
+ throw new Error(`Mintlify returned ${response.status} for ${pageUrl.pathname}.md`);
380
+ }
381
+ const flavour = toMintlifyCandidate(rawUrl)?.flavour ?? 'rest';
382
+ const cleaned = cleanMintlifyMarkdown((await response.text()).replace(INDEX_BANNER, ''), flavour);
383
+ // The page body still links other pages on dev.wix.com; resolve those too.
384
+ const { text: rewritten } = await rewriteDocsLinks(cleaned, {
385
+ label: `read ${pageUrl.pathname}`
386
+ });
387
+ const markdown = absolutizeSiteLinks(rewritten);
388
+ logger.log(`[mintlify] read ${pageUrl.pathname}.md (${markdown.length} chars) for ${rawUrl}`);
389
+ return { url: `${MINTLIFY_HOST}${pageUrl.pathname}`, markdown };
390
+ }
391
+ //# sourceMappingURL=mintlify-docs.js.map
@@ -1,6 +1,6 @@
1
1
  import type { DocsTool } from './docs.js';
2
2
  export declare const DEFAULT_MAX_RESULTS = 15;
3
- export declare const runSemanticSearchAndFormat: ({ toolName, toolParams, maxResults, rerank, linesInEachResult, usePlatformizedSearch, extraDocumentTypes }: {
3
+ export declare const runSemanticSearchAndFormat: ({ toolName, toolParams, maxResults, rerank, linesInEachResult, usePlatformizedSearch, extraDocumentTypes, documentTypes }: {
4
4
  toolName: DocsTool;
5
5
  toolParams: Record<string, string>;
6
6
  maxResults?: number;
@@ -8,5 +8,6 @@ export declare const runSemanticSearchAndFormat: ({ toolName, toolParams, maxRes
8
8
  linesInEachResult?: number | undefined;
9
9
  usePlatformizedSearch?: boolean;
10
10
  extraDocumentTypes?: string[];
11
+ documentTypes?: string[];
11
12
  }) => Promise<string>;
12
13
  //# sourceMappingURL=semanticSearch.d.ts.map