Skip to main content
doubleshift-edge.v1.js, one file with no dependency, for a Cloudflare Worker: version 1.1.4, SHA-256 d4db890cbf27a2da1977d3ba76e0585ab3bf7cc78111eaddc71019527114e9d1, served at https://api.shftd2.com/edge/doubleshift-edge.v1.js. Install it with the quickstart; earlier versions are in the changelog. It exports enrichWithDoubleshift(request, response, options, ctx), which returns the page to send, and prepareOriginRequest(request, config), which drops the conditional headers from an AI agent’s GET. config is optional: with a custom agentPattern, pass { agentPattern } with the same pattern to both functions, or an agent the enrichment recognises still sends its conditional headers to your origin.

Options

An invalid value gives config_invalid with its field, before anything is read, and the page goes out unchanged. An unusable scope gives scope_invalid instead (scope).

Scope

scope is checked first, on every request. Out of scope, the Worker returns the origin’s response as it came: no call, no record, no sending.
  • Patterns: a path starting with /, exact (/articles/geely.html) or a section ending in /*: /articles/* covers /articles, /articles/ and every path under it, never /articles-other. / is the home page only, /* every page. No host, query, hash, whitespace, backslash, . segment, .. anywhere, malformed % sequence, //, or * elsewhere; at most 200 patterns per list.
  • Which pages: no exclude pattern matches and, if include is set, one of its patterns does. exclude always wins; include: [] takes no page.
  • Comparison: as routing reads the path, query, hash, trailing slash and ASCII case ignored. The request’s own path, on any host; canonicalOrigin only changes page_url.
  • In scope: routing and card eligibility decide. A path in scope does not guarantee a card.
  • A scope that cannot be used never means every page: nothing is processed, and one scope_invalid diagnostic is written per isolate and per reason.
A copy older than 1.1.4 ignores scope silently: download the file again and check its SHA-256.

What it does with the page

Only an AI agent’s GET answered 200 text/html is touched. Nothing reaches the agent before the decision, taken at the first container: one call, GET /v1/fragment with format=json, X-Agent-UA and the page’s record_id in X-DS-Record-Id, redirects not followed. page_url never carries the query string. No container in the first 256 KiB, or within 800 ms of the origin’s headers: no call. With a card, the enriched page loses its validators, Age, Expires, length, encoding and origin cache directives, takes cachePolicy and adds Vary: User-Agent. Otherwise the page goes out as the origin sent it. invalid_card reasons: content_type, empty, too_large (over 64 KiB), read_failed, json, or the first refused field, from payload to source.newsroom_url (card format).

Delivery records and logs

Each agent page in scope normally writes one record, event: "doubleshift_edge"; a fallback internal_error may follow with the same record_id. Fields: version; level (info for served and no_fill, error for configuration, body, origin, stream and internal failures, else warn); outcome; agent, the marker found; page_url; record_id, a UUIDv7 sent in X-DS-Record-Id; ts, ISO 8601 UTC; duration_ms until the record. After an answer: status, api_rid (its X-Request-Id), api_ms (the call alone). With a card: brand (120 characters at most), resolution (rule, default, id or other), card_written (the runtime took the card’s end for sending, not a network acknowledgement). reason, field, limit and error are fixed codes.

Direct delivery logs

With logsToken, each record also goes to doubleshift through ctx.waitUntil, the token in Authorization only, 10 s per attempt. A timeout, a network error or a 503 gets one more attempt 2 s later; nothing is queued. An invalid agentPattern, logsToken or beaconUrl is checked before the sender exists, so its record stays local; with a valid token and a usable ctx, any other config_missing or config_invalid record is sent like the others. Records carry no article HTML or cookies, and both secrets are redacted. Contract: direct delivery logs API; which transport: delivery logs.

Diagnostics

event: "doubleshift_edge_diag" with a kind, written through log, never sent.
A sending diagnostic
Success is silent, and so is a missing or empty token.

Your own log function

log(record) replaces console output for records and diagnostics. Forward records with every field: record_id links a diagnostic that carries one to the page, api_rid to the delivery. Send only doubleshift_edge records to doubleshift. With logsToken, your changes do not reach what is sent. A function that stops writing to the console also removes the records from what Logpush exports.