Skip to content

docs: merge language-split agent integrations and close docs gaps - #919

Merged
davidmytton merged 9 commits into
mainfrom
docs/merge-multi-language-integrations
Sep 3, 2026
Merged

docs: merge language-split agent integrations and close docs gaps#919
davidmytton merged 9 commits into
mainfrom
docs/merge-multi-language-integrations

Conversation

@davidmytton

@davidmytton davidmytton commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Five changes, one commit each.

Merge the language-split integrations. Claude Agent SDK, Claude Managed Agents, LangChain, OpenAI Agents, and Strands Agents each had two pages and two /sdk/ routes, one per language. Each is now one page with language tabs, across get started, /guards/quick-start, and the integration pages. MERGED_GUARD_SDK_KEYS and MERGED_GUARD_SDK_LEGACY_DOC_PATHS generate redirects so every published -py, -js, and ?f= URL still resolves – including ?f=langchain-js on the nine building blocks where it was selectable, and the bare /sdk/:adapter-py route. All 28 destinations are checked against the build. Also writes the OpenAI Agents Python get started snippets, which didn't exist.

Cover every adapter in the building blocks. CrewAI, Google ADK, Strands Agents, TanStack AI, and Claude Managed Agents were missing from the rate limiting, sensitive information, content moderation, prompt injection, and AI protection guides. Each now has an example using that adapter's real deny point. The request-based reference pages stay HTTP-only – those rules have no Guard equivalent.

Refresh llms.txt and llms-full.txt. Drop the retired pages, add the SDK-scoped guard quick starts, and add AI protection, the utility packages, the integrations, and the blueprints, which were absent. llms-full gains an adapter table with the import path and deny point per framework. Every docs.arcjet.com URL checked against a built page.

Apply the writing style skill. 76 em dashes, 205 uses of e.g., please in example error strings, and via. Most sat in code comments, which the checker skips. Remaining checker output is triaged in the commit message.

Close the AI-search gaps. 14 pages had no description – the docs home and all 12 JavaScript SDK reference pages. /llms.txt, /llms-full.txt, and the 23 .md page copies were reachable only by guessing, so every page now advertises them with rel="alternate" plus a Link header. SoftwareApplication gains featureList, softwareHelp, and sameAs.

Fix the language switcher. The merged pages showed a switcher that did nothing, from two faults. Starlight <Tabs> can't be used in a snippet: SlotByFramework renders slots via Astro.slots.render(), so the markup shipped without the script defining <starlight-tabs>. The wrappers now use SelectableContent, this repo's own snippet switcher, which is a React island and persists to the same starlight-synced-tabs__language key the page-level tabs use. Separately, SelectableContent never swapped content on any page: Astro passes island slots as memo(StaticHtml, () => true), so React kept the previous slot's HTML. Keying the rendered slot fixes it, which also repairs every switcher already in the docs – picking pnpm on /sdk/next/get-started/ left npm i @arcjet/next, and picking TS on /sdk/node/filters/quick-start/ left index.js. tests/selectable-content.test.ts drives the DOM to cover both.

Left for you

  • HSTS is the only security header vercel.json doesn't set. That's a production change, not a docs change.
  • offers and aggregateRating are deliberately absent, with a test to keep them out: neither can be filled in without inventing a price or a rating.
  • Backticks in description frontmatter render literally in search snippets. ~15 pages already did this, so it's a house convention to flip or keep.

Verification

astro check --minimumSeverity warning 0 errors, 0 warnings. astro build clean, all internal links valid. 518 Playwright tests pass, no failures and no flakes. Snapshots regenerated, and the 8 screenshots for the retired pages deleted.

🤖 Generated with Claude Code

Claude and others added 6 commits September 3, 2026 14:36
Claude Agent SDK, Claude Managed Agents, LangChain, OpenAI Agents, and
Strands Agents each shipped as two adapters, two docs pages, and two
`/sdk/` routes – one per language. Readers had to pick the language from
a framework switcher before they could see whether the integration
existed at all.

Each integration is now a single page that selects the language with the
`language`-synced tabs already used elsewhere in the docs, matching the
Claude Managed Agents page that was written this way from the start.
This covers get started, /guards/quick-start, the integration reference
pages, and every building block that shipped a LangChain snippet.

- Retire the `-py` and `-js` framework keys. `MERGED_GUARD_SDK_KEYS` maps
  each retired key to its merged adapter and generates the Astro and
  Vercel redirects, so published `/guards/:adapter-py`,
  `/sdk/:adapter-py/...`, and `?f=` URLs keep working.
- Reorganize the paired snippet directories into `js/` and `py/`
  subdirectories with a tabbed wrapper at the top level, so page imports
  are unchanged.
- Add the OpenAI Agents Python get started snippets, which were missing,
  so the merged page has both tabs.
- Hoist `## 4. Start app` and the cross-reference out of the get started
  tabs to keep the heading anchor and avoid a duplicate link.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rate limiting, sensitive information, content moderation, prompt
injection, and the AI protection guides each shipped agent examples for
only some adapters. A reader on CrewAI, Google ADK, Strands Agents,
TanStack AI, or Claude Managed Agents saw the integration in the guards
section but not in the building block that describes the rule they
wanted, so the framework switcher had no entry for their stack.

Add a worked example for each of those five adapters to:

- /rate-limiting/quick-start and /rate-limiting/reference
- /sensitive-info/quick-start
- /content-moderation and /content-moderation/quick-start
- /prompt-injection/quick-start
- /ai-protection/abuse-protection, /ai-protection/budget-control,
  /ai-protection/data-loss-prevention, and
  /ai-protection/prompt-injection

Each example uses the adapter's real deny point rather than a generic
one: `register_arcjet_hooks` on `PRE_TOOL_CALL` for CrewAI,
`guardPlugin` for Google ADK, `guardMiddleware` for TanStack AI,
`guardTool` plus `guardHooks` for Strands Agents, and `guardCustomTool`
plus `guardEvents` for Claude Managed Agents. Google ADK and TanStack AI
have no `guardTool`, so their per-tool rules are selected by tool name
inside the plugin or middleware. Strands Agents and Claude Managed
Agents ship both languages, so those snippets use language tabs.

The request-based reference pages (bot protection, email validation,
filters, shield, signup protection, and the sensitive information
reference) stay HTTP-only, because those rules have no Guard equivalent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two files had drifted from the docs in both directions: they still
listed the retired language-specific agent guard pages, and they left
out whole sections that had been added since they were last reviewed.

llms.txt:

- Collapse the split agent guard entries into one per integration and
  name the language each adapter covers.
- Add the SDK-scoped agent guard quick starts next to the HTTP ones, and
  say which URL shape reaches a building block for an adapter.
- Record that the retired `-py` and `-js` URLs redirect.
- Add AI protection, which was missing from Features entirely.
- Add sections for the utility packages, the third-party integrations,
  and the blueprints, none of which appeared.

llms-full.txt:

- Add an agent framework adapter table with the import path and deny
  point per framework, so a model reading this file doesn't reach for a
  raw `guard()` call inside a framework that owns the tool loop.
- State the fail-closed contract and that human-in-the-loop confirmation
  is not a policy gate, since both are easy to get wrong.
- Fix the reference list: merged adapter links, plus the guard quick
  start, remote policies, reference, AI protection, Nosecone, and
  `@arcjet/redact` pages that were missing.

Every `docs.arcjet.com` URL in both files resolves against a built page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ran `.claude/skills/arcjet-writing-style` over every page in
`src/content/docs` and `src/snippets`, then fixed what the regex pass
misses: it skips code blocks and code files, and a reader reads a code
comment as prose.

- Replace every em dash with the house spaced en dash (76 occurrences,
  all inside code comments and example strings).
- Replace `e.g.` with `such as` or `for example` (205 occurrences, again
  all in code comments), reading each rewrite rather than substituting
  blind.
- Drop `please` from the example error strings so all of them read
  "Prompt injection detected – rephrase your message", matching the
  newer snippets instead of contradicting them. Rewrite the free-email
  message in the same voice.
- Replace `via`, which is on the word list's don't-use list, and rename
  the three headings that carried it: the Genkit, Google ADK, and
  TanStack AI "Deny ... via" headings now read as tasks and match the
  merged pages. Nothing links to the old anchors.
- Add the missing serial comma in the AI data loss prevention intro.

The remaining checker output is triaged, not ignored. It is either a
false positive on MDX import identifiers, frontmatter, badge URLs, and
verbatim RFC and video titles, or the numbered quick start headings,
which are a deliberate site-wide pattern that the `ajToc` anchors depend
on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Audited the site against the checks in
github.com/zubair-trabzada/geo-seo-claude. Most of what it looks for is
already here: llms.txt and llms-full.txt, a `Content-Signal` directive
and a sitemap reference in robots.txt, canonical URLs, `dateModified`
from git history, an `@id`-linked JSON-LD graph shared with arcjet.com,
`BreadcrumbList`, `FAQPage`, Open Graph and Twitter cards, alt text on
every image, and the security headers. These are the gaps that were
real.

Descriptions. Fourteen pages had none at all, including the docs home
and all twelve JavaScript SDK reference pages, so they shipped with no
meta description, no `og:description`, and no `description` on their
JSON-LD node. Each now says what the page covers. Ten more were too
short to stand on their own in a search result or an AI answer, so they
are expanded, and the framework integrations description is re-ordered
to put the purpose ahead of the framework list that was truncating it.

Machine-readable copies. `/llms.txt`, `/llms-full.txt`, and the 23
`text/markdown` page copies were only reachable by guessing the URL.
Every page now advertises them with `rel="alternate"`, and a `Link`
response header points at both llms files so a HEAD request finds them.

Product description. The `SoftwareApplication` node carried a name, a
category, and a description. It now also carries `featureList`,
`softwareHelp`, `operatingSystem`, and `sameAs`, so a page an assistant
lands on can enumerate what Arcjet does and where the source lives.

The audit template also asks for `offers` and `aggregateRating`. Both
are left out on purpose, and a test asserts they stay out: this is
technical documentation, and neither can be filled in without inventing
a price or a rating.

Also fixes the `e.g.` in the shared FAQ copy, which is documentation
prose even though it lives in `src/lib`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Runs `npm run pw:run -- --update-snapshots=changed` for the content
changes on this branch, and deletes the eight screenshots for the four
guard pages that the merge retired.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
arcjet-docs Ready Ready Preview Sep 3, 2026 5:47pm UTC

Request Review

@arcjet-review arcjet-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arcjet Review — 🔴 High Risk

Decision: Cannot Assess

Rationale: Review failed due to an internal error: unknown error. Escalating to human reviewers.

Review: 36a4debe | Powered by Arcjet Review

@davidmytton davidmytton changed the title docs: merge multi-language agent integrations, extend building block coverage, and refresh the AI-discoverability surface docs: merge language-split agent integrations and close docs gaps Sep 3, 2026
@arcjet-review arcjet-review Bot added needs review Awaiting human review labels Sep 3, 2026
The merged integration pages showed a JavaScript / Python switcher that
did nothing. Two separate faults, one of them long-standing.

Starlight `<Tabs>` cannot be used inside a snippet. `SlotByFramework`
renders slots with `Astro.slots.render()`, which returns an HTML string.
Astro only hoists a component's `<script>` for components it renders
into the page, so the `<starlight-tabs>` markup shipped without the
script that defines the custom element: 10 tab groups on
/sdk/claude-agent-sdk/get-started/ and no `customElements.define` for
any of them. The 102 snippet wrappers now use `SelectableContent`, the
component this repo already uses for switching snippet content, which is
a React island and so hydrates wherever it lands. It persists to
`starlight-synced-tabs__language`, the same key Starlight's tabs use, so
the snippet switcher and the page-level tabs on the integration
reference pages stay in step.

`slotIdx` has to sit on a rendered element. On a `<Fragment>` it is
dropped, and the slot order then falls back to prop order, which is not
stable – the install block on get-started came up with Python first
while every other block on the page came up with JavaScript first. The
wrappers now put it on a `<div>`, matching the package-manager blocks.

`SelectableContent` itself never swapped content, on any page. Astro
passes slots to a React island as `memo(StaticHtml, () => true)`, which
never re-renders on an update because Astro assumes island slot content
is static. Selecting a different slot is an update, so React kept the
previous slot's HTML. Keying the rendered slot forces a remount, which
the memo cannot skip. This also repairs every switcher that was already
in the docs: on /sdk/next/get-started/ picking pnpm left
`npm i @arcjet/next`, and on /sdk/node/filters/quick-start/ picking TS
left `index.js`. Both now change.

Adds tests/selectable-content.test.ts, which drives the DOM rather than
reading the select, because that is the only way any of this is visible.
It covers the package manager and JS/TS switchers that already shipped
as well as the merged language pages.

Two tests here asserted that both languages were in the DOM at once,
which was true only while the inert tabs rendered every panel. They now
assert the switcher is offered instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The merge commit redirected the `/guards/:adapter-py` pages and the
`/sdk/:adapter-py/` routes, but two cases were still broken.

`?f=` only covered the two hub pages. `langchain-js` was selectable on
nine building blocks as well – the rate limiting, sensitive information,
content moderation, prompt injection, and AI protection guides – so a
shared URL such as
`/sensitive-info/quick-start?f=langchain-js` landed on the page with no
framework selected. The doc paths each retired key accepted are now
recorded in `MERGED_GUARD_SDK_LEGACY_DOC_PATHS` and drive the redirects.
That set is frozen rather than derived, because it is a record of URLs
that were published and cannot change now the keys are gone. This takes
the retired `?f=` redirects from 4 to 18.

The bare `/sdk/:adapter-py` route pointed at a 404. Guard adapters have
no `/sdk/:sdk/` index, so the Vercel rule sending `/sdk/langchain-js` to
`/sdk/langchain` resolved to nothing. It now matches the Astro rule and
lands on the adapter's get started guide.

Verified by resolving all 28 retired-key redirect destinations against
the build, and by driving the Astro rules through the preview server.
Tests cover both: one asserts the bare route lands on a page that
exists, and one asserts every page that accepted a retired `?f=` value
still redirects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hots

CI failed on reference-nextjs-light and reference-remix-dark with the
same 5139-pixel diff on both attempts, in the same 212x24 region of both
images. That region is the table of contents: CI rendered "Overview"
highlighted as the current section and the committed snapshots did not.
Locally the highlight is present and stable from first paint, so the
committed images were the wrong ones, not CI.

They got that way through the regeneration itself. In an earlier run
these two tests failed on the 5s `toHaveScreenshot` budget while the
runner was loaded, and `--update-snapshots=changed` writes whatever the
attempt captured. A capture that timed out mid-settle was written to
disk as the new expectation, and every later run compared against it and
agreed, because the same wrong image kept being rewritten.

Regenerated both serially. They are now pixel-identical to what CI
rendered – 0 differing pixels against the actual images from the failed
run – and all 178 screenshots pass with `--retries=0`, which is the
check that would have caught this.

Raises the screenshot budget to 20s so a slow capture waits instead of
timing out. It goes on the assertion rather than in the config, because
`expect.toHaveScreenshot` takes image options only and widening
`expect.timeout` would loosen every assertion in the suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@arcjet-review arcjet-review Bot added the needs review Awaiting human review label Sep 3, 2026
@davidmytton
davidmytton added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 5a0b450 Sep 3, 2026
11 checks passed
@davidmytton
davidmytton deleted the docs/merge-multi-language-integrations branch September 3, 2026 18:32
@arcjet-review arcjet-review Bot removed the needs review Awaiting human review label Sep 3, 2026
cursor Bot pushed a commit that referenced this pull request Sep 3, 2026
Keep linux llms snapshots in sync with the new Google ADK Python
and Cloudflare Think entries after the #919 rebase.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Sep 3, 2026
Update get-started, Guard index, quick start, and framework
integrations snapshots for the new adapters after the #919 rebase.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant