feat(sdk): paste-and-go AssetRef with scriptTag/linkTag/imgTag emitters#118
Merged
Conversation
v1.45 best-DX: agents call blobs.put with immutable:true and paste asset.scriptTag() / asset.linkTag() / asset.imgTag() into generated HTML. Each emitter returns a ready-to-use tag with src/href set to a content-hashed URL on pr-<public_id>.run402.com (the host guaranteed to work through the v1.33 CDN), integrity set to the SRI hash, and crossorigin set so browsers verify the bytes.
AssetRef widening:
- cdnUrl: the immutable-form auto-subdomain URL — what scriptTag/linkTag/imgTag bind to. Null on non-immutable or private uploads.
- cdnMutableUrl: mutable-form auto-subdomain URL. Eventual-consistency caveats apply; prefer cdnUrl in code.
- url / immutable_url / immutableUrl (preferred-host forms) — kept for backward compat and direct-API consumers (e.g. blobs.get). Currently NOT served through the v1.33 CDN behavior on claimed subdomains / custom domains; followup tracked separately.
Tag emitter ergonomics:
- scriptTag({ type?, defer?, async? }): bakes integrity + crossorigin
- linkTag({ rel = 'stylesheet', as? }): bakes integrity + crossorigin
- imgTag(alt = ''): no SRI (HTML5 spec), but URL is content-hashed so still stable across re-deploys
- HTML attribute escaping for url + alt
- Throw with actionable hint when called on non-immutable uploads
Tests:
- 4 new test cases (immutable widening, tag emitter shapes, HTML escaping, non-immutable error)
- Existing 22 tests still green; total now 25
Coordinated with run402-private: the gateway emits the new cdn_url + cdn_immutable_url fields on the upload-completion response. Older gateway versions (without the new fields) still work — the SDK leaves cdnUrl/cdnMutableUrl null and the tag emitters throw with the immutable-only hint, which surfaces on the first call rather than during upload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…async-decode
Push the agent-DX defaults all the way: with these on, the agent's typical flow is one line and gets every modern best practice for free.
Defaults flipped:
1. BlobPutOptions.immutable defaults to true. The cdnUrl + sri + tag emitters all require an immutable upload, so the v1.45 default reaches for the best path. Cost: one client-side SHA-256 pass, dominated by network for typical asset sizes (< 1 MB). Storage cost: +128 bytes (one blob_url_refs row). Pass { immutable: false } to opt out for very large uploads where you specifically don't need a content-hashed URL.
2. scriptTag() defaults defer: true. Modern best practice — non-render-blocking when placed in <head>, no-op at end of <body>. async: true overrides defer (the two are mutually exclusive per HTML spec). Pass { defer: false } for the rare sync-required case.
3. imgTag() defaults loading="lazy" + decoding="async". Lazy is harmless for above-fold images (browsers handle the heuristic) and a flat win for the much more common below-fold case. Async decoding moves the decode off the main thread. Both are baseline-supported in all major browsers.
linkTag intentionally NOT changed — crossorigin always emitted because we always emit integrity (SRI requires CORS mode), AND for rel="preload" matching crossorigin is what lets the browser dedupe with the eventual fetch instead of double-fetching.
Tests:
- New: 'defaults to immutable: true' covers the v1.45 default path + cdnUrl/sri/scriptTag work without the agent passing { immutable: true }
- Updated: scriptTag/linkTag/imgTag emitter test asserts the new default attributes (defer, loading=lazy, decoding=async) and the explicit-opt-out paths (defer: false, async: true overriding defer)
- Updated: existing tests that checked sha256=undefined / immutable=false explicitly pass { immutable: false } to preserve their intent
- All 26 SDK tests green
Docs:
- SKILL.md and cli/llms-cli.txt updated to reflect immutable=true as the default and remove the 'always pass { immutable: true }' guidance (no longer needed)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
e66b3cd to
b5827a6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The agent-DX rewrite of
client.blobs.putreturn shape. The agent's flow becomes one line:No URL-form decision (just paste it), no SRI plumbing (baked in), no
wait_for_cdn_freshnessfollow-up (URL is content-addressed → always served correctly first try).Changes
AssetRefcdnUrl— content-addressed URL onpr-<public_id>.run402.com, the host guaranteed to work through the v1.33/_blob/*CDN behavior. Null on non-immutable or private uploads.cdnMutableUrl— mutable form on auto-subdomain. Eventual-consistency caveats; prefercdnUrlfor generated code.scriptTag(opts?)/linkTag(opts?)/imgTag(alt?)— paste-and-go HTML tag emitters. Throw with an actionable hint on non-immutable uploads.url,immutable_url,immutableUrl,etag,sri,contentDigest,cdn) kept unchanged.Tag emitters
scriptTag({ type?, defer?, async? })— emits<script>withintegrity+crossorigin.linkTag({ rel = "stylesheet", as? })— emits<link>withintegrity+crossorigin. Supportspreload/prefetchviarel.imgTag(alt = "")— emits<img>withsrc+alt. Nointegrity(HTML5 doesn't support SRI on<img>), but the URL is content-hashed so it's still stable across re-deploys.{ immutable: true }.Docs
SKILL.md—blob_putrewritten to lead with the tag-emitter flow. AssetRef field reference reorganized so the agent-DX fields come first.cli/llms-cli.txt— same agent-DX guidance for the CLI / SDK reference section.Coordinated change
Pairs with the gateway PR run402-private#73 which emits
cdn_url+cdn_immutable_urlon the upload-completion response. The SDK falls back gracefully against older gateway versions:cdnUrlis null and the tag emitters throw with an actionable hint.Test plan
npx tsc --noEmitclean (SDK + MCP + CLI)cdnUrlwidening, tag-emitter shape, HTML attribute escaping, non-immutable error)client.blobs.put,get,ls,rm,sign,diagnoseUrl,waitFreshFollowup (separate PR)
Make claimed subdomains and custom domains serve
/_blob/*through the v1.33 CDN. Today they 404 fromBlobRoutingV2because the KVS values lackproject_id. The SDK steers agents around this via the auto-subdomain URL — but it's worth fixing soresult.url(preferred host) becomes embed-safe too.🤖 Generated with Claude Code