[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-navigation":3,"docs-page-ai":166},[4,38,57,65,114,138,157],{"id":5,"group":6,"pages":7},"get-started","Get started",[8,13,18,23,28,33],{"path":9,"title":10,"description":11,"navTitle":10,"group":6,"order":12},"\u002Fdocs\u002Fgetting-started","Getting started","Create your app from the kit, run it locally with no credentials, make yourself superadmin and find where your own code goes.",1,{"path":14,"title":15,"description":16,"navTitle":15,"group":6,"order":17},"\u002Fdocs\u002Flayout","Project structure","Where code lives by where it runs, and which server\u002F directories turn a new file into a route, a job or a provider.",2,{"path":19,"title":20,"description":21,"navTitle":20,"group":6,"order":22},"\u002Fdocs\u002Fconfiguration","Configuration","starter.config.ts switches each part of the kit and its providers, environment variables hold the secrets: every key and what it changes.",3,{"path":24,"title":25,"description":26,"navTitle":25,"group":6,"order":27},"\u002Fdocs\u002Ftemplate","Updating from the kit","Keep the kit as a git remote and take each update as one commit you review, with pnpm template:update, then resolve what conflicts.",4,{"path":29,"title":30,"description":31,"navTitle":30,"group":6,"order":32},"\u002Fdocs\u002Fdeployment","Deployment","Ship the app as one Cloudflare Worker: create the resources, set the secrets, deploy main from GitHub Actions or your machine.",5,{"path":34,"title":35,"description":36,"navTitle":35,"group":6,"order":37},"\u002Fdocs\u002Fmcp","MCP server","Connect Claude Code, Cursor or VS Code to https:\u002F\u002Fnuxter.app\u002Fmcp, so your agent searches and reads these docs and their design notes.",6,{"id":39,"group":40,"pages":41},"accounts","Accounts",[42,47,52],{"path":43,"title":44,"description":45,"navTitle":44,"group":40,"order":46},"\u002Fdocs\u002Fauth","Auth","Pick the sign-in methods, add an OAuth provider, protect a route, and handle sessions, second factors and account deletion.",7,{"path":48,"title":49,"description":50,"navTitle":49,"group":40,"order":51},"\u002Fdocs\u002Ftenancy","Organizations","Scope routes and pages to an organization, and work with its members, roles, seats, invitations and a second level such as branches.",8,{"path":53,"title":54,"description":55,"navTitle":54,"group":40,"order":56},"\u002Fdocs\u002Fpermissions","Roles and permissions","Routes check permissions that an account’s role or its organization’s plan grants: checking one, adding a role, and the staff tools.",9,{"id":58,"group":59,"pages":60},"payments","Payments",[61],{"path":62,"title":59,"description":63,"navTitle":59,"group":59,"order":64},"\u002Fdocs\u002Fpayments","Sell plans through Stripe, Polar, Lemon Squeezy or Creem: define plans, connect a provider, gate features, trials, coupons and refunds.",10,{"id":66,"group":67,"pages":68},"building-blocks","Building blocks",[69,74,79,84,89,94,99,104,109],{"path":70,"title":71,"description":72,"navTitle":71,"group":67,"order":73},"\u002Fdocs\u002Fdatabase","Database","Postgres through Drizzle, on Hyperdrive in production and PGlite locally: add a table, query, transactions, migrations and seed data.",11,{"path":75,"title":76,"description":77,"navTitle":76,"group":67,"order":78},"\u002Fdocs\u002Fbackground-jobs","Background jobs","Run work after the response on Cloudflare Queues with retries: add and enqueue a job, run jobs locally, and schedule cron tasks.",12,{"path":80,"title":81,"description":82,"navTitle":81,"group":67,"order":83},"\u002Fdocs\u002Fevents","Events","React to what the kit does by adding a file to server\u002Flisteners\u002F, fire events of your own, and look up every event’s payload.",13,{"path":85,"title":86,"description":87,"navTitle":86,"group":67,"order":88},"\u002Fdocs\u002Femail","Email","Set up sending, send your own mail with deliverEmail, change the templates and wording, handle unsubscribes and add a provider.",14,{"path":90,"title":91,"description":92,"navTitle":91,"group":67,"order":93},"\u002Fdocs\u002Fcontact","Contact","A \u002Fcontact page that mails you a message or shows your address after a Turnstile check: choose the channels, set the address, know the limits.",15,{"path":95,"title":96,"description":97,"navTitle":96,"group":67,"order":98},"\u002Fdocs\u002Fnotifications","Notifications","The dashboard bell: add a notification type, send one with notify(), and how categories, opt-outs and retention apply.",16,{"path":100,"title":101,"description":102,"navTitle":101,"group":67,"order":103},"\u002Fdocs\u002Fstorage","Storage","Uploads in R2 with one policy per purpose: add a purpose, accept an upload, replace or delete a file, and serve it to its owner.",17,{"path":105,"title":106,"description":107,"navTitle":106,"group":67,"order":108},"\u002Fdocs\u002Ffeature-flags","Feature flags","Declare typed flags in starter.config.ts, read them with flag() or useFlag(), roll them out by rule or percentage, and add a provider.",18,{"path":110,"title":111,"description":112,"navTitle":111,"group":67,"order":113},"\u002Fdocs\u002Fai","AI","Call models from server code by role: text, images, speech, transcription, embeddings and video, with provider fallbacks.",19,{"id":115,"group":116,"pages":117},"web","Web",[118,123,128,133],{"path":119,"title":120,"description":121,"navTitle":120,"group":116,"order":122},"\u002Fdocs\u002Fsecurity-headers","Security headers","The Content-Security-Policy and hardening headers on every response: allow another origin, keep tokens out of Referer, accept cross-site posts.",20,{"path":124,"title":125,"description":126,"navTitle":125,"group":116,"order":127},"\u002Fdocs\u002Frate-limiting","Rate limiting","Cap how often a caller hits a route with requireRateLimit, count only failures, space out sends, and protect the zone.",21,{"path":129,"title":130,"description":131,"navTitle":130,"group":116,"order":132},"\u002Fdocs\u002Fanalytics","Analytics","Product analytics and ad conversions sent from the server with no browser script: add an event, connect a provider, and handle consent.",22,{"path":134,"title":135,"description":136,"navTitle":135,"group":116,"order":137},"\u002Fdocs\u002Fobservability","Observability","Find a failed request by its id, send faults to Sentry or Better Stack, report an error you caught, and change the error page.",23,{"id":139,"group":140,"pages":141},"content-and-look","Content and look",[142,147,152],{"path":143,"title":144,"description":145,"navTitle":144,"group":140,"order":146},"\u002Fdocs\u002Fblog","Blog","Markdown posts with an RSS feed per language, and workflows that draft posts with an AI model and open each one as a pull request.",24,{"path":148,"title":149,"description":150,"navTitle":149,"group":140,"order":151},"\u002Fdocs\u002Flanguages","Languages","Add or remove a language, write copy in the locale files, and write code, refusals and links that follow the reader’s language.",25,{"path":153,"title":154,"description":155,"navTitle":154,"group":140,"order":156},"\u002Fdocs\u002Ftheme","Theme","Change the brand colour and font, and build dashboard sections, list pages, loading placeholders and charts that match the kit.",26,{"id":158,"group":159,"pages":160},"quality","Quality",[161],{"path":162,"title":163,"description":164,"navTitle":163,"group":159,"order":165},"\u002Fdocs\u002Ftesting","Testing","Unit tests in Node, component tests in a real Nuxt app and Playwright end to end: test a route or a component, and what CI runs.",27,{"page":167,"surround":2412,"linksToKit":486},{"path":110,"title":111,"description":112,"navTitle":111,"group":67,"seo":168,"body":170},{"title":169,"description":112},"AI in Nuxt: text, images and speech with fallbacks",{"type":171,"value":172,"toc":2398},"minimark",[173,177,188,193,238,242,433,444,448,454,773,791,802,878,889,894,900,904,918,922,933,1232,1246,1250,1256,1267,1295,1299,1324,1933,1936,2105,2130,2134,2151,2154,2332,2336,2394],[174,175,111],"h1",{"id":176},"ai",[178,179,180,184,185,187],"p",{},[181,182,183],"code",{},"#server\u002Fai"," runs model calls from your server code: text, images, speech,\ntranscription, embeddings and video. The kit ships one provider, Workers AI\nthrough the ",[181,186,111],{}," binding, and calls no model itself. You work here when you\nbuild an AI feature or point a role at a different model.",[189,190,192],"h2",{"id":191},"how-it-works","How it works",[194,195,196,214,221,235],"ol",{},[197,198,199,202,203,213],"li",{},[181,200,201],{},"ai.roles"," in ",[204,205,210],"a",{"href":206,"rel":207,"title":209},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fstarter.config.ts",[208],"nofollow","Licensees only",[181,211,212],{},"starter.config.ts"," lists each thing\nyour app asks a model for: its modality, and the model each provider uses\nfor it.",[197,215,216,217,220],{},"You call the entry point for that modality with a role, such as\n",[181,218,219],{},"runText(event, 'copy', …)",". A role of another modality fails type checking.",[197,222,223,230,231,234],{},[204,224,227],{"href":225,"rel":226,"title":209},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fai\u002Findex.ts",[208],[181,228,229],{},"server\u002Fai\u002Findex.ts"," tries the providers in\n",[181,232,233],{},"ai.providers"," order. It skips a provider that names no model for the role,\nhas no method for the modality, or has no binding or key. A failure the\nprovider marks retryable is sent once more, then the next provider gets the\nsame request.",[197,236,237],{},"The entry point returns the result. It throws a 501 when no provider can\nserve the role and a 502 when every provider refused.",[189,239,241],{"id":240},"the-shipped-roles","The shipped roles",[243,244,245,267],"table",{},[246,247,248],"thead",{},[249,250,251,255,258,261,264],"tr",{},[252,253,254],"th",{},"Role",[252,256,257],{},"Modality",[252,259,260],{},"Workers AI model",[252,262,263],{},"Call",[252,265,266],{},"Returns",[268,269,270,298,325,352,378,406],"tbody",{},[249,271,272,278,283,288,293],{},[273,274,275],"td",{},[181,276,277],{},"copy",[273,279,280],{},[181,281,282],{},"text",[273,284,285],{},[181,286,287],{},"@cf\u002Fmeta\u002Fllama-3.1-8b-instruct-fast",[273,289,290],{},[181,291,292],{},"runText",[273,294,295],{},[181,296,297],{},"string",[249,299,300,305,310,315,320],{},[273,301,302],{},[181,303,304],{},"illustration",[273,306,307],{},[181,308,309],{},"image",[273,311,312],{},[181,313,314],{},"@cf\u002Fblack-forest-labs\u002Fflux-1-schnell",[273,316,317],{},[181,318,319],{},"runImage",[273,321,322],{},[181,323,324],{},"{ data, mediaType }",[249,326,327,332,337,342,347],{},[273,328,329],{},[181,330,331],{},"narration",[273,333,334],{},[181,335,336],{},"speech",[273,338,339],{},[181,340,341],{},"@cf\u002Fdeepgram\u002Faura-1",[273,343,344],{},[181,345,346],{},"runSpeech",[273,348,349],{},[181,350,351],{},"{ data, mediaType: 'audio\u002Fmpeg' }",[249,353,354,359,364,369,374],{},[273,355,356],{},[181,357,358],{},"transcript",[273,360,361],{},[181,362,363],{},"transcription",[273,365,366],{},[181,367,368],{},"@cf\u002Fopenai\u002Fwhisper",[273,370,371],{},[181,372,373],{},"runTranscription",[273,375,376],{},[181,377,297],{},[249,379,380,385,390,395,400],{},[273,381,382],{},[181,383,384],{},"search",[273,386,387],{},[181,388,389],{},"embedding",[273,391,392],{},[181,393,394],{},"@cf\u002Fbaai\u002Fbge-large-en-v1.5",[273,396,397],{},[181,398,399],{},"runEmbedding",[273,401,402,405],{},[181,403,404],{},"number[][]",", one per input",[249,407,408,413,418,423,428],{},[273,409,410],{},[181,411,412],{},"motion",[273,414,415],{},[181,416,417],{},"video",[273,419,420],{},[181,421,422],{},"bytedance\u002Fseedance-2.0",[273,424,425],{},[181,426,427],{},"runVideo",[273,429,430],{},[181,431,432],{},"{ url, mediaType: 'video\u002Fmp4' }",[178,434,435,436,439,440,443],{},"Rename them, add more roles of one modality, or point them at other models. A\nrole is an entry such as\n",[181,437,438],{},"copy: { modality: 'text', models: { workersai: '@cf\u002Fmeta\u002Fllama-3.1-8b-instruct-fast' } }",".\nRole ids must match ",[181,441,442],{},"\u002F^[a-z][a-z0-9]*$\u002F",", because each one becomes part of an\nenvironment variable. The build also refuses a role that names a model for a\nprovider that can't serve its modality, and a role no bundled provider serves.",[189,445,447],{"id":446},"call-a-model","Call a model",[178,449,450,451,453],{},"Rate-limit anything that spends money per call\n(",[204,452,125],{"href":124},"):",[455,456,461],"pre",{"className":457,"code":458,"language":459,"meta":460,"style":460},"language-ts shiki shiki-themes github-light github-light github-dark","import { runText } from '#server\u002Fai'\n\nexport default defineEventHandler(async (event) => {\n  const session = await requireVerifiedSession(event)\n\n  const { title } = await readValidatedBody(\n    event,\n    z.object({ title: z.string().min(1).max(200) }).parse,\n  )\n\n  await requireRateLimit(event, {\n    scope: 'ai-copy',\n    key: String(session.user.id),\n    policy: { max: 30, windowMs: 60 * 60_000, lockoutMs: 15 * 60_000 },\n  })\n\n  const blurb = await runText(event, 'copy', {\n    system: 'Write two plain sentences for a product listing.',\n    messages: [{ role: 'user', content: title }],\n    maxTokens: 300,\n  })\n\n  return { blurb }\n})\n","ts","",[181,462,463,482,488,522,543,547,571,576,614,619,623,634,645,656,689,694,698,721,731,742,752,756,760,768],{"__ignoreMap":460},[464,465,467,471,475,478],"span",{"class":466,"line":12},"line",[464,468,470],{"class":469},"s8jYJ","import",[464,472,474],{"class":473},"sxrX7"," { runText } ",[464,476,477],{"class":469},"from",[464,479,481],{"class":480},"sIIMD"," '#server\u002Fai'\n",[464,483,484],{"class":466,"line":17},[464,485,487],{"emptyLinePlaceholder":486},true,"\n",[464,489,490,493,496,500,503,506,509,513,516,519],{"class":466,"line":22},[464,491,492],{"class":469},"export",[464,494,495],{"class":469}," default",[464,497,499],{"class":498},"snPdu"," defineEventHandler",[464,501,502],{"class":473},"(",[464,504,505],{"class":469},"async",[464,507,508],{"class":473}," (",[464,510,512],{"class":511},"sP4rz","event",[464,514,515],{"class":473},") ",[464,517,518],{"class":469},"=>",[464,520,521],{"class":473}," {\n",[464,523,524,527,531,534,537,540],{"class":466,"line":27},[464,525,526],{"class":469},"  const",[464,528,530],{"class":529},"sBjJW"," session",[464,532,533],{"class":469}," =",[464,535,536],{"class":469}," await",[464,538,539],{"class":498}," requireVerifiedSession",[464,541,542],{"class":473},"(event)\n",[464,544,545],{"class":466,"line":32},[464,546,487],{"emptyLinePlaceholder":486},[464,548,549,551,554,557,560,563,565,568],{"class":466,"line":37},[464,550,526],{"class":469},[464,552,553],{"class":473}," { ",[464,555,556],{"class":529},"title",[464,558,559],{"class":473}," } ",[464,561,562],{"class":469},"=",[464,564,536],{"class":469},[464,566,567],{"class":498}," readValidatedBody",[464,569,570],{"class":473},"(\n",[464,572,573],{"class":466,"line":46},[464,574,575],{"class":473},"    event,\n",[464,577,578,581,584,587,589,592,595,597,600,603,606,608,611],{"class":466,"line":51},[464,579,580],{"class":473},"    z.",[464,582,583],{"class":498},"object",[464,585,586],{"class":473},"({ title: z.",[464,588,297],{"class":498},[464,590,591],{"class":473},"().",[464,593,594],{"class":498},"min",[464,596,502],{"class":473},[464,598,599],{"class":529},"1",[464,601,602],{"class":473},").",[464,604,605],{"class":498},"max",[464,607,502],{"class":473},[464,609,610],{"class":529},"200",[464,612,613],{"class":473},") }).parse,\n",[464,615,616],{"class":466,"line":56},[464,617,618],{"class":473},"  )\n",[464,620,621],{"class":466,"line":64},[464,622,487],{"emptyLinePlaceholder":486},[464,624,625,628,631],{"class":466,"line":73},[464,626,627],{"class":469},"  await",[464,629,630],{"class":498}," requireRateLimit",[464,632,633],{"class":473},"(event, {\n",[464,635,636,639,642],{"class":466,"line":78},[464,637,638],{"class":473},"    scope: ",[464,640,641],{"class":480},"'ai-copy'",[464,643,644],{"class":473},",\n",[464,646,647,650,653],{"class":466,"line":83},[464,648,649],{"class":473},"    key: ",[464,651,652],{"class":498},"String",[464,654,655],{"class":473},"(session.user.id),\n",[464,657,658,661,664,667,670,673,676,679,682,684,686],{"class":466,"line":88},[464,659,660],{"class":473},"    policy: { max: ",[464,662,663],{"class":529},"30",[464,665,666],{"class":473},", windowMs: ",[464,668,669],{"class":529},"60",[464,671,672],{"class":469}," *",[464,674,675],{"class":529}," 60_000",[464,677,678],{"class":473},", lockoutMs: ",[464,680,681],{"class":529},"15",[464,683,672],{"class":469},[464,685,675],{"class":529},[464,687,688],{"class":473}," },\n",[464,690,691],{"class":466,"line":93},[464,692,693],{"class":473},"  })\n",[464,695,696],{"class":466,"line":98},[464,697,487],{"emptyLinePlaceholder":486},[464,699,700,702,705,707,709,712,715,718],{"class":466,"line":103},[464,701,526],{"class":469},[464,703,704],{"class":529}," blurb",[464,706,533],{"class":469},[464,708,536],{"class":469},[464,710,711],{"class":498}," runText",[464,713,714],{"class":473},"(event, ",[464,716,717],{"class":480},"'copy'",[464,719,720],{"class":473},", {\n",[464,722,723,726,729],{"class":466,"line":108},[464,724,725],{"class":473},"    system: ",[464,727,728],{"class":480},"'Write two plain sentences for a product listing.'",[464,730,644],{"class":473},[464,732,733,736,739],{"class":466,"line":113},[464,734,735],{"class":473},"    messages: [{ role: ",[464,737,738],{"class":480},"'user'",[464,740,741],{"class":473},", content: title }],\n",[464,743,744,747,750],{"class":466,"line":122},[464,745,746],{"class":473},"    maxTokens: ",[464,748,749],{"class":529},"300",[464,751,644],{"class":473},[464,753,754],{"class":466,"line":127},[464,755,693],{"class":473},[464,757,758],{"class":466,"line":132},[464,759,487],{"emptyLinePlaceholder":486},[464,761,762,765],{"class":466,"line":137},[464,763,764],{"class":469},"  return",[464,766,767],{"class":473}," { blurb }\n",[464,769,770],{"class":466,"line":146},[464,771,772],{"class":473},"})\n",[178,774,775,776,779,780,783,784,787,788,790],{},"A text request takes ",[181,777,778],{},"system",", ",[181,781,782],{},"messages"," and ",[181,785,786],{},"maxTokens",". There is no\ntemperature and no streaming. ",[181,789,786],{}," caps the whole answer, including\nthinking on models that think. Workers AI text models can't read an image part\nin a message, so the adapter refuses such a request.",[178,792,793,794,801],{},"The other entry points take their request shapes from\n",[204,795,798],{"href":796,"rel":797,"title":209},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fai\u002Ftypes.ts",[208],[181,799,800],{},"server\u002Fai\u002Ftypes.ts",":",[455,803,805],{"className":457,"code":804,"language":459,"meta":460,"style":460},"const { data, mediaType } = await runImage(event, 'illustration', {\n  prompt: 'A lighthouse at dusk, flat colours',\n})\n\nconst vectors = await runEmbedding(event, 'search', { input: [title, blurb] })\n",[181,806,807,838,848,852,856],{"__ignoreMap":460},[464,808,809,812,814,817,819,822,824,826,828,831,833,836],{"class":466,"line":12},[464,810,811],{"class":469},"const",[464,813,553],{"class":473},[464,815,816],{"class":529},"data",[464,818,779],{"class":473},[464,820,821],{"class":529},"mediaType",[464,823,559],{"class":473},[464,825,562],{"class":469},[464,827,536],{"class":469},[464,829,830],{"class":498}," runImage",[464,832,714],{"class":473},[464,834,835],{"class":480},"'illustration'",[464,837,720],{"class":473},[464,839,840,843,846],{"class":466,"line":17},[464,841,842],{"class":473},"  prompt: ",[464,844,845],{"class":480},"'A lighthouse at dusk, flat colours'",[464,847,644],{"class":473},[464,849,850],{"class":466,"line":22},[464,851,772],{"class":473},[464,853,854],{"class":466,"line":27},[464,855,487],{"emptyLinePlaceholder":486},[464,857,858,860,863,865,867,870,872,875],{"class":466,"line":32},[464,859,811],{"class":469},[464,861,862],{"class":529}," vectors",[464,864,533],{"class":469},[464,866,536],{"class":469},[464,868,869],{"class":498}," runEmbedding",[464,871,714],{"class":473},[464,873,874],{"class":480},"'search'",[464,876,877],{"class":473},", { input: [title, blurb] })\n",[178,879,880,882,883,885,886,888],{},[181,881,319],{}," reads the media type from the bytes (JPEG, PNG or WebP), so store it\nas the Content-Type when you put the image in R2 (",[204,884,101],{"href":100},").\n",[181,887,399],{}," always takes a list and returns vectors in input order. Where\nthey go is up to you: there is no vector store.",[890,891,893],"h3",{"id":892},"check-before-you-offer-a-feature","Check before you offer a feature",[178,895,896,899],{},[181,897,898],{},"isAiConfigured(event, role)"," says whether any bundled provider can serve the\nrole on this deploy, with its binding or key in place. Return it from a route\nand hide the button rather than show one that answers 501.",[890,901,903],{"id":902},"handle-a-failure-yourself","Handle a failure yourself",[178,905,906,909,910,913,914,917],{},[181,907,908],{},"attemptAi(event, role, request)"," runs the same pass without throwing. It\nreturns the success object or ",[181,911,912],{},"{ ok: false, reason, retryable }",". The reason is\nthe provider's own message and can quote your prompt back, so log it, but never\nshow it or store it on a row a user sees. The entry points log it as\n",[181,915,916],{},"[ai] every provider refused \"\u003Crole>\": …"," before their 502.",[189,919,921],{"id":920},"generate-video-in-a-job","Generate video in a job",[178,923,924,926,927,929,930,801],{},[181,925,427],{}," waits minutes for the result, so call it from a background job\n(",[204,928,76],{"href":75},"), not from a request. The route\nrecords a row and enqueues; the job settles the row.\n",[181,931,932],{},"server\u002Fjobs\u002Frender-clip.ts",[455,934,936],{"className":457,"code":935,"language":459,"meta":460,"style":460},"import { attemptAi } from '#server\u002Fai'\nimport { defineJob } from '#server\u002Futils\u002Fjobs'\n\nexport interface Payload {\n  clipId: number\n  prompt: string\n}\n\nexport default defineJob\u003CPayload>({\n  async run({ clipId, prompt }, event) {\n    const result = await attemptAi(event, 'motion', {\n      prompt,\n      durationSeconds: 5,\n      aspectRatio: '16:9',\n    })\n\n    if (!result.ok && result.retryable) throw new Error('motion: retry later')\n\n    await useDrizzle(event)\n      .update(tables.clips)\n      .set(\n        result.ok ? { status: 'ready', url: result.url } : { status: 'failed' },\n      )\n      .where(eq(tables.clips.id, clipId))\n  },\n})\n",[181,937,938,949,961,965,977,987,997,1002,1006,1024,1051,1073,1078,1088,1098,1103,1107,1143,1147,1157,1168,1177,1203,1208,1223,1228],{"__ignoreMap":460},[464,939,940,942,945,947],{"class":466,"line":12},[464,941,470],{"class":469},[464,943,944],{"class":473}," { attemptAi } ",[464,946,477],{"class":469},[464,948,481],{"class":480},[464,950,951,953,956,958],{"class":466,"line":17},[464,952,470],{"class":469},[464,954,955],{"class":473}," { defineJob } ",[464,957,477],{"class":469},[464,959,960],{"class":480}," '#server\u002Futils\u002Fjobs'\n",[464,962,963],{"class":466,"line":22},[464,964,487],{"emptyLinePlaceholder":486},[464,966,967,969,972,975],{"class":466,"line":27},[464,968,492],{"class":469},[464,970,971],{"class":469}," interface",[464,973,974],{"class":498}," Payload",[464,976,521],{"class":473},[464,978,979,982,984],{"class":466,"line":32},[464,980,981],{"class":511},"  clipId",[464,983,801],{"class":469},[464,985,986],{"class":529}," number\n",[464,988,989,992,994],{"class":466,"line":37},[464,990,991],{"class":511},"  prompt",[464,993,801],{"class":469},[464,995,996],{"class":529}," string\n",[464,998,999],{"class":466,"line":46},[464,1000,1001],{"class":473},"}\n",[464,1003,1004],{"class":466,"line":51},[464,1005,487],{"emptyLinePlaceholder":486},[464,1007,1008,1010,1012,1015,1018,1021],{"class":466,"line":56},[464,1009,492],{"class":469},[464,1011,495],{"class":469},[464,1013,1014],{"class":498}," defineJob",[464,1016,1017],{"class":473},"\u003C",[464,1019,1020],{"class":498},"Payload",[464,1022,1023],{"class":473},">({\n",[464,1025,1026,1029,1032,1035,1038,1040,1043,1046,1048],{"class":466,"line":64},[464,1027,1028],{"class":469},"  async",[464,1030,1031],{"class":498}," run",[464,1033,1034],{"class":473},"({ ",[464,1036,1037],{"class":511},"clipId",[464,1039,779],{"class":473},[464,1041,1042],{"class":511},"prompt",[464,1044,1045],{"class":473}," }, ",[464,1047,512],{"class":511},[464,1049,1050],{"class":473},") {\n",[464,1052,1053,1056,1059,1061,1063,1066,1068,1071],{"class":466,"line":73},[464,1054,1055],{"class":469},"    const",[464,1057,1058],{"class":529}," result",[464,1060,533],{"class":469},[464,1062,536],{"class":469},[464,1064,1065],{"class":498}," attemptAi",[464,1067,714],{"class":473},[464,1069,1070],{"class":480},"'motion'",[464,1072,720],{"class":473},[464,1074,1075],{"class":466,"line":78},[464,1076,1077],{"class":473},"      prompt,\n",[464,1079,1080,1083,1086],{"class":466,"line":83},[464,1081,1082],{"class":473},"      durationSeconds: ",[464,1084,1085],{"class":529},"5",[464,1087,644],{"class":473},[464,1089,1090,1093,1096],{"class":466,"line":88},[464,1091,1092],{"class":473},"      aspectRatio: ",[464,1094,1095],{"class":480},"'16:9'",[464,1097,644],{"class":473},[464,1099,1100],{"class":466,"line":93},[464,1101,1102],{"class":473},"    })\n",[464,1104,1105],{"class":466,"line":98},[464,1106,487],{"emptyLinePlaceholder":486},[464,1108,1109,1112,1114,1117,1120,1123,1126,1129,1132,1135,1137,1140],{"class":466,"line":103},[464,1110,1111],{"class":469},"    if",[464,1113,508],{"class":473},[464,1115,1116],{"class":469},"!",[464,1118,1119],{"class":473},"result.ok ",[464,1121,1122],{"class":469},"&&",[464,1124,1125],{"class":473}," result.retryable) ",[464,1127,1128],{"class":469},"throw",[464,1130,1131],{"class":469}," new",[464,1133,1134],{"class":498}," Error",[464,1136,502],{"class":473},[464,1138,1139],{"class":480},"'motion: retry later'",[464,1141,1142],{"class":473},")\n",[464,1144,1145],{"class":466,"line":108},[464,1146,487],{"emptyLinePlaceholder":486},[464,1148,1149,1152,1155],{"class":466,"line":113},[464,1150,1151],{"class":469},"    await",[464,1153,1154],{"class":498}," useDrizzle",[464,1156,542],{"class":473},[464,1158,1159,1162,1165],{"class":466,"line":122},[464,1160,1161],{"class":473},"      .",[464,1163,1164],{"class":498},"update",[464,1166,1167],{"class":473},"(tables.clips)\n",[464,1169,1170,1172,1175],{"class":466,"line":127},[464,1171,1161],{"class":473},[464,1173,1174],{"class":498},"set",[464,1176,570],{"class":473},[464,1178,1179,1182,1185,1188,1191,1194,1196,1198,1201],{"class":466,"line":132},[464,1180,1181],{"class":473},"        result.ok ",[464,1183,1184],{"class":469},"?",[464,1186,1187],{"class":473}," { status: ",[464,1189,1190],{"class":480},"'ready'",[464,1192,1193],{"class":473},", url: result.url } ",[464,1195,801],{"class":469},[464,1197,1187],{"class":473},[464,1199,1200],{"class":480},"'failed'",[464,1202,688],{"class":473},[464,1204,1205],{"class":466,"line":137},[464,1206,1207],{"class":473},"      )\n",[464,1209,1210,1212,1215,1217,1220],{"class":466,"line":146},[464,1211,1161],{"class":473},[464,1213,1214],{"class":498},"where",[464,1216,502],{"class":473},[464,1218,1219],{"class":498},"eq",[464,1221,1222],{"class":473},"(tables.clips.id, clipId))\n",[464,1224,1225],{"class":466,"line":151},[464,1226,1227],{"class":473},"  },\n",[464,1229,1230],{"class":466,"line":156},[464,1231,772],{"class":473},[178,1233,1234,1237,1238,1241,1242,1245],{},[181,1235,1236],{},"clips"," is your own table. A job retries on a throw and every retry is billed\nagain, so throw only when a repeat can help. The URL belongs to the provider and\nexpires; copy the file into R2 if it has to last. For image-to-video, pass\n",[181,1239,1240],{},"imageUrl",": a URL the provider can fetch, or a ",[181,1243,1244],{},"data:"," URI of the frame.",[189,1247,1249],{"id":1248},"change-a-roles-model","Change a role's model",[178,1251,1252,1253,1255],{},"Edit the model id in ",[181,1254,201],{},", or override it per deploy without a rebuild:",[455,1257,1261],{"className":1258,"code":1259,"language":1260,"meta":460,"style":460},"language-sh shiki shiki-themes github-light github-light github-dark","NUXT_AI_WORKERSAI_MODELS_COPY=anthropic\u002Fclaude-sonnet-5\n","sh",[181,1262,1263],{"__ignoreMap":460},[464,1264,1265],{"class":466,"line":12},[464,1266,1259],{},[178,1268,1269,1270,1273,1274,1277,1278,1281,1282,1285,1286,1289,1290,1294],{},"The variable is ",[181,1271,1272],{},"NUXT_AI_\u003CPROVIDER>_MODELS_\u003CROLE>"," and wins over the config.\n",[181,1275,1276],{},"@cf\u002F…"," ids are Cloudflare's own models, billed as Workers AI usage.\n",[181,1279,1280],{},"author\u002Fmodel"," ids are third-party models Cloudflare resells through AI\nGateway and bills to your Cloudflare account, so load credits under AI Gateway >\nCredits first. They also need ",[181,1283,1284],{},"ai.gateway"," set; the shipped ",[181,1287,1288],{},"'default'"," works.\nThe model catalogue is at ",[204,1291,1292],{"href":1292,"rel":1293},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai\u002Fmodels\u002F",[208],".",[189,1296,1298],{"id":1297},"add-a-provider","Add a provider",[178,1300,1301,1302,1305,1306,1309,1310,1313,1314,1316,1317,1320,1321,801],{},"Create ",[181,1303,1304],{},"server\u002Fai\u002Fadapters\u002Fcustom\u002F\u003Cid>.ts"," with a default-exported ",[181,1307,1308],{},"AiAdapter","\nwhose ",[181,1311,1312],{},"id"," is the file name, and implement a method per modality it serves. A\nmethod must not throw: return ",[181,1315,912],{},", with\n",[181,1318,1319],{},"retryable"," only for failures a repeat can fix, such as a 429 or a 5xx.\n",[181,1322,1323],{},"server\u002Fai\u002Fadapters\u002Fcustom\u002Fopenai.ts",[455,1325,1327],{"className":457,"code":1326,"language":459,"meta":460,"style":460},"import type { AiAdapter } from '#server\u002Fai\u002Ftypes'\n\ninterface ChatCompletion {\n  choices?: { message?: { content?: string | null } }[]\n}\n\nconst openai: AiAdapter = {\n  id: 'openai',\n  transport: 'fetch',\n\n  async text(request, config) {\n    if (request.messages.some((m) => typeof m.content !== 'string')) {\n      return { ok: false, reason: 'text messages only', retryable: false }\n    }\n\n    try {\n      const response = await $fetch.raw\u003CChatCompletion>(\n        'https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions',\n        {\n          method: 'POST',\n          headers: { authorization: `Bearer ${config.key}` },\n          body: {\n            model: config.model,\n            messages: [\n              ...(request.system\n                ? [{ role: 'system', content: request.system }]\n                : []),\n              ...request.messages,\n            ],\n          },\n          ignoreResponseError: true,\n        },\n      )\n\n      if (!response.ok) {\n        return {\n          ok: false,\n          reason: `openai answered ${response.status}`,\n          retryable: response.status === 429 || response.status >= 500,\n        }\n      }\n\n      const text = response._data?.choices?.[0]?.message?.content\n\n      return text\n        ? { ok: true, text, provider: 'openai' }\n        : { ok: false, reason: 'openai returned no text', retryable: false }\n    } catch (error) {\n      return { ok: false, reason: `openai: ${String(error)}`, retryable: true }\n    }\n  },\n}\n\nexport default openai\n",[181,1328,1329,1344,1348,1358,1392,1396,1400,1416,1426,1436,1440,1459,1494,1519,1524,1528,1535,1561,1568,1573,1583,1603,1608,1613,1618,1626,1640,1648,1656,1662,1668,1679,1685,1690,1695,1708,1716,1726,1747,1773,1779,1785,1790,1808,1813,1821,1838,1859,1871,1903,1908,1913,1918,1923],{"__ignoreMap":460},[464,1330,1331,1333,1336,1339,1341],{"class":466,"line":12},[464,1332,470],{"class":469},[464,1334,1335],{"class":469}," type",[464,1337,1338],{"class":473}," { AiAdapter } ",[464,1340,477],{"class":469},[464,1342,1343],{"class":480}," '#server\u002Fai\u002Ftypes'\n",[464,1345,1346],{"class":466,"line":17},[464,1347,487],{"emptyLinePlaceholder":486},[464,1349,1350,1353,1356],{"class":466,"line":22},[464,1351,1352],{"class":469},"interface",[464,1354,1355],{"class":498}," ChatCompletion",[464,1357,521],{"class":473},[464,1359,1360,1363,1366,1368,1371,1373,1375,1378,1380,1383,1386,1389],{"class":466,"line":27},[464,1361,1362],{"class":511},"  choices",[464,1364,1365],{"class":469},"?:",[464,1367,553],{"class":473},[464,1369,1370],{"class":511},"message",[464,1372,1365],{"class":469},[464,1374,553],{"class":473},[464,1376,1377],{"class":511},"content",[464,1379,1365],{"class":469},[464,1381,1382],{"class":529}," string",[464,1384,1385],{"class":469}," |",[464,1387,1388],{"class":529}," null",[464,1390,1391],{"class":473}," } }[]\n",[464,1393,1394],{"class":466,"line":32},[464,1395,1001],{"class":473},[464,1397,1398],{"class":466,"line":37},[464,1399,487],{"emptyLinePlaceholder":486},[464,1401,1402,1404,1407,1409,1412,1414],{"class":466,"line":46},[464,1403,811],{"class":469},[464,1405,1406],{"class":529}," openai",[464,1408,801],{"class":469},[464,1410,1411],{"class":498}," AiAdapter",[464,1413,533],{"class":469},[464,1415,521],{"class":473},[464,1417,1418,1421,1424],{"class":466,"line":51},[464,1419,1420],{"class":473},"  id: ",[464,1422,1423],{"class":480},"'openai'",[464,1425,644],{"class":473},[464,1427,1428,1431,1434],{"class":466,"line":56},[464,1429,1430],{"class":473},"  transport: ",[464,1432,1433],{"class":480},"'fetch'",[464,1435,644],{"class":473},[464,1437,1438],{"class":466,"line":64},[464,1439,487],{"emptyLinePlaceholder":486},[464,1441,1442,1444,1447,1449,1452,1454,1457],{"class":466,"line":73},[464,1443,1028],{"class":469},[464,1445,1446],{"class":498}," text",[464,1448,502],{"class":473},[464,1450,1451],{"class":511},"request",[464,1453,779],{"class":473},[464,1455,1456],{"class":511},"config",[464,1458,1050],{"class":473},[464,1460,1461,1463,1466,1469,1472,1475,1477,1479,1482,1485,1488,1491],{"class":466,"line":78},[464,1462,1111],{"class":469},[464,1464,1465],{"class":473}," (request.messages.",[464,1467,1468],{"class":498},"some",[464,1470,1471],{"class":473},"((",[464,1473,1474],{"class":511},"m",[464,1476,515],{"class":473},[464,1478,518],{"class":469},[464,1480,1481],{"class":469}," typeof",[464,1483,1484],{"class":473}," m.content ",[464,1486,1487],{"class":469},"!==",[464,1489,1490],{"class":480}," 'string'",[464,1492,1493],{"class":473},")) {\n",[464,1495,1496,1499,1502,1505,1508,1511,1514,1516],{"class":466,"line":83},[464,1497,1498],{"class":469},"      return",[464,1500,1501],{"class":473}," { ok: ",[464,1503,1504],{"class":529},"false",[464,1506,1507],{"class":473},", reason: ",[464,1509,1510],{"class":480},"'text messages only'",[464,1512,1513],{"class":473},", retryable: ",[464,1515,1504],{"class":529},[464,1517,1518],{"class":473}," }\n",[464,1520,1521],{"class":466,"line":88},[464,1522,1523],{"class":473},"    }\n",[464,1525,1526],{"class":466,"line":93},[464,1527,487],{"emptyLinePlaceholder":486},[464,1529,1530,1533],{"class":466,"line":98},[464,1531,1532],{"class":469},"    try",[464,1534,521],{"class":473},[464,1536,1537,1540,1543,1545,1547,1550,1553,1555,1558],{"class":466,"line":103},[464,1538,1539],{"class":469},"      const",[464,1541,1542],{"class":529}," response",[464,1544,533],{"class":469},[464,1546,536],{"class":469},[464,1548,1549],{"class":473}," $fetch.",[464,1551,1552],{"class":498},"raw",[464,1554,1017],{"class":473},[464,1556,1557],{"class":498},"ChatCompletion",[464,1559,1560],{"class":473},">(\n",[464,1562,1563,1566],{"class":466,"line":108},[464,1564,1565],{"class":480},"        'https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions'",[464,1567,644],{"class":473},[464,1569,1570],{"class":466,"line":113},[464,1571,1572],{"class":473},"        {\n",[464,1574,1575,1578,1581],{"class":466,"line":122},[464,1576,1577],{"class":473},"          method: ",[464,1579,1580],{"class":480},"'POST'",[464,1582,644],{"class":473},[464,1584,1585,1588,1591,1593,1595,1598,1601],{"class":466,"line":127},[464,1586,1587],{"class":473},"          headers: { authorization: ",[464,1589,1590],{"class":480},"`Bearer ${",[464,1592,1456],{"class":473},[464,1594,1294],{"class":480},[464,1596,1597],{"class":473},"key",[464,1599,1600],{"class":480},"}`",[464,1602,688],{"class":473},[464,1604,1605],{"class":466,"line":132},[464,1606,1607],{"class":473},"          body: {\n",[464,1609,1610],{"class":466,"line":137},[464,1611,1612],{"class":473},"            model: config.model,\n",[464,1614,1615],{"class":466,"line":146},[464,1616,1617],{"class":473},"            messages: [\n",[464,1619,1620,1623],{"class":466,"line":151},[464,1621,1622],{"class":469},"              ...",[464,1624,1625],{"class":473},"(request.system\n",[464,1627,1628,1631,1634,1637],{"class":466,"line":156},[464,1629,1630],{"class":469},"                ?",[464,1632,1633],{"class":473}," [{ role: ",[464,1635,1636],{"class":480},"'system'",[464,1638,1639],{"class":473},", content: request.system }]\n",[464,1641,1642,1645],{"class":466,"line":165},[464,1643,1644],{"class":469},"                :",[464,1646,1647],{"class":473}," []),\n",[464,1649,1651,1653],{"class":466,"line":1650},28,[464,1652,1622],{"class":469},[464,1654,1655],{"class":473},"request.messages,\n",[464,1657,1659],{"class":466,"line":1658},29,[464,1660,1661],{"class":473},"            ],\n",[464,1663,1665],{"class":466,"line":1664},30,[464,1666,1667],{"class":473},"          },\n",[464,1669,1671,1674,1677],{"class":466,"line":1670},31,[464,1672,1673],{"class":473},"          ignoreResponseError: ",[464,1675,1676],{"class":529},"true",[464,1678,644],{"class":473},[464,1680,1682],{"class":466,"line":1681},32,[464,1683,1684],{"class":473},"        },\n",[464,1686,1688],{"class":466,"line":1687},33,[464,1689,1207],{"class":473},[464,1691,1693],{"class":466,"line":1692},34,[464,1694,487],{"emptyLinePlaceholder":486},[464,1696,1698,1701,1703,1705],{"class":466,"line":1697},35,[464,1699,1700],{"class":469},"      if",[464,1702,508],{"class":473},[464,1704,1116],{"class":469},[464,1706,1707],{"class":473},"response.ok) {\n",[464,1709,1711,1714],{"class":466,"line":1710},36,[464,1712,1713],{"class":469},"        return",[464,1715,521],{"class":473},[464,1717,1719,1722,1724],{"class":466,"line":1718},37,[464,1720,1721],{"class":473},"          ok: ",[464,1723,1504],{"class":529},[464,1725,644],{"class":473},[464,1727,1729,1732,1735,1738,1740,1743,1745],{"class":466,"line":1728},38,[464,1730,1731],{"class":473},"          reason: ",[464,1733,1734],{"class":480},"`openai answered ${",[464,1736,1737],{"class":473},"response",[464,1739,1294],{"class":480},[464,1741,1742],{"class":473},"status",[464,1744,1600],{"class":480},[464,1746,644],{"class":473},[464,1748,1750,1753,1756,1759,1762,1765,1768,1771],{"class":466,"line":1749},39,[464,1751,1752],{"class":473},"          retryable: response.status ",[464,1754,1755],{"class":469},"===",[464,1757,1758],{"class":529}," 429",[464,1760,1761],{"class":469}," ||",[464,1763,1764],{"class":473}," response.status ",[464,1766,1767],{"class":469},">=",[464,1769,1770],{"class":529}," 500",[464,1772,644],{"class":473},[464,1774,1776],{"class":466,"line":1775},40,[464,1777,1778],{"class":473},"        }\n",[464,1780,1782],{"class":466,"line":1781},41,[464,1783,1784],{"class":473},"      }\n",[464,1786,1788],{"class":466,"line":1787},42,[464,1789,487],{"emptyLinePlaceholder":486},[464,1791,1793,1795,1797,1799,1802,1805],{"class":466,"line":1792},43,[464,1794,1539],{"class":469},[464,1796,1446],{"class":529},[464,1798,533],{"class":469},[464,1800,1801],{"class":473}," response._data?.choices?.[",[464,1803,1804],{"class":529},"0",[464,1806,1807],{"class":473},"]?.message?.content\n",[464,1809,1811],{"class":466,"line":1810},44,[464,1812,487],{"emptyLinePlaceholder":486},[464,1814,1816,1818],{"class":466,"line":1815},45,[464,1817,1498],{"class":469},[464,1819,1820],{"class":473}," text\n",[464,1822,1824,1827,1829,1831,1834,1836],{"class":466,"line":1823},46,[464,1825,1826],{"class":469},"        ?",[464,1828,1501],{"class":473},[464,1830,1676],{"class":529},[464,1832,1833],{"class":473},", text, provider: ",[464,1835,1423],{"class":480},[464,1837,1518],{"class":473},[464,1839,1841,1844,1846,1848,1850,1853,1855,1857],{"class":466,"line":1840},47,[464,1842,1843],{"class":469},"        :",[464,1845,1501],{"class":473},[464,1847,1504],{"class":529},[464,1849,1507],{"class":473},[464,1851,1852],{"class":480},"'openai returned no text'",[464,1854,1513],{"class":473},[464,1856,1504],{"class":529},[464,1858,1518],{"class":473},[464,1860,1862,1865,1868],{"class":466,"line":1861},48,[464,1863,1864],{"class":473},"    } ",[464,1866,1867],{"class":469},"catch",[464,1869,1870],{"class":473}," (error) {\n",[464,1872,1874,1876,1878,1880,1882,1885,1887,1889,1892,1895,1897,1899,1901],{"class":466,"line":1873},49,[464,1875,1498],{"class":469},[464,1877,1501],{"class":473},[464,1879,1504],{"class":529},[464,1881,1507],{"class":473},[464,1883,1884],{"class":480},"`openai: ${",[464,1886,652],{"class":498},[464,1888,502],{"class":480},[464,1890,1891],{"class":473},"error",[464,1893,1894],{"class":480},")",[464,1896,1600],{"class":480},[464,1898,1513],{"class":473},[464,1900,1676],{"class":529},[464,1902,1518],{"class":473},[464,1904,1906],{"class":466,"line":1905},50,[464,1907,1523],{"class":473},[464,1909,1911],{"class":466,"line":1910},51,[464,1912,1227],{"class":473},[464,1914,1916],{"class":466,"line":1915},52,[464,1917,1001],{"class":473},[464,1919,1921],{"class":466,"line":1920},53,[464,1922,487],{"emptyLinePlaceholder":486},[464,1924,1926,1928,1930],{"class":466,"line":1925},54,[464,1927,492],{"class":469},[464,1929,495],{"class":469},[464,1931,1932],{"class":473}," openai\n",[178,1934,1935],{},"Then list it, declare what it serves, and give each role it should serve a\nmodel:",[455,1937,1939],{"className":457,"code":1938,"language":459,"meta":460,"style":460},"ai: {\n  enabled: true,\n  providers: ['workersai', 'openai'],\n  gateway: 'default',\n  adapters: {\n    openai: { modalities: ['text'], credentials: ['apiKey'] },\n  },\n  roles: {\n    copy: {\n      modality: 'text',\n      models: {\n        workersai: '@cf\u002Fmeta\u002Fllama-3.1-8b-instruct-fast',\n        openai: 'gpt-5-mini',\n      },\n    },\n  },\n},\n",[181,1940,1941,1948,1960,1978,1989,1996,2026,2030,2037,2044,2055,2062,2074,2086,2091,2096,2100],{"__ignoreMap":460},[464,1942,1943,1945],{"class":466,"line":12},[464,1944,176],{"class":498},[464,1946,1947],{"class":473},": {\n",[464,1949,1950,1953,1956,1958],{"class":466,"line":17},[464,1951,1952],{"class":498},"  enabled",[464,1954,1955],{"class":473},": ",[464,1957,1676],{"class":529},[464,1959,644],{"class":473},[464,1961,1962,1965,1968,1971,1973,1975],{"class":466,"line":22},[464,1963,1964],{"class":498},"  providers",[464,1966,1967],{"class":473},": [",[464,1969,1970],{"class":480},"'workersai'",[464,1972,779],{"class":473},[464,1974,1423],{"class":480},[464,1976,1977],{"class":473},"],\n",[464,1979,1980,1983,1985,1987],{"class":466,"line":27},[464,1981,1982],{"class":498},"  gateway",[464,1984,1955],{"class":473},[464,1986,1288],{"class":480},[464,1988,644],{"class":473},[464,1990,1991,1994],{"class":466,"line":32},[464,1992,1993],{"class":498},"  adapters",[464,1995,1947],{"class":473},[464,1997,1998,2001,2004,2007,2009,2012,2015,2018,2020,2023],{"class":466,"line":37},[464,1999,2000],{"class":498},"    openai",[464,2002,2003],{"class":473},": { ",[464,2005,2006],{"class":498},"modalities",[464,2008,1967],{"class":473},[464,2010,2011],{"class":480},"'text'",[464,2013,2014],{"class":473},"], ",[464,2016,2017],{"class":498},"credentials",[464,2019,1967],{"class":473},[464,2021,2022],{"class":480},"'apiKey'",[464,2024,2025],{"class":473},"] },\n",[464,2027,2028],{"class":466,"line":46},[464,2029,1227],{"class":473},[464,2031,2032,2035],{"class":466,"line":51},[464,2033,2034],{"class":498},"  roles",[464,2036,1947],{"class":473},[464,2038,2039,2042],{"class":466,"line":56},[464,2040,2041],{"class":498},"    copy",[464,2043,1947],{"class":473},[464,2045,2046,2049,2051,2053],{"class":466,"line":64},[464,2047,2048],{"class":498},"      modality",[464,2050,1955],{"class":473},[464,2052,2011],{"class":480},[464,2054,644],{"class":473},[464,2056,2057,2060],{"class":466,"line":73},[464,2058,2059],{"class":498},"      models",[464,2061,1947],{"class":473},[464,2063,2064,2067,2069,2072],{"class":466,"line":78},[464,2065,2066],{"class":498},"        workersai",[464,2068,1955],{"class":473},[464,2070,2071],{"class":480},"'@cf\u002Fmeta\u002Fllama-3.1-8b-instruct-fast'",[464,2073,644],{"class":473},[464,2075,2076,2079,2081,2084],{"class":466,"line":83},[464,2077,2078],{"class":498},"        openai",[464,2080,1955],{"class":473},[464,2082,2083],{"class":480},"'gpt-5-mini'",[464,2085,644],{"class":473},[464,2087,2088],{"class":466,"line":88},[464,2089,2090],{"class":473},"      },\n",[464,2092,2093],{"class":466,"line":93},[464,2094,2095],{"class":473},"    },\n",[464,2097,2098],{"class":466,"line":98},[464,2099,1227],{"class":473},[464,2101,2102],{"class":466,"line":103},[464,2103,2104],{"class":473},"},\n",[178,2106,2107,2108,2110,2111,2114,2115,2118,2119,2122,2123,202,2126,2129],{},"The build refuses a provider you added until ",[181,2109,2006],{}," lists what it\nserves. The key arrives as ",[181,2112,2113],{},"config.key"," from ",[181,2116,2117],{},"NUXT_AI_OPENAI_API_KEY",", and the\nWorker logs that variable at boot while it is unset. Without a key the provider\nis skipped. Restart ",[181,2120,2121],{},"pnpm dev"," after adding the file. A file named\n",[181,2124,2125],{},"workersai.ts",[181,2127,2128],{},"custom\u002F"," replaces the shipped adapter.",[189,2131,2133],{"id":2132},"turn-ai-off","Turn AI off",[178,2135,2136,2137,2140,2141,2143,2144,2147,2148,1294],{},"Set ",[181,2138,2139],{},"ai.enabled: false",". No adapter is bundled, and every entry point answers\nwith a 501. You can then drop the ",[181,2142,176],{}," block from ",[181,2145,2146],{},"wrangler.jsonc"," and run\n",[181,2149,2150],{},"pnpm cf-typegen",[189,2152,20],{"id":2153},"configuration",[243,2155,2156,2172],{},[246,2157,2158],{},[249,2159,2160,2163,2166,2169],{},[252,2161,2162],{},"Key or variable",[252,2164,2165],{},"Where",[252,2167,2168],{},"Default",[252,2170,2171],{},"What it does",[268,2173,2174,2192,2210,2227,2243,2260,2281,2296,2310],{},[249,2175,2176,2181,2185,2189],{},[273,2177,2178],{},[181,2179,2180],{},"ai.enabled",[273,2182,2183],{},[181,2184,212],{},[273,2186,2187],{},[181,2188,1676],{},[273,2190,2191],{},"Off bundles no adapter; every call answers 501.",[249,2193,2194,2198,2202,2207],{},[273,2195,2196],{},[181,2197,233],{},[273,2199,2200],{},[181,2201,212],{},[273,2203,2204],{},[181,2205,2206],{},"['workersai']",[273,2208,2209],{},"Providers to bundle, in fallback order.",[249,2211,2212,2216,2220,2224],{},[273,2213,2214],{},[181,2215,1284],{},[273,2217,2218],{},[181,2219,212],{},[273,2221,2222],{},[181,2223,1288],{},[273,2225,2226],{},"AI Gateway id every Workers AI call goes through.",[249,2228,2229,2233,2237,2240],{},[273,2230,2231],{},[181,2232,201],{},[273,2234,2235],{},[181,2236,212],{},[273,2238,2239],{},"six roles",[273,2241,2242],{},"The catalogue of model calls.",[249,2244,2245,2250,2254,2257],{},[273,2246,2247],{},[181,2248,2249],{},"ai.adapters",[273,2251,2252],{},[181,2253,212],{},[273,2255,2256],{},"none",[273,2258,2259],{},"Modalities and credential keys of an adapter you added.",[249,2261,2262,2267,2271,2274],{},[273,2263,2264],{},[181,2265,2266],{},"\"ai\": { \"binding\": \"AI\" }",[273,2268,2269],{},[181,2270,2146],{},[273,2272,2273],{},"present",[273,2275,2276,2277,2280],{},"The binding ",[181,2278,2279],{},"workersai"," calls. The build fails without it.",[249,2282,2283,2287,2290,2293],{},[273,2284,2285],{},[181,2286,1272],{},[273,2288,2289],{},"environment",[273,2291,2292],{},"unset",[273,2294,2295],{},"Overrides one role's model for one provider.",[249,2297,2298,2303,2305,2307],{},[273,2299,2300],{},[181,2301,2302],{},"NUXT_AI_\u003CPROVIDER>_API_KEY",[273,2304,2289],{},[273,2306,2292],{},[273,2308,2309],{},"Key of a provider you added. Workers AI needs none.",[249,2311,2312,2317,2322,2324],{},[273,2313,2314],{},[181,2315,2316],{},"NUXT_DEV_REMOTE_AI",[273,2318,2319],{},[181,2320,2321],{},".env",[273,2323,2292],{},[273,2325,2326,2328,2329,2331],{},[181,2327,1676],{}," lets ",[181,2330,2121],{}," call Workers AI on your Cloudflare account.",[189,2333,2335],{"id":2334},"good-to-know","Good to know",[2337,2338,2339,2354,2365,2375,2384],"ul",{},[197,2340,2341,2343,2344,2347,2348,202,2351,2353],{},[181,2342,2121],{}," runs without the AI binding, so every call answers 501 until you\nrun ",[181,2345,2346],{},"pnpm exec wrangler login"," and set ",[181,2349,2350],{},"NUXT_DEV_REMOTE_AI=true",[181,2352,2321],{},".\nThose calls are real and billed.",[197,2355,2356,2357,2364],{},"Workers AI checks each model's input against that model's schema and refuses\nunknown fields with error 5006. The adapter in\n",[204,2358,2361],{"href":2359,"rel":2360,"title":209},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fai\u002Fadapters\u002Fworkersai.ts",[208],[181,2362,2363],{},"server\u002Fai\u002Fadapters\u002Fworkersai.ts"," maps\nrequests for Flux, the diffusion image models, Aura, MeloTTS and\nSeedance-style video. A model from another family may need its own mapping\nthere.",[197,2366,2367,2368,2371,2372,2374],{},"A provider you add receives only the credential named ",[181,2369,2370],{},"apiKey",", as\n",[181,2373,2113],{},". Other keys you list are read into runtime config but never\nreach the adapter.",[197,2376,2377,2379,2380,2383],{},[181,2378,373],{}," treats silence as a failure and throws a 502. Use\n",[181,2381,2382],{},"attemptAi"," where empty audio is expected.",[197,2385,2386,2393],{},[204,2387,2390],{"href":2388,"rel":2389,"title":209},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Ftest\u002Funit\u002Fai-registry.test.ts",[208],[181,2391,2392],{},"test\u002Funit\u002Fai-registry.test.ts"," expects\nthe shipped catalogue to cover all six modalities. If you remove the last\nrole of a modality, update that test with it.",[2395,2396,2397],"style",{},"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);}html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":460,"searchDepth":17,"depth":17,"links":2399},[2400,2401,2402,2406,2407,2408,2409,2410,2411],{"id":191,"depth":17,"text":192},{"id":240,"depth":17,"text":241},{"id":446,"depth":17,"text":447,"children":2403},[2404,2405],{"id":892,"depth":22,"text":893},{"id":902,"depth":22,"text":903},{"id":920,"depth":17,"text":921},{"id":1248,"depth":17,"text":1249},{"id":1297,"depth":17,"text":1298},{"id":2132,"depth":17,"text":2133},{"id":2153,"depth":17,"text":20},{"id":2334,"depth":17,"text":2335},[2413,2414],{"path":105,"title":106,"description":107,"navTitle":106,"group":67,"order":108},{"path":119,"title":120,"description":121,"navTitle":120,"group":116,"order":122}]