diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/chat/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/chat/route.ts index 78f4a4e7fd..23a53007ef 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/chat/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/chat/route.ts @@ -13,7 +13,7 @@ import { } from "@fern-docs/search-server/turbopuffer"; import { COOKIE_FERN_TOKEN, withoutStaging } from "@fern-docs/utils"; import { embed, EmbeddingModel, streamText, tool } from "ai"; -import { initLogger, traced, wrapAISDKModel } from "braintrust"; +import { initLogger, wrapAISDKModel } from "braintrust"; import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; @@ -86,54 +86,51 @@ export async function POST(req: NextRequest) { date: new Date().toDateString(), documents, }); - // eslint-disable-next-line @typescript-eslint/await-thenable - const result = await traced(() => - streamText({ - model: languageModel, - system, - messages, - maxSteps: 10, - maxRetries: 3, - tools: { - search: tool({ - description: - "Search the knowledge base for the user's query. Semantic search is enabled.", - parameters: z.object({ - query: z.string(), - }), - async execute({ query }) { - const response = await runQueryTurbopuffer(query, { - embeddingModel, - namespace, - authed: user != null, - roles: user?.roles ?? [], - }); - return response.map((hit) => { - const { domain, pathname, hash } = hit.attributes; - const url = `https://${domain}${pathname}${hash ?? ""}`; - return { url, ...hit.attributes }; - }); - }, + const result = streamText({ + model: languageModel, + system, + messages, + maxSteps: 10, + maxRetries: 3, + tools: { + search: tool({ + description: + "Search the knowledge base for the user's query. Semantic search is enabled.", + parameters: z.object({ + query: z.string(), }), - }, - onFinish: async (e) => { - const end = Date.now(); - await track("ask_ai", { - languageModel: languageModel.modelId, - embeddingModel: embeddingModel.modelId, - durationMs: end - start, - domain, - namespace, - numToolCalls: e.toolCalls.length, - finishReason: e.finishReason, - ...e.usage, - }); - e.warnings?.forEach((warning) => { - console.warn(warning); - }); - }, - }) - ); + async execute({ query }) { + const response = await runQueryTurbopuffer(query, { + embeddingModel, + namespace, + authed: user != null, + roles: user?.roles ?? [], + }); + return response.map((hit) => { + const { domain, pathname, hash } = hit.attributes; + const url = `https://${domain}${pathname}${hash ?? ""}`; + return { url, ...hit.attributes }; + }); + }, + }), + }, + onFinish: async (e) => { + const end = Date.now(); + await track("ask_ai", { + languageModel: languageModel.modelId, + embeddingModel: embeddingModel.modelId, + durationMs: end - start, + domain, + namespace, + numToolCalls: e.toolCalls.length, + finishReason: e.finishReason, + ...e.usage, + }); + e.warnings?.forEach((warning) => { + console.warn(warning); + }); + }, + }); const response = result.toDataStreamResponse(); response.headers.set("Access-Control-Allow-Origin", "*"); diff --git a/packages/fern-docs/bundle/src/server/withInitialProps.ts b/packages/fern-docs/bundle/src/server/withInitialProps.ts index 6d4bd39c89..08ed2c019d 100644 --- a/packages/fern-docs/bundle/src/server/withInitialProps.ts +++ b/packages/fern-docs/bundle/src/server/withInitialProps.ts @@ -334,14 +334,13 @@ export async function withInitialProps({ definition: docs.definition, edgeFlags, scope: { - props: { - authed: authState.authed, - user: authState.authed ? authState.user : undefined, - // frontmatter is already available under `{frontmatter}`, so this adds a new scope variable {props} - // note: do NOT override `props.components` - version: found?.currentVersion?.versionId, - tab: found?.currentTab?.title, - }, + authed: authState.authed, + user: authState.authed ? authState.user : undefined, + // frontmatter is already available under `{frontmatter}`, so this adds a new scope variable {props} + // note: do NOT override `props.components` + version: found?.currentVersion?.versionId, + tab: found?.currentTab?.title, + slug: slug, }, replaceSrc: resolveFileSrc, }); diff --git a/packages/fern-docs/local-preview-bundle/src/utils/getDocsPageProps.ts b/packages/fern-docs/local-preview-bundle/src/utils/getDocsPageProps.ts index 1491263bbd..c5ed93fce8 100644 --- a/packages/fern-docs/local-preview-bundle/src/utils/getDocsPageProps.ts +++ b/packages/fern-docs/local-preview-bundle/src/utils/getDocsPageProps.ts @@ -146,12 +146,10 @@ export async function getDocsPageProps( files: docs.definition.jsFiles, scope: { env: "development", - props: { - authed: false, - user: undefined, - version: node.currentVersion?.versionId, - tab: node.currentTab?.title, - }, + authed: false, + user: undefined, + version: node.currentVersion?.versionId, + tab: node.currentTab?.title, }, // inject the file url and dimensions for images and other embeddable files diff --git a/packages/fern-docs/mdx/package.json b/packages/fern-docs/mdx/package.json index d90e2af0a4..eb41429455 100644 --- a/packages/fern-docs/mdx/package.json +++ b/packages/fern-docs/mdx/package.json @@ -46,6 +46,7 @@ "@types/unist": "^3.0.3", "collapse-white-space": "^2.1.0", "es-toolkit": "^1.27.0", + "estree-util-is-identifier-name": "^3.0.0", "estree-util-value-to-estree": "^3.2.1", "estree-walker": "^3.0.3", "github-slugger": "^2.0.0", diff --git a/packages/fern-docs/mdx/src/plugins/index.ts b/packages/fern-docs/mdx/src/plugins/index.ts index 26d371ca4f..c200081296 100644 --- a/packages/fern-docs/mdx/src/plugins/index.ts +++ b/packages/fern-docs/mdx/src/plugins/index.ts @@ -1,5 +1,6 @@ export * from "./rehype-acorn-error-boundary"; export * from "./rehype-mdx-class-style"; export * from "./rehype-squeeze-paragraphs"; +export * from "./remark-inject-esm"; export * from "./remark-sanitize-acorn"; export * from "./remark-squeeze-paragraphs"; diff --git a/packages/fern-docs/mdx/src/plugins/remark-inject-esm.ts b/packages/fern-docs/mdx/src/plugins/remark-inject-esm.ts new file mode 100644 index 0000000000..a2f9409760 --- /dev/null +++ b/packages/fern-docs/mdx/src/plugins/remark-inject-esm.ts @@ -0,0 +1,62 @@ +/** + * A remark plugin to inject an ESM export into the MDX file. + * + * Inspired by https://github.com/remcohaszing/remark-mdx-frontmatter/blob/main/src/remark-mdx-frontmatter.ts + */ + +import type { Statement } from "estree"; +import { name as isIdentifierName } from "estree-util-is-identifier-name"; +import { valueToEstree } from "estree-util-value-to-estree"; +import type { Root } from "mdast"; +import type { Plugin } from "unified"; + +export interface RemarkInjectEsmOptions { + scope: Record; +} + +/** + * A remark plugin to inject scope of variables into the MDX file. + * + * @param options Optional options to configure the output. + * @returns A unified transformer. + */ +export const remarkInjectEsm: Plugin<[RemarkInjectEsmOptions?], Root> = ( + { scope } = { scope: {} } +) => { + const keys = Object.keys(scope); + if (keys.some((key) => !isIdentifierName(key))) { + throw new Error( + `Scope keys should be valid identifiers, got: ${JSON.stringify(keys)}` + ); + } + + return (ast) => { + if (Object.keys(scope).length === 0) { + return; + } + + ast.children.unshift({ + type: "mdxjsEsm", + value: "", + data: { + estree: { + type: "Program", + sourceType: "module", + body: Object.entries(scope).map( + ([key, value]): Statement => ({ + type: "VariableDeclaration", + kind: "const", + declarations: [ + { + type: "VariableDeclarator", + id: { type: "Identifier", name: key }, + init: valueToEstree(value, { preserveReferences: true }), + }, + ], + }) + ), + }, + }, + }); + }; +}; diff --git a/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler-component.tsx b/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler-component.tsx index d0b79576e2..cda8b5b719 100644 --- a/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler-component.tsx +++ b/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler-component.tsx @@ -6,25 +6,17 @@ import { createMdxComponents } from "../components"; export const MdxBundlerComponent = ({ code, - scope, - frontmatter, jsxRefs, }: Exclude): ReactElement => { const Component = useMemo( () => getMDXComponent(code, { - // Note: do not override `props` from scope - ...scope, - // allows us to use MDXProvider to pass components to children MdxJsReact: { useMDXComponents, }, - - // allows us to use frontmatter in the MDX - frontmatter, }), - [code, scope, frontmatter] + [code] ); return ( diff --git a/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler.ts b/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler.ts index 70a85de7ec..1f46fbc19f 100644 --- a/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler.ts +++ b/packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler.ts @@ -10,6 +10,7 @@ import { rehypeAcornErrorBoundary, rehypeMdxClassStyle, rehypeSqueezeParagraphs, + remarkInjectEsm, remarkSanitizeAcorn, remarkSqueezeParagraphs, } from "@fern-docs/mdx/plugins"; @@ -117,13 +118,15 @@ async function serializeMdxImpl( const remarkPlugins: PluggableList = [ [remarkExtractTitle, { frontmatter }], remarkSqueezeParagraphs, - remarkSanitizeAcorn, + [remarkInjectEsm, { scope }], + [remarkSanitizeAcorn], remarkGfm, remarkSmartypants, remarkMath, remarkGemoji, ]; + // inject scope variables if (options?.remarkPlugins != null) { remarkPlugins.push(...options.remarkPlugins); } @@ -185,7 +188,7 @@ async function serializeMdxImpl( engine: "mdx-bundler", code: bundled.code, frontmatter: bundled.frontmatter, - scope, + scope: {}, jsxRefs: jsxElements, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f2d7fbeef..eec21fb00b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -794,7 +794,7 @@ importers: version: 5.13.0 braintrust: specifier: ^0.0.182 - version: 0.0.182(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.682.0))(react@18.3.1)(sswr@2.1.0(svelte@5.19.2))(svelte@5.19.2)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8) + version: 0.0.182(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.723.0))(react@18.3.1)(sswr@2.1.0(svelte@5.19.2))(svelte@5.19.2)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8) cssnano: specifier: ^6.0.3 version: 6.1.2(postcss@8.4.31) @@ -1500,6 +1500,9 @@ importers: es-toolkit: specifier: ^1.27.0 version: 1.30.0 + estree-util-is-identifier-name: + specifier: ^3.0.0 + version: 3.0.0 estree-util-value-to-estree: specifier: ^3.2.1 version: 3.2.1 @@ -18674,16 +18677,6 @@ snapshots: '@smithy/types': 3.6.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.682.0)': - dependencies: - '@aws-sdk/client-sts': 3.682.0 - '@aws-sdk/core': 3.723.0 - '@aws-sdk/types': 3.723.0 - '@smithy/property-provider': 4.0.0 - '@smithy/types': 4.1.0 - tslib: 2.8.1 - optional: true - '@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.723.0)': dependencies: '@aws-sdk/client-sts': 3.723.0 @@ -24830,9 +24823,9 @@ snapshots: transitivePeerDependencies: - typescript - '@vercel/functions@1.5.2(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.682.0))': + '@vercel/functions@1.5.2(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.723.0))': optionalDependencies: - '@aws-sdk/credential-provider-web-identity': 3.723.0(@aws-sdk/client-sts@3.682.0) + '@aws-sdk/credential-provider-web-identity': 3.723.0(@aws-sdk/client-sts@3.723.0) '@vercel/kv@2.0.0': dependencies: @@ -26737,12 +26730,12 @@ snapshots: dependencies: fill-range: 7.1.1 - braintrust@0.0.182(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.682.0))(react@18.3.1)(sswr@2.1.0(svelte@5.19.2))(svelte@5.19.2)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8): + braintrust@0.0.182(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.723.0))(react@18.3.1)(sswr@2.1.0(svelte@5.19.2))(svelte@5.19.2)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8): dependencies: '@ai-sdk/provider': 1.0.4 '@braintrust/core': 0.0.76 '@next/env': 14.2.9 - '@vercel/functions': 1.5.2(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.682.0)) + '@vercel/functions': 1.5.2(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.723.0)) ai: 3.4.33(react@18.3.1)(sswr@2.1.0(svelte@5.19.2))(svelte@5.19.2)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8) argparse: 2.0.1 chalk: 4.1.2