[{"data":1,"prerenderedAt":815},["ShallowReactive",2],{"navigation_docs":3,"-guide-custom":88,"-guide-custom-surround":810},[4,48],{"title":5,"path":6,"stem":7,"children":8,"icon":47},"Guide","\u002Fguide","1.guide\u002F01.index",[9,11,15,19,23,27,31,35,39,43],{"title":10,"path":6,"stem":7},"Getting Started",{"title":12,"path":13,"stem":14},"Scrape","\u002Fguide\u002Fscrape","1.guide\u002F02.scrape",{"title":16,"path":17,"stem":18},"Screenshots and PDFs","\u002Fguide\u002Fcaptures","1.guide\u002F03.captures",{"title":20,"path":21,"stem":22},"Links and accessibility and extract","\u002Fguide\u002Freading","1.guide\u002F04.reading",{"title":24,"path":25,"stem":26},"Crawl and search","\u002Fguide\u002Fcrawl","1.guide\u002F05.crawl",{"title":28,"path":29,"stem":30},"Sessions","\u002Fguide\u002Fsessions","1.guide\u002F06.sessions",{"title":32,"path":33,"stem":34},"CLI","\u002Fguide\u002Fcli","1.guide\u002F07.cli",{"title":36,"path":37,"stem":38},"MCP and Pi and OMP","\u002Fguide\u002Fagents","1.guide\u002F08.agents",{"title":40,"path":41,"stem":42},"Errors","\u002Fguide\u002Ferrors","1.guide\u002F09.errors",{"title":44,"path":45,"stem":46},"Custom providers","\u002Fguide\u002Fcustom","1.guide\u002F10.custom","i-lucide-book-open",{"title":49,"path":50,"stem":51,"children":52,"icon":87},"Providers","\u002Fproviders","2.providers\u002F00.index",[53,55,59,63,67,71,75,79,83],{"title":54,"path":50,"stem":51},"Every provider",{"title":56,"path":57,"stem":58},"Steel","\u002Fproviders\u002Fsteel","2.providers\u002F01.steel",{"title":60,"path":61,"stem":62},"Browserbase","\u002Fproviders\u002Fbrowserbase","2.providers\u002F02.browserbase",{"title":64,"path":65,"stem":66},"Kernel","\u002Fproviders\u002Fkernel","2.providers\u002F03.kernel",{"title":68,"path":69,"stem":70},"Browserless","\u002Fproviders\u002Fbrowserless","2.providers\u002F04.browserless",{"title":72,"path":73,"stem":74},"Hyperbrowser","\u002Fproviders\u002Fhyperbrowser","2.providers\u002F05.hyperbrowser",{"title":76,"path":77,"stem":78},"Anchor","\u002Fproviders\u002Fanchor","2.providers\u002F06.anchor",{"title":80,"path":81,"stem":82},"Cloudflare","\u002Fproviders\u002Fcloudflare","2.providers\u002F07.cloudflare",{"title":84,"path":85,"stem":86},"Playwright","\u002Fproviders\u002Fplaywright","2.providers\u002F08.playwright","i-lucide-server",{"id":89,"title":44,"body":90,"description":803,"extension":804,"links":805,"meta":806,"navigation":177,"path":45,"seo":808,"stem":46,"__hash__":809},"docs\u002F1.guide\u002F10.custom.md",{"type":91,"value":92,"toc":797},"minimark",[93,102,107,122,669,681,685,741,745,754,758,793],[94,95,96,97,101],"p",{},"The built-ins are classes behind one interface, ",[98,99,100],"code",{},"BrowserProvider",", and yours is the same shape. Nothing about a built-in is special. It has a manifest entry so the package can import it lazily, that's the whole difference.",[103,104,106],"h2",{"id":105},"a-provider-in-one-file","A provider in one file",[94,108,109,110,117,118,121],{},"This one puts ",[111,112,116],"a",{"href":113,"rel":114},"https:\u002F\u002Fjina.ai\u002Freader\u002F",[115],"nofollow","Jina Reader"," behind ",[98,119,120],{},"scrape()",". It renders a page and answers with markdown, one GET per URL, no session, and it answered us without a key.",[123,124,130],"pre",{"className":125,"code":126,"filename":127,"language":128,"meta":129,"style":129},"language-ts shiki shiki-themes browsers browsers browsers","import { create, notSupportedViaRest, register } from \"@agntn\u002Fbrowsers\";\nimport type { BrowserProvider, ProviderCapabilities, ProviderConfig, ScrapeResult } from \"@agntn\u002Fbrowsers\";\n\nclass Reader implements BrowserProvider {\n  private readonly baseURL: string;\n\n  constructor(config: ProviderConfig) {\n    this.baseURL = config.baseURL!;\n  }\n\n  name(): string {\n    return \"reader\";\n  }\n\n  capabilities(): ProviderCapabilities {\n    return {\n      scrape: true,\n      statelessScrape: true,\n      screenshot: false,\n      statelessScreenshot: false,\n      navigate: false,\n      evaluate: false,\n      sessions: false,\n      cdp: false,\n      crawl: false,\n      pdf: false,\n      links: false,\n      search: false,\n      extract: false,\n    };\n  }\n\n  async scrape(url: string): Promise\u003CScrapeResult> {\n    const response = await fetch(`${this.baseURL}\u002F${url}`, { headers: { \"X-No-Cache\": \"true\" } });\n    return { url, markdown: await response.text(), statusCode: response.status };\n  }\n\n  async createSession(): Promise\u003Cnever> {\n    return notSupportedViaRest(\"reader\", \"sessions\");\n  }\n  \u002F\u002F getSession, listSessions, releaseSession, screenshot, navigate and evaluate the same way\n}\n\nregister(\"reader\", \"https:\u002F\u002Fr.jina.ai\", (config) => new Reader(config));\n\nconst page = await (await create(\"reader\")).scrape(\"https:\u002F\u002Fexample.com\");\n","reader.ts","ts","",[98,131,132,155,172,179,195,215,220,237,254,260,265,281,292,297,302,317,324,330,336,342,348,354,360,366,372,378,384,390,396,402,408,413,418,451,501,521,526,531,552,574,579,586,592,597,626,631],{"__ignoreMap":129},[133,134,137,141,145,148,152],"span",{"class":135,"line":136},"line",1,[133,138,140],{"class":139},"skH_V","import",[133,142,144],{"class":143},"s38Sx"," { create, notSupportedViaRest, register } ",[133,146,147],{"class":139},"from",[133,149,151],{"class":150},"shU9J"," \"@agntn\u002Fbrowsers\"",[133,153,154],{"class":143},";\n",[133,156,158,160,163,166,168,170],{"class":135,"line":157},2,[133,159,140],{"class":139},[133,161,162],{"class":139}," type",[133,164,165],{"class":143}," { BrowserProvider, ProviderCapabilities, ProviderConfig, ScrapeResult } ",[133,167,147],{"class":139},[133,169,151],{"class":150},[133,171,154],{"class":143},[133,173,175],{"class":135,"line":174},3,[133,176,178],{"emptyLinePlaceholder":177},true,"\n",[133,180,182,185,189,192],{"class":135,"line":181},4,[133,183,184],{"class":139},"class",[133,186,188],{"class":187},"sK71F"," Reader",[133,190,191],{"class":139}," implements",[133,193,194],{"class":143}," BrowserProvider {\n",[133,196,198,201,204,207,210,213],{"class":135,"line":197},5,[133,199,200],{"class":139},"  private",[133,202,203],{"class":139}," readonly",[133,205,206],{"class":143}," baseURL",[133,208,209],{"class":139},":",[133,211,212],{"class":187}," string",[133,214,154],{"class":143},[133,216,218],{"class":135,"line":217},6,[133,219,178],{"emptyLinePlaceholder":177},[133,221,223,226,229,231,234],{"class":135,"line":222},7,[133,224,225],{"class":139},"  constructor",[133,227,228],{"class":143},"(config",[133,230,209],{"class":139},[133,232,233],{"class":187}," ProviderConfig",[133,235,236],{"class":143},") {\n",[133,238,240,243,246,249,252],{"class":135,"line":239},8,[133,241,242],{"class":143},"    this.baseURL ",[133,244,245],{"class":139},"=",[133,247,248],{"class":143}," config.baseURL",[133,250,251],{"class":139},"!",[133,253,154],{"class":143},[133,255,257],{"class":135,"line":256},9,[133,258,259],{"class":143},"  }\n",[133,261,263],{"class":135,"line":262},10,[133,264,178],{"emptyLinePlaceholder":177},[133,266,268,271,274,276,278],{"class":135,"line":267},11,[133,269,270],{"class":187},"  name",[133,272,273],{"class":143},"()",[133,275,209],{"class":139},[133,277,212],{"class":187},[133,279,280],{"class":143}," {\n",[133,282,284,287,290],{"class":135,"line":283},12,[133,285,286],{"class":139},"    return",[133,288,289],{"class":150}," \"reader\"",[133,291,154],{"class":143},[133,293,295],{"class":135,"line":294},13,[133,296,259],{"class":143},[133,298,300],{"class":135,"line":299},14,[133,301,178],{"emptyLinePlaceholder":177},[133,303,305,308,310,312,315],{"class":135,"line":304},15,[133,306,307],{"class":187},"  capabilities",[133,309,273],{"class":143},[133,311,209],{"class":139},[133,313,314],{"class":187}," ProviderCapabilities",[133,316,280],{"class":143},[133,318,320,322],{"class":135,"line":319},16,[133,321,286],{"class":139},[133,323,280],{"class":143},[133,325,327],{"class":135,"line":326},17,[133,328,329],{"class":143},"      scrape: true,\n",[133,331,333],{"class":135,"line":332},18,[133,334,335],{"class":143},"      statelessScrape: true,\n",[133,337,339],{"class":135,"line":338},19,[133,340,341],{"class":143},"      screenshot: false,\n",[133,343,345],{"class":135,"line":344},20,[133,346,347],{"class":143},"      statelessScreenshot: false,\n",[133,349,351],{"class":135,"line":350},21,[133,352,353],{"class":143},"      navigate: false,\n",[133,355,357],{"class":135,"line":356},22,[133,358,359],{"class":143},"      evaluate: false,\n",[133,361,363],{"class":135,"line":362},23,[133,364,365],{"class":143},"      sessions: false,\n",[133,367,369],{"class":135,"line":368},24,[133,370,371],{"class":143},"      cdp: false,\n",[133,373,375],{"class":135,"line":374},25,[133,376,377],{"class":143},"      crawl: false,\n",[133,379,381],{"class":135,"line":380},26,[133,382,383],{"class":143},"      pdf: false,\n",[133,385,387],{"class":135,"line":386},27,[133,388,389],{"class":143},"      links: false,\n",[133,391,393],{"class":135,"line":392},28,[133,394,395],{"class":143},"      search: false,\n",[133,397,399],{"class":135,"line":398},29,[133,400,401],{"class":143},"      extract: false,\n",[133,403,405],{"class":135,"line":404},30,[133,406,407],{"class":143},"    };\n",[133,409,411],{"class":135,"line":410},31,[133,412,259],{"class":143},[133,414,416],{"class":135,"line":415},32,[133,417,178],{"emptyLinePlaceholder":177},[133,419,421,424,427,430,432,434,437,439,442,445,448],{"class":135,"line":420},33,[133,422,423],{"class":139},"  async",[133,425,426],{"class":187}," scrape",[133,428,429],{"class":143},"(url",[133,431,209],{"class":139},[133,433,212],{"class":187},[133,435,436],{"class":143},")",[133,438,209],{"class":139},[133,440,441],{"class":187}," Promise",[133,443,444],{"class":143},"\u003C",[133,446,447],{"class":187},"ScrapeResult",[133,449,450],{"class":143},"> {\n",[133,452,454,457,460,462,465,468,471,474,477,480,483,486,489,492,495,498],{"class":135,"line":453},34,[133,455,456],{"class":139},"    const",[133,458,459],{"class":143}," response ",[133,461,245],{"class":139},[133,463,464],{"class":139}," await",[133,466,467],{"class":187}," fetch",[133,469,470],{"class":143},"(",[133,472,473],{"class":150},"`${",[133,475,476],{"class":150},"this.baseURL",[133,478,479],{"class":150},"}\u002F${",[133,481,482],{"class":150},"url",[133,484,485],{"class":150},"}`",[133,487,488],{"class":143},", { headers: { ",[133,490,491],{"class":150},"\"X-No-Cache\"",[133,493,494],{"class":143},": ",[133,496,497],{"class":150},"\"true\"",[133,499,500],{"class":143}," } });\n",[133,502,504,506,509,512,515,518],{"class":135,"line":503},35,[133,505,286],{"class":139},[133,507,508],{"class":143}," { url, markdown: ",[133,510,511],{"class":139},"await",[133,513,514],{"class":143}," response.",[133,516,517],{"class":187},"text",[133,519,520],{"class":143},"(), statusCode: response.status };\n",[133,522,524],{"class":135,"line":523},36,[133,525,259],{"class":143},[133,527,529],{"class":135,"line":528},37,[133,530,178],{"emptyLinePlaceholder":177},[133,532,534,536,539,541,543,545,547,550],{"class":135,"line":533},38,[133,535,423],{"class":139},[133,537,538],{"class":187}," createSession",[133,540,273],{"class":143},[133,542,209],{"class":139},[133,544,441],{"class":187},[133,546,444],{"class":143},[133,548,549],{"class":187},"never",[133,551,450],{"class":143},[133,553,555,557,560,562,565,568,571],{"class":135,"line":554},39,[133,556,286],{"class":139},[133,558,559],{"class":187}," notSupportedViaRest",[133,561,470],{"class":143},[133,563,564],{"class":150},"\"reader\"",[133,566,567],{"class":143},", ",[133,569,570],{"class":150},"\"sessions\"",[133,572,573],{"class":143},");\n",[133,575,577],{"class":135,"line":576},40,[133,578,259],{"class":143},[133,580,582],{"class":135,"line":581},41,[133,583,585],{"class":584},"scIB-","  \u002F\u002F getSession, listSessions, releaseSession, screenshot, navigate and evaluate the same way\n",[133,587,589],{"class":135,"line":588},42,[133,590,591],{"class":143},"}\n",[133,593,595],{"class":135,"line":594},43,[133,596,178],{"emptyLinePlaceholder":177},[133,598,600,603,605,607,609,612,615,618,621,623],{"class":135,"line":599},44,[133,601,602],{"class":187},"register",[133,604,470],{"class":143},[133,606,564],{"class":150},[133,608,567],{"class":143},[133,610,611],{"class":150},"\"https:\u002F\u002Fr.jina.ai\"",[133,613,614],{"class":143},", (config) ",[133,616,617],{"class":139},"=>",[133,619,620],{"class":139}," new",[133,622,188],{"class":187},[133,624,625],{"class":143},"(config));\n",[133,627,629],{"class":135,"line":628},45,[133,630,178],{"emptyLinePlaceholder":177},[133,632,634,637,640,642,644,647,649,652,654,656,659,662,664,667],{"class":135,"line":633},46,[133,635,636],{"class":139},"const",[133,638,639],{"class":143}," page ",[133,641,245],{"class":139},[133,643,464],{"class":139},[133,645,646],{"class":143}," (",[133,648,511],{"class":139},[133,650,651],{"class":187}," create",[133,653,470],{"class":143},[133,655,564],{"class":150},[133,657,658],{"class":143},")).",[133,660,661],{"class":187},"scrape",[133,663,470],{"class":143},[133,665,666],{"class":150},"\"https:\u002F\u002Fexample.com\"",[133,668,573],{"class":143},[94,670,671,672,676,677,680],{},"The version on the ",[111,673,675],{"href":674},"\u002F","home page"," has every method written out, and that one compiles under ",[98,678,679],{},"strict"," and ran against the real service before it went on the page.",[103,682,684],{"id":683},"what-each-part-does","What each part does",[686,687,688,703,713,735],"ul",{},[689,690,691,694,695,698,699,702],"li",{},[98,692,693],{},"capabilities()"," is what callers read to pick a provider, not a gate. A method flagged ",[98,696,697],{},"false"," can still be called, a tool with your provider named does exactly that, so the method itself has to refuse. Say ",[98,700,701],{},"true"," only when it works.",[689,704,705,708,709,712],{},[98,706,707],{},"notSupportedViaRest(name, operation)"," throws ",[98,710,711],{},"UnsupportedOperationError"," with a message that points at CDP. Every method you can't do should end there, not in a silent empty result.",[689,714,715,718,719,722,723,726,727,730,731,734],{},[98,716,717],{},"register(name, defaultURL, factory)"," adds it to the registry. The factory gets ",[98,720,721],{},"{ apiKey, baseURL, … }",", with ",[98,724,725],{},"apiKey"," read from ",[98,728,729],{},"READER_API_KEY"," if you didn't pass one and ",[98,732,733],{},"baseURL"," defaulting to what you registered.",[689,736,737,740],{},[98,738,739],{},"X-No-Cache"," is Jina's, not ours. Without it Jina served us a cached page that wasn't example.com at all.",[103,742,744],{"id":743},"agents-and-the-cli","Agents and the CLI",[94,746,747,750,751,753],{},[98,748,749],{},"create(\"reader\")"," works right away. The CLI and the agent tools also check for a key before they use a provider, ",[98,752,729],{}," here, so a keyless provider of your own stays in the library until you set that variable. Playwright is the one built-in exception to that rule, and it's hardcoded.",[103,755,757],{"id":756},"shipping-it-in-the-package","Shipping it in the package",[94,759,760,761,764,765,768,769,772,773,776,777,780,781,784,785,788,789,792],{},"For a provider that belongs in ",[98,762,763],{},"@agntn\u002Fbrowsers"," itself: a file in ",[98,766,767],{},"src\u002Fproviders\u002F"," that exports ",[98,770,771],{},"factory",", a manifest entry in ",[98,774,775],{},"src\u002Fproviders\u002Findex.ts",", the key in ",[98,778,779],{},"browserProviderNames"," in ",[98,782,783],{},"src\u002Ftool-contract.ts",", and any unusual environment key in ",[98,786,787],{},"src\u002Fcore\u002Fresolve.ts",". The tests fail when those disagree, which is the point. Then a page here, next to the other ",[111,790,791],{"href":50},"providers",".",[794,795,796],"style",{},"html pre.shiki code .skH_V, html code.shiki .skH_V{--shiki-light:var(--shiki-token-keyword);--shiki-default:var(--shiki-token-keyword);--shiki-dark:var(--shiki-token-keyword)}html pre.shiki code .s38Sx, html code.shiki .s38Sx{--shiki-light:var(--ui-text-highlighted);--shiki-default:var(--ui-text-highlighted);--shiki-dark:var(--ui-text-highlighted)}html pre.shiki code .shU9J, html code.shiki .shU9J{--shiki-light:var(--shiki-token-string);--shiki-default:var(--shiki-token-string);--shiki-dark:var(--shiki-token-string)}html pre.shiki code .sK71F, html code.shiki .sK71F{--shiki-light:var(--shiki-token-function);--shiki-default:var(--shiki-token-function);--shiki-dark:var(--shiki-token-function)}html pre.shiki code .scIB-, html code.shiki .scIB-{--shiki-light:var(--shiki-token-comment);--shiki-default:var(--shiki-token-comment);--shiki-dark:var(--shiki-token-comment)}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":129,"searchDepth":157,"depth":157,"links":798},[799,800,801,802],{"id":105,"depth":157,"text":106},{"id":683,"depth":157,"text":684},{"id":743,"depth":157,"text":744},{"id":756,"depth":157,"text":757},"Put your own backend behind create(). One class that implements BrowserProvider and one register() call","md",null,{"icon":807},"i-lucide-plus",{"title":44,"description":803},"N3AMfMszg_vq6bMPjk7n2YHsiS0ejtKwMZnvq3eDbbQ",[811,813],{"title":40,"path":41,"stem":42,"description":812,"children":-1},"Most failures are a typed BrowserError. Which class means fix the call and which means wait and which means try another provider",{"title":54,"path":50,"stem":51,"description":814,"children":-1},"Every browser backend the package ships with its key and how it scrapes and what a real scrape of example.com gave back",1790685115877]