Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,4 @@ package-lock.json
# Generated documentation
docs/api-reference/
__*__/


.build
189 changes: 187 additions & 2 deletions CMS-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ We're using [Astro](https://astro.build/) with the [Starlight](https://starlight

### 2. Route Middleware (`src/route-middleware.ts`)

**What it does:** Filters the sidebar at buildtime so each page only shows items from its top-level group.
**What it does:** Filters the sidebar at buildtime so each page only shows items from its top-level group. For API pages (Python and TypeScript), it dynamically generates sidebars from the docs collection and computes pagination links.

**Why:** Our sidebar is organized into top-level groups (User Guide, Community, Examples, etc.). Without this middleware, every page would show the entire sidebar. This middleware scopes the sidebar to the current section, providing a cleaner navigation experience.

**Python API sidebar:** When viewing pages under `api/python/`, the middleware uses `buildPythonApiSidebar()` from `src/dynamic-sidebar.ts` to generate a nested sidebar structure based on module names (e.g., `strands.agent.agent` becomes `Agent > Agent`).

**TypeScript API sidebar:** When viewing pages under `api/typescript/`, the middleware uses `buildTypeScriptApiSidebar()` to generate a category-grouped sidebar (Classes, Interfaces, Type Aliases, Functions).

**Pagination:** For API pages, the middleware also updates `starlightRoute.pagination` using `getPrevNextLinks()` from `src/dynamic-sidebar.ts`. This ensures the previous/next navigation links at the bottom of pages work correctly with the dynamically generated sidebar.

### 3. MkDocs Snippets Plugin (`src/plugins/remark-mkdocs-snippets.ts`)

**What it does:** Processes MkDocs-style code snippet references in markdown files.
Expand Down Expand Up @@ -227,4 +233,183 @@ Used by `MarkdownContent.astro` to render frontmatter banners:
- `CommunityContributionAside.astro`
- `LanguageSupportAside.astro`

These are not meant to be imported directly in MDX files—use the frontmatter fields instead.
These are not meant to be imported directly in MDX files—use the frontmatter fields instead.

## Python API Reference Generation

The Python API reference documentation is auto-generated from the SDK source code using pydoc-markdown.

### Generation Script (`scripts/api-generation-python.py`)

**What it does:** Parses Python source code from the SDK and generates MDX documentation files.

**How to run:**
```bash
uv run scripts/api-generation-python.py
```

**Input:** `.build/sdk-python/src` (cloned SDK repository)
**Output:** `.build/api-docs/python/*.mdx`

**Filtering:**
- Skips private modules (any module path containing `_` prefix)
- Skips explicitly excluded modules (e.g., `strands.agent` which just re-exports)

**Output format:** Each module becomes a flat MDX file named `strands.module.name.mdx` with frontmatter containing the title and slug.

### Symlink Setup

The generated docs are accessed via a committed symlink:
```
src/content/docs/api/python/_generated -> ../../../../../.build/api-docs/python
```

This symlink is checked into git, so no manual setup is required. The generation script outputs to `.build/api-docs/python/`, and the symlink makes those files available to the content collection.

The index page (`src/content/docs/api/python/index.mdx`) is a permanent file (not generated) that imports the `PythonApiList` component.

### Dynamic Sidebar (`src/dynamic-sidebar.ts`)

**What it does:** Builds a hierarchical sidebar structure from Python API docs at runtime, and provides pagination utilities.

**How it works:**
1. Filters docs collection for `api/python/*` pages
2. Parses module names from page titles (e.g., `strands.agent.agent`)
3. Builds a nested tree structure based on module path segments
4. Converts tree to Starlight sidebar entries with groups and links

**Pagination utilities:**
- `flattenSidebar()` - Converts nested sidebar structure to a flat list of links
- `getPrevNextLinks()` - Finds the current page in the flattened sidebar and returns prev/next links

**Sorting:**
- Alphabetical A-Z within each level
- "Experimental" group always appears last
- Groups at depth ≥2 are collapsed by default

**Example transformation:**
```
strands.agent.agent → Agent > Agent
strands.agent.base → Agent > Base
strands.experimental.bidi.types.events → Experimental > Bidi > Types > Events
```

### Index Page Component (`src/components/PythonApiList.astro`)

**What it does:** Renders the API reference index page with a hierarchical list of all modules.

**How it works:** Uses the same `buildPythonApiSidebar()` function as the route middleware to ensure consistency between the sidebar navigation and the index page listing.

### Path Alias

Components can be imported using the `@components` alias:
```typescript
import PythonApiList from '@components/PythonApiList.astro'
```

This is configured in `tsconfig.json` under `compilerOptions.paths`.

## TypeScript API Reference Generation

The TypeScript API reference documentation is auto-generated from the SDK source code using [typedoc](https://typedoc.org/) with [typedoc-plugin-markdown](https://typedoc-plugin-markdown.org/).

### Generation Script (`scripts/api-generation-typescript.ts`)

**What it does:** Runs typedoc to generate markdown files, then post-processes them to add frontmatter.

**How to run:**
```bash
npm run sdk:generate:ts
# or
npx tsx scripts/api-generation-typescript.ts
```

**Input:** `.build/sdk-typescript/src` (cloned SDK repository)
**Output:** `.build/api-docs/typescript/{classes,interfaces,type-aliases,functions}/*.md`

### TypeDoc Configuration (`typedoc.json`)

Key settings:
- `outputFileStrategy: "members"` - Creates separate files per class/interface/type/function
- `fileExtension: ".md"` - Outputs standard markdown format
- `basePath: ".build/sdk-typescript"` - Strips build path prefix from source links
- `hideBreadcrumbs: true`, `hidePageHeader: true` - Cleaner output for Starlight integration

### Post-Processing

The generation script performs these transformations after typedoc runs:

1. **Adds frontmatter** with title, slug, and category:
```yaml
---
title: "Agent"
slug: api/typescript/Agent
category: classes
---
```

2. **Fixes relative links** to match the flat slug structure (e.g., `../interfaces/AgentData.md` → `../AgentData.md`)

3. **Deletes the generated index.md** - We use our own custom index page instead

### Flat Slugs with Category Grouping

Unlike Python API docs which use hierarchical slugs based on module paths, TypeScript API docs use flat slugs:
- URL: `/api/typescript/Agent/` (not `/api/typescript/classes/Agent/`)
- The `category` frontmatter field is used for sidebar grouping

This keeps URLs clean while still organizing the sidebar by type (Classes, Interfaces, Type Aliases, Functions).

### Symlink Setup

The generated docs are accessed via a committed symlink:
```
src/content/docs/api/typescript/_generated -> ../../../../../.build/api-docs/typescript
```

The index page (`src/content/docs/api/typescript/index.mdx`) is a permanent file that imports the `TypeScriptApiList` component.

### Dynamic Sidebar (`src/dynamic-sidebar.ts`)

**What it does:** Builds a category-grouped sidebar structure from TypeScript API docs at runtime.

**How it works:**
1. Filters docs collection for `api/typescript/*` pages
2. Groups docs by their `category` frontmatter field
3. Creates sidebar groups for Classes, Interfaces, Type Aliases, and Functions
4. Sorts entries alphabetically within each group

**Example structure:**
```
Classes
├── Agent
├── BedrockModel
└── Tool
Interfaces
├── AgentConfig
└── ToolSpec
Type Aliases
├── ContentBlock
└── ToolChoice
Functions
├── configureLogging
└── tool
```

### Index Page Component (`src/components/TypeScriptApiList.astro`)

**What it does:** Renders the API reference index page with a categorized list of all exports.

**How it works:** Uses the same `buildTypeScriptApiSidebar()` function as the route middleware to ensure consistency between the sidebar navigation and the index page listing.

### Content Collection Schema

The `category` field is defined in `src/content.config.ts`:
```typescript
extend: z.object({
// ...
category: z.string().optional(),
})
```

This allows the content collection to validate and expose the category for sidebar generation.
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,25 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "astro build",
"test": "tsc --noEmit",
"format": "prettier --write docs",
"format:check": "prettier --check docs",
"docs:clone": "rm -rf sdk-typescript && git clone https://github.com/strands-agents/sdk-typescript.git",
"docs:ts": "typedoc --options typedoc.json",
"serve": "npm run docs:clone && npm run docs:ts && mkdocs serve",
"serve": "mkdocs serve",
"clean": "rm -rf docs/api-reference/typescript node_modules",
"cms:dev": "astro dev",
"cms:start": "astro dev",
"cms:build": "npm run docs:revert && npm run docs:update && astro build",
"cms:build": "npm run sdk:clone && npm run sdk:generate && npm run docs:revert && npm run docs:update && npm run build",
"cms:preview": "astro preview",
"cms:test": "vitest run",
"docs:update": "tsx scripts/update-docs.ts",
"docs:revert": "git checkout HEAD -- docs/ && git clean -d -f src/content/docs",
"sdk:clone": "tsx scripts/clone-sdks.ts",
"sdk:generate:py": "command -v uv >/dev/null 2>&1 && uv run scripts/api-generation-python.py || (pip install pydoc-markdown>=4.8.2 && python scripts/api-generation-python.py)",
"sdk:generate:ts": "tsx scripts/api-generation-typescript.ts",
"sdk:generate": "npm run sdk:generate:py && npm run sdk:generate:ts",
"astro": "astro"
},
"author": "",
Expand All @@ -43,6 +48,7 @@
"remark-stringify": "^11.0.0",
"tsx": "^4.21.0",
"typedoc": "^0.28.14",
"typedoc-plugin-markdown": "^4.10.0",
"unified": "^11.0.5",
"unist-util-visit": "^5.1.0",
"vitest": "^4.0.18"
Expand Down
149 changes: 149 additions & 0 deletions scripts/api-generation-python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pydoc-markdown>=4.8.2",
# ]
# ///
"""Generate markdown documentation for strands-agents SDK using pydoc-markdown.

This script generates per-module markdown files in the .build/api-docs/python/ directory.

Usage:
uv run scripts/api-generation-python.py # if uv is available
pip install pydoc-markdown && python scripts/api-generation-python.py # fallback
"""

import shutil
from pathlib import Path

from pydoc_markdown import PydocMarkdown
from pydoc_markdown.contrib.loaders.python import PythonLoader
from pydoc_markdown.contrib.renderers.markdown import MarkdownRenderer
from pydoc_markdown.contrib.processors.filter import FilterProcessor
from pydoc_markdown.contrib.processors.crossref import CrossrefProcessor
from pydoc_markdown.contrib.processors.smart import SmartProcessor
from pydoc_markdown.contrib.source_linkers.git import GitSourceLinker
import docspec


class CustomGitSourceLinker(GitSourceLinker):
"""Custom source linker that returns 'Defined in: [path:line](url)' format."""

def get_source_url(self, obj: docspec.ApiObject) -> str | None:
# Get the base URL from parent
url = super().get_source_url(obj)
if not url or not obj.location:
return None

# Extract path relative to src/
path = obj.location.filename
if "src/" in path:
path = "src/" + path.split("src/")[-1]

lineno = obj.location.lineno
return f"Defined in: [{path}:{lineno}]({url})"


def generate_docs():
input_path = "./.build/sdk-python/src"
output_path = "./.build/api-docs/python"

"""Generate markdown documentation for all strands modules."""
output_dir = Path(output_path)

# Delete existing output directory to ensure clean generation
if output_dir.exists():
shutil.rmtree(output_dir)
print(f"Deleted existing output directory: {output_dir}")

output_dir.mkdir(exist_ok=True, parents=True)

# Configure the session
session = PydocMarkdown()

# Configure the Python loader
loader = PythonLoader(
search_path=[input_path],
packages=["strands"],
)
session.loaders = [loader]

# Configure processors (filter, crossref, smart)
session.processors = [
FilterProcessor(skip_empty_modules=True),
CrossrefProcessor(),
SmartProcessor(),
]

# Configure the renderer
renderer = MarkdownRenderer(
render_module_header=False,
descriptive_class_title="",
add_module_prefix=True,
render_toc=False,
source_linker=CustomGitSourceLinker(
root=".build/sdk-python/src",
url_template="https://github.com/strands-agents/sdk-python/blob/main/src/{path}#L{lineno}",
use_branch=False,
),
source_format="{url}", # URL already contains the full formatted string
)
session.renderer = renderer

# Load and process modules
modules = session.load_modules()
session.process(modules)

# Modules to exclude from documentation
excluded_modules = {
"strands.agent", # Not useful, just re-exports
}

# Generate index file
module_files = []

# Write each module to a separate file
for module in modules:
module_name = module.name

# Skip modules with underscore (private/internal modules)
# Check if any part of the module path starts with underscore
if any(part.startswith("_") for part in module_name.split(".")):
print(f"Skipping private module: {module_name}")
continue

# Skip explicitly excluded modules
if module_name in excluded_modules:
print(f"Skipping excluded module: {module_name}")
continue

# Parse module path: strands.agent.base -> strands.agent.base.mdx
parts = module_name.split(".")
simple_name = parts[-1]
filename = f"{module_name}.mdx"
filepath = output_dir / filename
slug = f"api/python/{module_name}"

# Render single module
content = renderer.render_to_string([module])

content = f"""
---
title: {module_name}
slug: {slug}
---
{content}
""".strip()

if content.strip(): # Only write non-empty files
# Because we're writing MDX we need to escape brackets so that it's not variable interpolation
content = content.replace("{", "\\{").replace("<A2A", "&gt;A2A")
filepath.write_text(content, encoding="utf-8")
module_files.append((module_name, str(filepath.relative_to(output_dir))))
print(f"Generated: {filepath}")

print(f"\nTotal modules documented: {len(module_files)}")


if __name__ == "__main__":
generate_docs()
Loading
Loading