@stainless-api/docs 0.1.0-beta.129 → 0.1.0-beta.130

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 (61) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/ambient.d.ts +6 -0
  3. package/eslint-suppressions.json +0 -5
  4. package/package.json +19 -15
  5. package/plugin/generateAPIReferenceLink.ts +0 -40
  6. package/plugin/index.ts +12 -0
  7. package/plugin/loadPluginConfig.ts +36 -5
  8. package/plugin/markdown/highlighter.ts +1 -1
  9. package/plugin/react/Routing.tsx +1 -85
  10. package/plugin/referencePlaceholderUtils.ts +1 -1
  11. package/plugin/routes/llms.ts +186 -0
  12. package/plugin/sidebar-utils/sidebar-builder.ts +2 -7
  13. package/plugin/specs/FileCache.ts +1 -1
  14. package/plugin/specs/index.ts +1 -6
  15. package/plugin/vendor/preview.worker.docs.js +9001 -8694
  16. package/shared/virtualModule.ts +1 -9
  17. package/stl-docs/chat/docs-chat-handler.ts +18 -0
  18. package/stl-docs/chat/hook.ts +215 -0
  19. package/stl-docs/chat/schemas.ts +70 -0
  20. package/stl-docs/chat/stainless-handler/index.ts +126 -0
  21. package/stl-docs/chat/stream-util.ts +16 -0
  22. package/stl-docs/chat/ui/AiChat.module.css +591 -0
  23. package/stl-docs/chat/ui/AiChat.tsx +188 -0
  24. package/stl-docs/chat/ui/Trigger.tsx +154 -0
  25. package/stl-docs/chat/ui/components/ChatControls.tsx +51 -0
  26. package/stl-docs/chat/ui/components/ChatEmpty.tsx +42 -0
  27. package/stl-docs/chat/ui/components/ChatLog.tsx +96 -0
  28. package/stl-docs/chat/ui/components/ChatMessage.tsx +47 -0
  29. package/stl-docs/chat/ui/components/CodeBlock.tsx +33 -0
  30. package/stl-docs/chat/ui/components/MessageFeedback.tsx +109 -0
  31. package/stl-docs/chat/ui/components/Table.tsx +15 -0
  32. package/stl-docs/chat/ui/components/ToolCall.tsx +34 -0
  33. package/stl-docs/chat/ui/components/hljs-github.css +81 -0
  34. package/stl-docs/chat/ui/scroll-manager.ts +86 -0
  35. package/stl-docs/chat/ui/types.ts +45 -0
  36. package/stl-docs/components/AiChatIsland.tsx +14 -12
  37. package/stl-docs/components/PageFrame.astro +7 -4
  38. package/stl-docs/components/headers/DefaultHeader.astro +2 -2
  39. package/stl-docs/components/headers/StackedHeader.astro +2 -2
  40. package/stl-docs/components/mintlify-compat/Accordion.astro +2 -2
  41. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +0 -4
  42. package/stl-docs/components/mintlify-compat/Columns.astro +2 -2
  43. package/stl-docs/components/mintlify-compat/Frame.astro +2 -2
  44. package/stl-docs/components/mintlify-compat/Tab.astro +2 -2
  45. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +2 -2
  46. package/stl-docs/components/mintlify-compat/callouts/Check.astro +0 -4
  47. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +0 -4
  48. package/stl-docs/components/mintlify-compat/callouts/Info.astro +0 -4
  49. package/stl-docs/components/mintlify-compat/callouts/Note.astro +0 -4
  50. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +0 -4
  51. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +0 -4
  52. package/stl-docs/components/nav-tabs/NavDropdown.astro +1 -1
  53. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +2 -2
  54. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +2 -2
  55. package/stl-docs/components/pagination/util.ts +3 -3
  56. package/stl-docs/disableCalloutSyntax.ts +1 -1
  57. package/stl-docs/index.ts +14 -28
  58. package/stl-docs/loadStlDocsConfig.ts +15 -4
  59. package/stl-docs/proseSearchIndexing.ts +2 -6
  60. package/virtual-module.d.ts +8 -17
  61. package/stl-docs/components/ClientRouterHead.astro +0 -41
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.130
4
+
5
+ ### Minor Changes
6
+
7
+ - da5debb: Move ai chat packages into stl-starlight, and extract to a generic interface
8
+
9
+ ### Patch Changes
10
+
11
+ - 3019a8b: Adds support for generating an llms.txt
12
+ - 4d3b938: Remove dead code flagged by knip
13
+ - b887dbb: Fix off-axis rotation of loading spinner in safari
14
+ - cfd8ea1: Minor fixes for Python and Ruby
15
+ - Updated dependencies [3019a8b]
16
+ - Updated dependencies [4d3b938]
17
+ - Updated dependencies [0791fb6]
18
+ - Updated dependencies [01b77d0]
19
+ - Updated dependencies [15e07d8]
20
+ - Updated dependencies [ebc039a]
21
+ - Updated dependencies [cfd8ea1]
22
+ - Updated dependencies [68a7bf8]
23
+ - @stainless-api/docs-ui@0.1.0-beta.93
24
+ - @stainless-api/ui-primitives@0.1.0-beta.53
25
+ - @stainless-api/docs-search@0.1.0-beta.46
26
+
3
27
  ## 0.1.0-beta.129
4
28
 
5
29
  ### Minor Changes
package/ambient.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import 'react';
2
+ declare module 'react' {
3
+ interface CSSProperties {
4
+ [key: `--${string}`]: string | number;
5
+ }
6
+ }
@@ -12,11 +12,6 @@
12
12
  "count": 2
13
13
  }
14
14
  },
15
- "plugin/generateAPIReferenceLink.ts": {
16
- "@typescript-eslint/no-unsafe-assignment": {
17
- "count": 2
18
- }
19
- },
20
15
  "plugin/globalJs/copy.ts": {
21
16
  "@typescript-eslint/no-explicit-any": {
22
17
  "count": 4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.129",
3
+ "version": "0.1.0-beta.130",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -42,46 +42,50 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@astrojs/markdown-remark": "^7.1.0",
45
- "@astrojs/react": "^5.0.2",
45
+ "@astrojs/react": "^5.0.3",
46
46
  "@markdoc/markdoc": "^0.5.7",
47
47
  "@stainless-api/sdk": "0.5.0",
48
- "astro-expressive-code": "^0.41.7",
48
+ "@streamparser/json-whatwg": "^0.0.22",
49
49
  "cheerio": "^1.2.0",
50
50
  "clsx": "^2.1.1",
51
- "dotenv": "17.4.0",
51
+ "dotenv": "17.4.1",
52
52
  "lucide-react": "^0.577.0",
53
+ "motion": "^12.38.0",
53
54
  "node-html-parser": "^7.1.0",
54
55
  "rehype-parse": "^9.0.1",
55
56
  "rehype-remark": "^10.0.1",
57
+ "react-markdown": "^10.1.0",
58
+ "react-syntax-highlighter": "^16.1.1",
56
59
  "remark-gfm": "^4.0.1",
57
60
  "remark-github-alerts": "^0.1.1",
58
61
  "remark-stringify": "^11.0.0",
62
+ "remend": "^1.3.0",
59
63
  "shiki": "^4.0.2",
60
64
  "unified": "^11.0.5",
61
65
  "vite-plugin-prebundle-workers": "^0.2.0",
62
66
  "web-worker": "^1.5.0",
63
- "yaml": "^2.8.3",
64
- "@stainless-api/docs-search": "0.1.0-beta.45",
65
- "@stainless-api/docs-ui": "0.1.0-beta.92",
66
- "@stainless-api/ui-primitives": "0.1.0-beta.52"
67
+ "@stainless-api/docs-search": "0.1.0-beta.46",
68
+ "@stainless-api/docs-ui": "0.1.0-beta.93",
69
+ "@stainless-api/ui-primitives": "0.1.0-beta.53"
67
70
  },
68
71
  "devDependencies": {
69
72
  "@astrojs/check": "^0.9.8",
70
- "@types/node": "24.12.0",
73
+ "@types/node": "24.12.2",
71
74
  "@types/react": "19.2.14",
72
75
  "@types/react-dom": "^19.2.3",
76
+ "@types/react-syntax-highlighter": "^15.5.13",
77
+ "astro": "^6.1.5",
73
78
  "react": "^19.2.4",
74
79
  "react-dom": "^19.2.4",
75
- "tsx": "^4.21.0",
76
80
  "typescript": "6.0.2",
77
- "vite": "^7.3.1",
78
- "vitest": "^4.1.2",
81
+ "vite": "^7.3.2",
82
+ "vitest": "^4.1.3",
79
83
  "zod": "^4.3.6",
80
- "@stainless/eslint-config": "0.1.0-beta.1",
81
- "@stainless/sdk-json": "^0.1.0-beta.9"
84
+ "@stainless/eslint-config": "0.1.0-beta.2",
85
+ "@stainless/sdk-json": "^0.1.0-beta.10"
82
86
  },
83
87
  "scripts": {
84
- "vendor-deps": "tsx scripts/vendor_deps.ts",
88
+ "vendor-deps": "node scripts/vendor_deps.ts",
85
89
  "lint": "eslint --flag unstable_native_nodejs_ts_config . --max-warnings 0",
86
90
  "sync": "astro sync",
87
91
  "check:types": "astro check",
@@ -1,47 +1,7 @@
1
1
  // This is probably temporary, but it fills in functionality needed for Mintlify imports
2
2
 
3
- import type { StarlightRouteData } from '@astrojs/starlight/route-data';
4
- import type * as SDKJSON from '@stainless/sdk-json';
5
- import { walkTree } from '@stainless-api/docs-ui/routing';
6
-
7
3
  const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_METHOD_LINK_PLACEHOLDER';
8
4
 
9
- type SidebarEntry = StarlightRouteData['sidebar'][number];
10
-
11
- type SidebarLink = Extract<SidebarEntry, { href: string }>;
12
-
13
- export function getMethodFromSDKJSON(spec: SDKJSON.Spec, endpoint: string) {
14
- for (const entry of walkTree(spec)) {
15
- if (entry.data.kind === 'http_method' && entry.data.endpoint === endpoint) {
16
- return entry.data;
17
- }
18
- }
19
- throw new Error(`Endpoint ${endpoint} not found in API`);
20
- }
21
-
22
- export function recursiveReplacePlaceholderItems(
23
- sidebar: SidebarEntry[],
24
- modifyFn: (entry: SidebarLink, props: { endpoint: string; label?: string }) => void,
25
- ) {
26
- for (const entry of sidebar) {
27
- const endpoint = 'attrs' in entry && entry.attrs?.['data-stldocs-endpoint'];
28
- if (
29
- 'attrs' in entry &&
30
- entry.attrs?.about === INTERNAL_REFERENCE_ENTRY_MARKER &&
31
- endpoint &&
32
- typeof endpoint === 'string'
33
- ) {
34
- modifyFn(entry, {
35
- endpoint,
36
- label: entry.attrs?.['data-stldocs-label'],
37
- });
38
- }
39
- if ('entries' in entry) {
40
- recursiveReplacePlaceholderItems(entry.entries, modifyFn);
41
- }
42
- }
43
- }
44
-
45
5
  type GenerateProps = string | { label?: string; endpoint: string };
46
6
 
47
7
  function normalizeGenerateProps(generateProps: GenerateProps) {
package/plugin/index.ts CHANGED
@@ -190,6 +190,16 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
190
190
  prerender: pluginConfig.experimentalPrerender ? command === 'build' : false,
191
191
  });
192
192
 
193
+ if (pluginConfig.llmsTxt.enabled) {
194
+ injectRoute({
195
+ pattern: '/llms.txt',
196
+ entrypoint: resolveSrcFile('/plugin/routes/llms.ts'),
197
+ prerender: pluginConfig.experimentalPrerender ? command === 'build' : false,
198
+ });
199
+ } else {
200
+ logger.info('LLMS.txt generation is disabled.');
201
+ }
202
+
193
203
  updateConfig({
194
204
  vite: {
195
205
  plugins: [
@@ -314,6 +324,8 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
314
324
  EXPERIMENTAL_PLAYGROUNDS: !!pluginConfig.experimentalPlaygrounds,
315
325
  EXPERIMENTAL_REQUEST_BUILDER: pluginConfig.experimentalRequestBuilder,
316
326
  STAINLESS_PROJECT: pluginConfig.stainlessProject,
327
+ LLMS_TXT_DESCRIPTION: pluginConfig.llmsTxt.description,
328
+ LLMS_TXT_DETAIL_THRESHOLD: pluginConfig.llmsTxt.detailThreshold,
317
329
  } satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
318
330
  vmMiddlewareExport,
319
331
  ].join('\n');
@@ -14,11 +14,11 @@ export type LanguageGenerateQuery = {
14
14
  list: DocsLanguage[];
15
15
  };
16
16
 
17
- export type AstroCommand = 'dev' | 'build' | 'preview' | 'sync';
17
+ type AstroCommand = 'dev' | 'build' | 'preview' | 'sync';
18
18
 
19
- export type ContentLayout = 'double-pane' | 'single-pane';
19
+ type ContentLayout = 'double-pane' | 'single-pane';
20
20
 
21
- export type VersionUserConfig = {
21
+ type VersionUserConfig = {
22
22
  version: string;
23
23
  stainlessProject: string;
24
24
  branch: string;
@@ -165,6 +165,31 @@ export type StainlessStarlightUserConfig = {
165
165
  * @default true
166
166
  */
167
167
  experimentalPrerender?: boolean;
168
+
169
+ /**
170
+ * Configuration for the generated `/llms.txt` file.
171
+ */
172
+ llmsTxt?: {
173
+ /**
174
+ * Whether to disable the generated `/llms.txt` file.
175
+ *
176
+ * @default false
177
+ */
178
+ disabled?: boolean;
179
+ /**
180
+ * A short description of the site, used as the blockquote summary at the top of the file.
181
+ * Falls back to the top-level Starlight `description` field if not set.
182
+ */
183
+ description?: string;
184
+ /**
185
+ * The maximum number of total routes (prose + API reference) at which the file switches
186
+ * from compact mode (resources only) to detailed mode (resources and
187
+ * methods).
188
+ *
189
+ * @default 2000
190
+ */
191
+ detailThreshold?: number;
192
+ };
168
193
  };
169
194
 
170
195
  // TODO: eventually? re-add support for external spec servers
@@ -198,7 +223,7 @@ function getLocalFilePaths(command: AstroCommand) {
198
223
  };
199
224
  }
200
225
 
201
- export type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
226
+ type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
202
227
 
203
228
  export type LoadedApiKey = {
204
229
  value: string;
@@ -252,6 +277,7 @@ function loadApiKey(configValue: string | undefined): LoadedApiKey | null {
252
277
  return { value: accessToken, source: 'cli' };
253
278
  }
254
279
 
280
+ /** @public but discouraged - used by cloudflare via relative import */
255
281
  export function forceLoadStainlessCredentials(): LoadedApiKey {
256
282
  const v = loadApiKey(undefined);
257
283
  if (!v) {
@@ -265,7 +291,7 @@ type AstroOptions = {
265
291
  base: string;
266
292
  };
267
293
 
268
- export type ResolvedAPIConfigEntry = {
294
+ type ResolvedAPIConfigEntry = {
269
295
  loadSpecs: () => Promise<SpecCacheResult[]>;
270
296
  };
271
297
 
@@ -385,6 +411,11 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, astroOptions
385
411
  experimentalRequestBuilder: partial.experimentalRequestBuilder ?? false,
386
412
  experimentalPrerender: partial.experimentalPrerender ?? true,
387
413
  stainlessProject: partial.stainlessProject,
414
+ llmsTxt: {
415
+ enabled: partial.llmsTxt?.disabled ?? true,
416
+ description: partial.llmsTxt?.description ?? null,
417
+ detailThreshold: partial.llmsTxt?.detailThreshold ?? 2000,
418
+ },
388
419
  };
389
420
 
390
421
  const api = loadAPIConfig(partial, astroOptions);
@@ -54,7 +54,7 @@ let astroShikiHighlighter:
54
54
  | HighlighterGeneric<BundledLanguage, BundledTheme>
55
55
  | Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>
56
56
  | null = null;
57
- export async function getAstroHighlighter() {
57
+ async function getAstroHighlighter() {
58
58
  if (astroShikiHighlighter) {
59
59
  return astroShikiHighlighter;
60
60
  }
@@ -5,22 +5,15 @@ import { astroMarkdownRenderText } from '../markdown';
5
5
  import { highlight } from '../markdown/highlighter';
6
6
 
7
7
  import type { MarkdownHeading } from 'astro';
8
- import type { StarlightRouteData } from '@astrojs/starlight/route-data';
9
8
  import type * as SDKJSON from '@stainless/sdk-json';
10
9
  import { LanguageNames, type DocsLanguage } from '@stainless-api/docs-ui/routing';
11
10
 
12
- import {
13
- generateRoute,
14
- parseStainlessPath,
15
- walkTree,
16
- getLanguageSnippet,
17
- } from '@stainless-api/docs-ui/routing';
11
+ import { parseStainlessPath, getLanguageSnippet } from '@stainless-api/docs-ui/routing';
18
12
 
19
13
  import {
20
14
  DocsProvider,
21
15
  MarkdownProvider,
22
16
  NavigationProvider,
23
- useSpec,
24
17
  type ContentPanelLayout,
25
18
  } from '@stainless-api/docs-ui/contexts';
26
19
 
@@ -40,7 +33,6 @@ import { Dropdown } from '@stainless-api/docs/components';
40
33
 
41
34
  import {
42
35
  RESOLVED_API_REFERENCE_PATH,
43
- EXPAND_RESOURCES,
44
36
  BREADCRUMB_CONFIG,
45
37
  PROPERTY_SETTINGS,
46
38
  ENABLE_CONTEXT_MENU,
@@ -61,66 +53,6 @@ import { AIDropdown } from '../../stl-docs/components/AIDropdown';
61
53
  import { ChevronsUpDownIcon } from 'lucide-react';
62
54
  import { MethodDescription } from '../components/MethodDescription';
63
55
 
64
- function isResourceNonEmpty(resource: SDKJSON.Resource) {
65
- return (
66
- Object.keys(resource.methods ?? {}).length > 0 ||
67
- Object.keys(resource.subresources ?? {}).length > 0 ||
68
- Object.keys(resource.models ?? {}).length > 0
69
- );
70
- }
71
-
72
- type SidebarEntry = StarlightRouteData['sidebar'][number];
73
-
74
- export function buildSidebar(
75
- basePath: string,
76
- language: DocsLanguage,
77
- current: string,
78
- spec: SDKJSON.Spec,
79
- resources?: Record<string, SDKJSON.Resource>,
80
- nested?: boolean,
81
- ): StarlightRouteData['sidebar'] {
82
- const totalRoutes = Array.from(walkTree(spec, false)).filter(
83
- (item) => item.data.kind === 'http_method',
84
- ).length;
85
-
86
- return Object.values(resources ?? spec.resources ?? [])
87
- .filter((resource) => isResourceNonEmpty(resource))
88
- .sort((a, b) => (a.name.startsWith('$') === b.name.startsWith('$') ? 0 : a.name.startsWith('$') ? 1 : -1))
89
- .map((resource) => {
90
- const subs = buildSidebar(basePath, language, current, spec, resource.subresources, true);
91
-
92
- const overview: SidebarEntry = {
93
- type: 'link',
94
- isCurrent: current === resource.stainlessPath,
95
- attrs: { 'data-stldocs-overview': resource.name },
96
- label: 'Overview',
97
- href: generateRoute(basePath, language, resource.stainlessPath) ?? basePath,
98
- badge: undefined,
99
- };
100
-
101
- const meths: SidebarEntry[] = Object.values(resource.methods ?? [])
102
- .filter((method) => spec.decls?.[language]?.[method.stainlessPath])
103
- .map((method) => ({
104
- type: 'link',
105
- isCurrent: current === method.stainlessPath,
106
- attrs: { 'data-stldocs-method': method.httpMethod },
107
- label: method.summary ?? method.name,
108
- href: generateRoute(basePath, language, method.stainlessPath) ?? basePath,
109
- badge: undefined,
110
- }));
111
-
112
- const shouldExpand = EXPAND_RESOURCES ?? totalRoutes < 20;
113
-
114
- return {
115
- type: 'group',
116
- label: resource.title,
117
- badge: undefined,
118
- collapsed: !shouldExpand || nested === true,
119
- entries: [...(resources ? [] : [overview]), ...meths, ...subs],
120
- };
121
- });
122
- }
123
-
124
56
  export function buildPageNavigation(resource: SDKJSON.Resource, depth: number = 2): MarkdownHeading[] {
125
57
  const output: MarkdownHeading[] = [{ depth, slug: resource.stainlessPath, text: resource.title }];
126
58
 
@@ -321,22 +253,6 @@ export function RenderSpec({
321
253
  );
322
254
  }
323
255
 
324
- export function RenderMethod({ path }: { path: string }) {
325
- const spec = useSpec();
326
- if (!spec) return null;
327
-
328
- const parsed = parseStainlessPath(path);
329
- const resource = getResourceFromSpec(path, spec);
330
-
331
- if (!resource || !parsed) {
332
- console.warn(`Could not find resource or parsed path for '${path}'`);
333
- return null;
334
- }
335
-
336
- const method = resource.methods[parsed.method!]!;
337
- return <SDKMethod method={method} />;
338
- }
339
-
340
256
  export async function getReadmeContent(spec: SDKJSON.Spec, language: DocsLanguage) {
341
257
  const repoUrl = spec.metadata?.[language]?.repo_url;
342
258
 
@@ -21,7 +21,7 @@ export function makePlaceholderItems(id: number) {
21
21
 
22
22
  type StarlightConfig = Parameters<typeof starlight>[0];
23
23
 
24
- export type SidebarConfigEntry = Exclude<StarlightConfig['sidebar'], undefined>[number];
24
+ type SidebarConfigEntry = Exclude<StarlightConfig['sidebar'], undefined>[number];
25
25
 
26
26
  export function getAPIReferencePlaceholderItemFromSidebarConfig(
27
27
  sidebar: SidebarConfigEntry[],
@@ -0,0 +1,186 @@
1
+ import type { APIRoute } from 'astro';
2
+ import { getCollection } from 'astro:content';
3
+ import { base as ASTRO_BASE } from 'astro:config/server';
4
+ import { SITE_TITLE, API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
5
+ import { LLMS_TXT_DESCRIPTION, LLMS_TXT_DETAIL_THRESHOLD } from 'virtual:stl-starlight-virtual-module';
6
+ import { getSDKJSONInSSR } from '../specs/fetchSpecSSR';
7
+ import Markdoc from '@markdoc/markdoc';
8
+ import type { Node } from '@markdoc/markdoc';
9
+ import * as md from '@stainless-api/docs-ui/markdown/md';
10
+ import type * as SDKJSON from '@stainless/sdk-json';
11
+ import { generateRoute, walkTree } from '@stainless-api/docs-ui/routing';
12
+
13
+ export const prerender = true;
14
+
15
+ function joinUrlParts(...parts: (string | boolean | null | undefined)[]) {
16
+ return (
17
+ '/' +
18
+ parts
19
+ .map((p) => {
20
+ if (typeof p === 'string') {
21
+ return p.split('/');
22
+ }
23
+ return p;
24
+ })
25
+ .flat()
26
+ .filter(Boolean)
27
+ .join('/')
28
+ );
29
+ }
30
+
31
+ export type ProsePageEntry = {
32
+ id: string;
33
+ title: string;
34
+ description?: string;
35
+ };
36
+
37
+ function isResourceEmpty(resource: SDKJSON.Resource) {
38
+ return Object.values(resource.methods).length < 1 && Object.values(resource.subresources ?? {}).length < 1;
39
+ }
40
+
41
+ function trimPath(path: string) {
42
+ return path.endsWith('/') ? path.slice(0, -1) : path;
43
+ }
44
+
45
+ function apiHref(basePath: string, language: string, stainlessPath: string) {
46
+ const href = generateRoute(basePath, language, stainlessPath);
47
+ if (!href) return null;
48
+ return joinUrlParts(ASTRO_BASE, href, 'index.md');
49
+ }
50
+
51
+ function linkListItem(title: string, url: string, description?: string): Node {
52
+ const children = [md.link(url, title)];
53
+ if (description) children.push(md.text(`: ${description}`));
54
+ return md.item(md.inline(...children));
55
+ }
56
+
57
+ function generateProseIndex(routes: ProsePageEntry[], detailed: boolean) {
58
+ const pageEntryLink = (page: ProsePageEntry) =>
59
+ linkListItem(
60
+ page.title,
61
+ joinUrlParts(ASTRO_BASE, page.id === 'index' ? null : page.id, 'index.md'),
62
+ detailed ? page.description : undefined,
63
+ );
64
+ return [md.heading(2, 'Docs'), md.list(...routes.map(pageEntryLink))];
65
+ }
66
+
67
+ function renderMethods(basePath: string, language: string, methods: Record<string, SDKJSON.Method>) {
68
+ const output: Node[] = [];
69
+
70
+ for (const method of Object.values(methods)) {
71
+ const href = apiHref(basePath, language, method.stainlessPath);
72
+ if (href) output.push(linkListItem(method.title, href, method.summary));
73
+ }
74
+
75
+ return md.list(...output);
76
+ }
77
+
78
+ function renderResource(
79
+ basePath: string,
80
+ language: string,
81
+ detailed: boolean,
82
+ resource: SDKJSON.Resource,
83
+ ): Node {
84
+ const href = apiHref(basePath, language, resource.stainlessPath);
85
+ const output = [md.paragraph(href ? md.link(href, resource.title) : md.text(resource.title))];
86
+ if (resource.methods && detailed) output.push(renderMethods(basePath, language, resource.methods));
87
+ if (resource.subresources) output.push(renderSubs(basePath, language, detailed, resource.subresources));
88
+ return md.item(...output);
89
+ }
90
+
91
+ function renderSubs(
92
+ basePath: string,
93
+ language: string,
94
+ detailed: boolean,
95
+ resources: Record<string, SDKJSON.Resource>,
96
+ ): Node {
97
+ return md.list(
98
+ ...Object.values(resources)
99
+ .filter((res) => !isResourceEmpty(res))
100
+ .map((sub) => renderResource(basePath, language, detailed, sub)),
101
+ );
102
+ }
103
+
104
+ function renderLanguageNote(basePath: string, languages: string[]) {
105
+ return [
106
+ md.paragraph(
107
+ md.text('Links below point to language-neutral HTTP documentation. '),
108
+ md.text(
109
+ 'SDK-specific docs follow the same URL structure with the language inserted after the base path: ',
110
+ ),
111
+ md.code(`${trimPath(basePath)}/{language}/resources/...`),
112
+ md.text('.'),
113
+ ),
114
+ md.paragraph(md.text(`Available languages: ${languages.join(', ')}`)),
115
+ ];
116
+ }
117
+
118
+ function generateRefIndex(
119
+ basePath: string,
120
+ languages: string[],
121
+ language: string,
122
+ detailed: boolean,
123
+ spec: SDKJSON.Spec,
124
+ ) {
125
+ const output = [md.heading(2, 'API Reference'), ...renderLanguageNote(basePath, languages)];
126
+
127
+ for (const resource of Object.values(spec.resources)) {
128
+ if (isResourceEmpty(resource)) continue;
129
+ const href = apiHref(basePath, language, resource.stainlessPath);
130
+ output.push(md.heading(3, [href ? md.link(href, resource.title) : md.text(resource.title)]));
131
+ if (resource.methods && detailed) output.push(renderMethods(basePath, language, resource.methods));
132
+ if (resource.subresources) output.push(renderSubs(basePath, language, detailed, resource.subresources));
133
+ }
134
+
135
+ return output;
136
+ }
137
+
138
+ function generateMarkdownIndex({
139
+ siteTitle,
140
+ description,
141
+ basePath,
142
+ languages,
143
+ spec,
144
+ detailed,
145
+ proseRoutes,
146
+ }: {
147
+ siteTitle: string;
148
+ description: string | null;
149
+ basePath: string;
150
+ languages: string[];
151
+ spec: SDKJSON.Spec;
152
+ detailed: boolean;
153
+ proseRoutes: ProsePageEntry[];
154
+ }) {
155
+ const output: Node[] = [md.heading(1, siteTitle)];
156
+ if (description) output.push(md.paragraph(md.text(description)));
157
+ if (proseRoutes.length > 0) output.push(...generateProseIndex(proseRoutes, detailed));
158
+ if (languages.length > 0) output.push(...generateRefIndex(basePath, languages, 'http', detailed, spec));
159
+
160
+ const doc = new Markdoc.Ast.Node('document', {}, output);
161
+ return Markdoc.format(doc);
162
+ }
163
+
164
+ export const GET: APIRoute = async () => {
165
+ const [docsCollection, spec] = await Promise.all([getCollection('docs'), getSDKJSONInSSR('http')]);
166
+ const apiEntries = [...walkTree(spec)].filter((n) => n.data.kind !== 'model');
167
+ const proseRoutes = docsCollection.map((entry) => ({
168
+ id: entry.id,
169
+ title: entry.data.title,
170
+ description: entry.data.description,
171
+ }));
172
+
173
+ const content = generateMarkdownIndex({
174
+ siteTitle: SITE_TITLE,
175
+ description: LLMS_TXT_DESCRIPTION,
176
+ basePath: API_REFERENCE_BASE_PATH,
177
+ languages: spec.docs?.languages ?? [],
178
+ detailed: apiEntries.length + proseRoutes.length < LLMS_TXT_DETAIL_THRESHOLD,
179
+ spec,
180
+ proseRoutes,
181
+ });
182
+
183
+ return new Response(content, {
184
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
185
+ });
186
+ };
@@ -173,8 +173,8 @@ export class SidebarConfigItemsBuilder {
173
173
  const decls = this.spec.decls[this.language] ?? {};
174
174
  const decl = decls[entry.stainlessPath];
175
175
  if (decl !== undefined) {
176
- if ('ident' in decl) {
177
- return decl;
176
+ if ('ident' in decl && decl.ident !== undefined) {
177
+ return decl as MethodDecl;
178
178
  }
179
179
  }
180
180
  return null;
@@ -338,11 +338,6 @@ function forceGenerateRoute({
338
338
  return route;
339
339
  }
340
340
 
341
- export type BuildSidebarParams = {
342
- basePath: string;
343
- currentSlug: string;
344
- };
345
-
346
341
  type ToStarlightSidebarParams = {
347
342
  basePath: string;
348
343
  spec: SDKJSON.Spec;
@@ -4,7 +4,7 @@ import { readdir, readFile, rm, writeFile } from 'fs/promises';
4
4
 
5
5
  type CacheResultSource = 'memory' | 'disk' | 'generation';
6
6
 
7
- export type CacheGetResult<T> = {
7
+ type CacheGetResult<T> = {
8
8
  resultSource: CacheResultSource;
9
9
  data: T;
10
10
  filePath: string;
@@ -5,17 +5,12 @@ import type * as VirtualManifestModule from 'virtual:stainless-apis-manifest';
5
5
 
6
6
  import { makeAsyncVirtualModPlugin } from '../../shared/virtualModule';
7
7
 
8
- import { NormalizedStainlessStarlightConfig, ResolvedAPIConfigEntry } from '../loadPluginConfig';
8
+ import { NormalizedStainlessStarlightConfig } from '../loadPluginConfig';
9
9
 
10
10
  import { specCache, SpecCacheResult } from './generateSpec';
11
11
  import { AstroIntegrationLogger } from 'astro';
12
12
  import type * as SDKJSON from '@stainless/sdk-json';
13
13
 
14
- export type LoadedAPIConfigEntry = Omit<ResolvedAPIConfigEntry, 'loadSpecs'> & {
15
- specs: SpecCacheResult[];
16
- languages: SDKJSON.SpecLanguage[];
17
- };
18
-
19
14
  /**
20
15
  * A helper class to manage multiple spec cache results for a single API
21
16
  * An API may have multiple spec cache results if it has multiple languages