@netlify/agent-runner-cli 1.68.1 → 1.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin-local.js +1 -1
- package/dist/bin.js +1 -1
- package/dist/index.js +1 -1
- package/dist/skills/netlify-inference/SKILL.md +510 -0
- package/package.json +1 -1
package/dist/bin-local.js
CHANGED
|
@@ -5,7 +5,7 @@ import $ from"process";import Sr from"path";import vr from"fs";import so from"mi
|
|
|
5
5
|
${i}
|
|
6
6
|
</extracted_error_chunk>`).join(`
|
|
7
7
|
|
|
8
|
-
`);return s.length>e.length*.8?e:s}import{execSync as Sn}from"child_process";import lr from"fs/promises";import vn from"path";import te from"process";import{getTracer as An}from"@netlify/otel";import Ie from"process";var se=class extends Error{constructor(r,o,n){super(r);this.statusCode=o;this.userMessage=n;this.name="GracefulShutdownError"}},Tt=e=>e instanceof se;var Ce=Ie.env.NETLIFY_API_URL,Ne=Ie.env.NETLIFY_API_TOKEN,W=_("api"),Pe=()=>Ie.env.NETLIFY_LOCAL_MODE==="true",Te=async(e,t={})=>{if(!Ce||!Ne)throw new Error("No API URL or token");let r=new URL(e,Ce),o={...t,headers:{...t.headers,Authorization:`Bearer ${Ne}`}};Ie.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),s=n.ok&&n.status<=299;if(Ie.env.AGENT_RUNNERS_DEBUG==="true")W.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{W.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");W.log(`Request ID for ${r}: ${a||"N/A"}`)}if(s||W.error(`Got status ${n.status} for request ${r}`),t.raw){if(!s)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let i=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!s){let a=typeof i=="string"?i:JSON.stringify(i);throw n.status===404?new se(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===403&&t.gracefulOn403?new se(`API request failed: 403 - ${a}`,403,"Credit limit reached. Please add more credits to continue using Agent Runners."):new Error(`API request failed: ${n.status} - ${a}`)}return i},xt=e=>{W.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Ce=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Ne=e.constants.NETLIFY_API_TOKEN)},Rt=()=>({apiUrl:Ce,token:Ne}),xe=async(e,t)=>Pe()?(W.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):Te(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),M=async(e,t,r)=>Pe()?(W.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var St=async(e,t)=>Pe()?(W.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`),vt=(e,t,r)=>Te(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn403:!0}),At=async(e,t)=>Pe()?(W.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):Te(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Ke=async(e,t)=>{W.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var ae=_("ai_gateway"),Je=null;var bt=async()=>{if(Je)return Je;ae.log("Fetching available AI gateway providers");let e=await fetch(`${Rt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return Je=t,ae.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},$r=async(e,t)=>{let o=(await bt()).providers[e];if(!o)return ae.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return ae.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Ct=async({netlify:e,config:t})=>{let r,o,n,s,i=e.constants?.SITE_ID;if(!i)throw new Error("No site id");let a=async()=>{clearTimeout(n),ae.log("Requesting AI gateway information");let c=await vt(i,t.id,t.sessionId);if({token:r,url:s}=c,o=c.expires_at?c.expires_at*1e3:void 0,ae.log("Got AI gateway information",{token:!!r,expiresAt:o,url:s}),o){let u=o-Date.now()-6e4;u>0&&(n=setTimeout(()=>{a()},u))}};return await Promise.all([a(),bt()]),{get url(){return s},get token(){return r},isModelAvailableForProvider:$r}};import J from"process";import Z from"path";import ke from"fs";import{fileURLToPath as jr}from"url";import{createRequire as Yr}from"module";import{execa as Br,execaCommand as Go}from"execa";import{Transform as Lr}from"stream";var Fr=new Set(["NODE_ENV","PATH","HOME","USER","USERNAME","SHELL","PWD","OLDPWD","TMPDIR","TMP","TEMP","LANG","TERM","EDITOR","PAGER","OS","PROCESSOR_ARCHITECTURE","PROCESSOR_IDENTIFIER","SYSTEMROOT","WINDIR","PROGRAMFILES","PROGRAMFILES(X86)","PROGRAMDATA","APPDATA","LOCALAPPDATA","NODE_OPTIONS","NODE_PATH","NODE_DEBUG","NODE_NO_WARNINGS","npm_config_registry","npm_config_cache","npm_execpath","npm_node_execpath","CI","GITHUB_ACTIONS","GITHUB_WORKSPACE","GITHUB_REPOSITORY","GITHUB_REF","BUILDKITE","BUILDKITE_BRANCH","BUILDKITE_COMMIT","BUILDKITE_BUILD_NUMBER","JENKINS_URL","TRAVIS","CIRCLECI","DISPLAY","COLORTERM","TERM_PROGRAM","TERM_PROGRAM_VERSION","COLUMNS","LINES","HISTSIZE","HISTFILE","NETLIFY_AGENT_RUNNER_ID","NETLIFY_AGENT_RUNNER_SESSION_ID","NETLIFY_AGENT_RUNNER_PROMPT","NETLIFY_AGENT_RUNNER_AGENT","NETLIFY_AGENT_RUNNER_MODEL","NETLIFY_AGENT_RUNNER_CONTEXT","NETLIFY_AGENT_RUNNER_HAS_REPO","NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED","NETLIFY_AGENT_RUNNER_SHA","NETLIFY_TEAM_TYPE","AGENT_RUNNERS_DEBUG","NETLIFY_TEAM_ID","NETLIFY_AGENT_RUNNER_USER_ID","SITE_NAME"]),Dr=new Set(["true","false","undefined","null","deploy","project","claude","gemini","codex",""]);function Mr(){return Object.entries(process.env).filter(([e,t])=>!(!t||Fr.has(e)||Dr.has(t)||!isNaN(Number(t))||t.length<5)).map(([,e])=>e).filter(Boolean)}function z(e){if(typeof e!="string")return e;let t=Mr();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(Ur(o),"g");r=r.replace(n,"******")}),r}function Ur(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var le=class extends Lr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),s=z(n);o(null,s)}};function Nt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,s){let i=typeof o=="string"?z(o):o;return typeof n=="function"?t(i,n):t(i,n,s)},process.stderr.write=function(o,n,s){let i=typeof o=="string"?z(o):o;return typeof n=="function"?r(i,n):r(i,n,s)}}var Re=null,Pt=e=>(Re&&Re.destroy(),Re=new ee({totalAllowedTime:e}),Re),kt=()=>Re;var ee=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),s=null,i=null;o!==void 0&&(i=new Promise((a,c)=>{s=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return i?await Promise.race([r(),i]):await r()}finally{n(),s&&clearTimeout(s)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Ot="netlify-agent-runner-context.md",Ve="task-history",K=".netlify",ne="results.md",Xe="assets";var oe=1800*1e3;var $t={name:"@netlify/agent-runner-cli",type:"module",version:"1.68.1",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.37","@anthropic-ai/sdk":"0.72.1","@google/gemini-cli":"0.27.3","@netlify/otel":"^5.1.1","@openai/codex":"0.93.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.208.0",execa:"^9.6.1",kaddidlehopper:"^0.1.2",minimist:"^1.2.8",openai:"6.17.0"}};var qr=jr(import.meta.url),Hr=Z.dirname(qr),Wr=Yr(import.meta.url),ce=_("shell"),ze=new Set,Kr={preferLocal:!0},x=(e,t,r)=>{let[o,n]=Jr(t,r),s={...Kr,...n},i=Br(e,o,s);Vr(i,s),zr(i);let a=r?.idleTimeout;return a&&a>0&&Xr(i,a),i};var Jr=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},Vr=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(J.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new le).pipe(J.stdout),e.stdout?.pipe(new le).pipe(J.stdout),e.stderr?.pipe(new le).pipe(J.stderr);return}e.stdout?.pipe(J.stdout),e.stderr?.pipe(J.stderr)},Ze=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(J.kill(-e.pid,t),ce.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ce.error("Error killing process:",r),!1}},Lt=e=>Ze(e,"SIGKILL"),Xr=(e,t)=>{let r=null,o=()=>{ce.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),Ze(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ce.log(`Force killing idle process ${e.pid}`),Lt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let s=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",s),e.on("error",s)},zr=e=>{ze.add(e);let t=kt();if(t){let r=t.onTimesUp(()=>{ce.log(`Global timer expired, killing process ${e.pid}`),Ze(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ce.log(`Force killing process ${e.pid} after timeout`),Lt(e))},5e3)});e.on("exit",()=>{ze.delete(e),r()}),e.on("error",()=>{ze.delete(e),r()})}};function Oe(e,t){return!!Q(e,t)}function Q(e,t){if(!J.env.NETLIFY_LOCAL_MODE)try{let n=Wr.resolve($t.name),s=Z.dirname(n);for(;s!==Z.dirname(s);){let i=Z.dirname(s);if(Z.basename(i)==="node_modules"){let a=Z.join(i,".bin",t);if(ke.existsSync(a))return a;break}s=i}}catch(n){console.error("Could not resolve package.json",n)}if(J.env.NODE_PATH){let n=Z.join(J.env.NODE_PATH,".bin",t);if(ke.existsSync(n))return n}let r=Z.join(e,"node_modules",".bin",t);if(ke.existsSync(r))return r;let o=Z.join(Hr,"..","node_modules",".bin",t);if(ke.existsSync(o))return o}var Zr=_("utils"),Qr=e=>new Promise(t=>{setTimeout(t,e)}),Ft=(e,t=3e3)=>{let r=!1,o=null,n=[],s=null,i=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,u=new Promise(d=>{c=d});return s=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Qr(t),!o)return r=!1,s=null,d;let p=o,m=n;o=null,n=[],d=await e(...p),m.forEach(g=>{g(d)})}})(),u};return i.flush=async()=>{if((r||o)&&s)return await s,i.flush()},i},ue=(e,t,r=!1)=>{let o=null,n=null,s=null,i=function(...a){n=a,s=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(s,n),n=null,s=null)},t),c&&(e.apply(s,n),n=null,s=null)};return i.cancel=()=>{clearTimeout(o),o=null,n=null,s=null},i.flush=()=>{if(o){clearTimeout(o);let a=n,c=s;o=null,n=null,s=null,e.apply(c,a)}},i},Dt=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):Zr.error("Could not parse JSON",o))}},Mt=(e,t)=>{let n=".netlify.app",s="agent-";if(!t)return`${s}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=s.length+6){let u=Math.min(c-s.length,e.length);return`${s}${e.slice(0,u)}`}return e.slice(0,c)};var en=50*1024,Qe=(e,t=en)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as Ut}from"buffer";import tn from"path";var Gt=_("repo"),Yt=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{Gt.info("Getting runner diffs");let o=await nn(r),{hasChanges:n}=o,{status:s}=o;if(!n)return{hasChanges:!1};if(!t){let w=sn(s);await an(w,r)}Gt.info("Changes after processing"),await tt(r);let i=await rt(s,r);if(await et(i,r),n=await on(r),!n)return{hasChanges:!1,ignored:i};process.env.NETLIFY_INTERNAL_GIT="1";try{await x("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await x("git",["diff",e.runSha,"HEAD"],a),u=String(c.stdout??"");if(n=!!u,!n)return await jt(r),{hasChanges:!1,ignored:i};let d=await x("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),m,g;if(e.sha){let w=await x("git",["diff",e.sha,"HEAD"],a);m=String(w.stdout??"");let I=await x("git",["diff",e.sha,"HEAD","--binary"],a),E=String(I.stdout??"");m!==E&&(g=Ut.from(E).toString("base64"))}await jt(r);let h={hasChanges:!0,diff:u,resultDiff:m,ignored:i};return u!==p&&(h.diffBinary=Ut.from(p).toString("base64")),g&&(h.resultDiffBinary=g),h},jt=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await x("git",["reset","--soft","HEAD~1"],{cwd:e})},et=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await x("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},tt=async(e=process.cwd())=>{let t=await x("git",["status","-s"],{cwd:e});return String(t.stdout??"")},Bt=/.. (.+)?\.log$/,rn=[Bt],nn=async(e=process.cwd())=>{let t=await tt(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
8
|
+
`);return s.length>e.length*.8?e:s}import{execSync as Sn}from"child_process";import lr from"fs/promises";import vn from"path";import te from"process";import{getTracer as An}from"@netlify/otel";import Ie from"process";var se=class extends Error{constructor(r,o,n){super(r);this.statusCode=o;this.userMessage=n;this.name="GracefulShutdownError"}},Tt=e=>e instanceof se;var Ce=Ie.env.NETLIFY_API_URL,Ne=Ie.env.NETLIFY_API_TOKEN,W=_("api"),Pe=()=>Ie.env.NETLIFY_LOCAL_MODE==="true",Te=async(e,t={})=>{if(!Ce||!Ne)throw new Error("No API URL or token");let r=new URL(e,Ce),o={...t,headers:{...t.headers,Authorization:`Bearer ${Ne}`}};Ie.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),s=n.ok&&n.status<=299;if(Ie.env.AGENT_RUNNERS_DEBUG==="true")W.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{W.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");W.log(`Request ID for ${r}: ${a||"N/A"}`)}if(s||W.error(`Got status ${n.status} for request ${r}`),t.raw){if(!s)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let i=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!s){let a=typeof i=="string"?i:JSON.stringify(i);throw n.status===404?new se(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===403&&t.gracefulOn403?new se(`API request failed: 403 - ${a}`,403,"Credit limit reached. Please add more credits to continue using Agent Runners."):new Error(`API request failed: ${n.status} - ${a}`)}return i},xt=e=>{W.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Ce=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Ne=e.constants.NETLIFY_API_TOKEN)},Rt=()=>({apiUrl:Ce,token:Ne}),xe=async(e,t)=>Pe()?(W.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):Te(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),M=async(e,t,r)=>Pe()?(W.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var St=async(e,t)=>Pe()?(W.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`),vt=(e,t,r)=>Te(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn403:!0}),At=async(e,t)=>Pe()?(W.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):Te(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Ke=async(e,t)=>{W.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var ae=_("ai_gateway"),Je=null;var bt=async()=>{if(Je)return Je;ae.log("Fetching available AI gateway providers");let e=await fetch(`${Rt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return Je=t,ae.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},$r=async(e,t)=>{let o=(await bt()).providers[e];if(!o)return ae.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return ae.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Ct=async({netlify:e,config:t})=>{let r,o,n,s,i=e.constants?.SITE_ID;if(!i)throw new Error("No site id");let a=async()=>{clearTimeout(n),ae.log("Requesting AI gateway information");let c=await vt(i,t.id,t.sessionId);if({token:r,url:s}=c,o=c.expires_at?c.expires_at*1e3:void 0,ae.log("Got AI gateway information",{token:!!r,expiresAt:o,url:s}),o){let u=o-Date.now()-6e4;u>0&&(n=setTimeout(()=>{a()},u))}};return await Promise.all([a(),bt()]),{get url(){return s},get token(){return r},isModelAvailableForProvider:$r}};import J from"process";import Z from"path";import ke from"fs";import{fileURLToPath as jr}from"url";import{createRequire as Yr}from"module";import{execa as Br,execaCommand as Go}from"execa";import{Transform as Lr}from"stream";var Fr=new Set(["NODE_ENV","PATH","HOME","USER","USERNAME","SHELL","PWD","OLDPWD","TMPDIR","TMP","TEMP","LANG","TERM","EDITOR","PAGER","OS","PROCESSOR_ARCHITECTURE","PROCESSOR_IDENTIFIER","SYSTEMROOT","WINDIR","PROGRAMFILES","PROGRAMFILES(X86)","PROGRAMDATA","APPDATA","LOCALAPPDATA","NODE_OPTIONS","NODE_PATH","NODE_DEBUG","NODE_NO_WARNINGS","npm_config_registry","npm_config_cache","npm_execpath","npm_node_execpath","CI","GITHUB_ACTIONS","GITHUB_WORKSPACE","GITHUB_REPOSITORY","GITHUB_REF","BUILDKITE","BUILDKITE_BRANCH","BUILDKITE_COMMIT","BUILDKITE_BUILD_NUMBER","JENKINS_URL","TRAVIS","CIRCLECI","DISPLAY","COLORTERM","TERM_PROGRAM","TERM_PROGRAM_VERSION","COLUMNS","LINES","HISTSIZE","HISTFILE","NETLIFY_AGENT_RUNNER_ID","NETLIFY_AGENT_RUNNER_SESSION_ID","NETLIFY_AGENT_RUNNER_PROMPT","NETLIFY_AGENT_RUNNER_AGENT","NETLIFY_AGENT_RUNNER_MODEL","NETLIFY_AGENT_RUNNER_CONTEXT","NETLIFY_AGENT_RUNNER_HAS_REPO","NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED","NETLIFY_AGENT_RUNNER_SHA","NETLIFY_TEAM_TYPE","AGENT_RUNNERS_DEBUG","NETLIFY_TEAM_ID","NETLIFY_AGENT_RUNNER_USER_ID","SITE_NAME"]),Dr=new Set(["true","false","undefined","null","deploy","project","claude","gemini","codex",""]);function Mr(){return Object.entries(process.env).filter(([e,t])=>!(!t||Fr.has(e)||Dr.has(t)||!isNaN(Number(t))||t.length<5)).map(([,e])=>e).filter(Boolean)}function z(e){if(typeof e!="string")return e;let t=Mr();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(Ur(o),"g");r=r.replace(n,"******")}),r}function Ur(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var le=class extends Lr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),s=z(n);o(null,s)}};function Nt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,s){let i=typeof o=="string"?z(o):o;return typeof n=="function"?t(i,n):t(i,n,s)},process.stderr.write=function(o,n,s){let i=typeof o=="string"?z(o):o;return typeof n=="function"?r(i,n):r(i,n,s)}}var Re=null,Pt=e=>(Re&&Re.destroy(),Re=new ee({totalAllowedTime:e}),Re),kt=()=>Re;var ee=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),s=null,i=null;o!==void 0&&(i=new Promise((a,c)=>{s=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return i?await Promise.race([r(),i]):await r()}finally{n(),s&&clearTimeout(s)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Ot="netlify-agent-runner-context.md",Ve="task-history",K=".netlify",ne="results.md",Xe="assets";var oe=1800*1e3;var $t={name:"@netlify/agent-runner-cli",type:"module",version:"1.69.0",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.37","@anthropic-ai/sdk":"0.72.1","@google/gemini-cli":"0.27.3","@netlify/otel":"^5.1.1","@openai/codex":"0.93.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.208.0",execa:"^9.6.1",kaddidlehopper:"^0.1.2",minimist:"^1.2.8",openai:"6.17.0"}};var qr=jr(import.meta.url),Hr=Z.dirname(qr),Wr=Yr(import.meta.url),ce=_("shell"),ze=new Set,Kr={preferLocal:!0},x=(e,t,r)=>{let[o,n]=Jr(t,r),s={...Kr,...n},i=Br(e,o,s);Vr(i,s),zr(i);let a=r?.idleTimeout;return a&&a>0&&Xr(i,a),i};var Jr=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},Vr=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(J.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new le).pipe(J.stdout),e.stdout?.pipe(new le).pipe(J.stdout),e.stderr?.pipe(new le).pipe(J.stderr);return}e.stdout?.pipe(J.stdout),e.stderr?.pipe(J.stderr)},Ze=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(J.kill(-e.pid,t),ce.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ce.error("Error killing process:",r),!1}},Lt=e=>Ze(e,"SIGKILL"),Xr=(e,t)=>{let r=null,o=()=>{ce.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),Ze(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ce.log(`Force killing idle process ${e.pid}`),Lt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let s=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",s),e.on("error",s)},zr=e=>{ze.add(e);let t=kt();if(t){let r=t.onTimesUp(()=>{ce.log(`Global timer expired, killing process ${e.pid}`),Ze(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ce.log(`Force killing process ${e.pid} after timeout`),Lt(e))},5e3)});e.on("exit",()=>{ze.delete(e),r()}),e.on("error",()=>{ze.delete(e),r()})}};function Oe(e,t){return!!Q(e,t)}function Q(e,t){if(!J.env.NETLIFY_LOCAL_MODE)try{let n=Wr.resolve($t.name),s=Z.dirname(n);for(;s!==Z.dirname(s);){let i=Z.dirname(s);if(Z.basename(i)==="node_modules"){let a=Z.join(i,".bin",t);if(ke.existsSync(a))return a;break}s=i}}catch(n){console.error("Could not resolve package.json",n)}if(J.env.NODE_PATH){let n=Z.join(J.env.NODE_PATH,".bin",t);if(ke.existsSync(n))return n}let r=Z.join(e,"node_modules",".bin",t);if(ke.existsSync(r))return r;let o=Z.join(Hr,"..","node_modules",".bin",t);if(ke.existsSync(o))return o}var Zr=_("utils"),Qr=e=>new Promise(t=>{setTimeout(t,e)}),Ft=(e,t=3e3)=>{let r=!1,o=null,n=[],s=null,i=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,u=new Promise(d=>{c=d});return s=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Qr(t),!o)return r=!1,s=null,d;let p=o,m=n;o=null,n=[],d=await e(...p),m.forEach(g=>{g(d)})}})(),u};return i.flush=async()=>{if((r||o)&&s)return await s,i.flush()},i},ue=(e,t,r=!1)=>{let o=null,n=null,s=null,i=function(...a){n=a,s=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(s,n),n=null,s=null)},t),c&&(e.apply(s,n),n=null,s=null)};return i.cancel=()=>{clearTimeout(o),o=null,n=null,s=null},i.flush=()=>{if(o){clearTimeout(o);let a=n,c=s;o=null,n=null,s=null,e.apply(c,a)}},i},Dt=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):Zr.error("Could not parse JSON",o))}},Mt=(e,t)=>{let n=".netlify.app",s="agent-";if(!t)return`${s}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=s.length+6){let u=Math.min(c-s.length,e.length);return`${s}${e.slice(0,u)}`}return e.slice(0,c)};var en=50*1024,Qe=(e,t=en)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as Ut}from"buffer";import tn from"path";var Gt=_("repo"),Yt=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{Gt.info("Getting runner diffs");let o=await nn(r),{hasChanges:n}=o,{status:s}=o;if(!n)return{hasChanges:!1};if(!t){let w=sn(s);await an(w,r)}Gt.info("Changes after processing"),await tt(r);let i=await rt(s,r);if(await et(i,r),n=await on(r),!n)return{hasChanges:!1,ignored:i};process.env.NETLIFY_INTERNAL_GIT="1";try{await x("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await x("git",["diff",e.runSha,"HEAD"],a),u=String(c.stdout??"");if(n=!!u,!n)return await jt(r),{hasChanges:!1,ignored:i};let d=await x("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),m,g;if(e.sha){let w=await x("git",["diff",e.sha,"HEAD"],a);m=String(w.stdout??"");let I=await x("git",["diff",e.sha,"HEAD","--binary"],a),E=String(I.stdout??"");m!==E&&(g=Ut.from(E).toString("base64"))}await jt(r);let h={hasChanges:!0,diff:u,resultDiff:m,ignored:i};return u!==p&&(h.diffBinary=Ut.from(p).toString("base64")),g&&(h.resultDiffBinary=g),h},jt=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await x("git",["reset","--soft","HEAD~1"],{cwd:e})},et=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await x("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},tt=async(e=process.cwd())=>{let t=await x("git",["status","-s"],{cwd:e});return String(t.stdout??"")},Bt=/.. (.+)?\.log$/,rn=[Bt],nn=async(e=process.cwd())=>{let t=await tt(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
9
9
|
`).filter(n=>rn.some(i=>i instanceof RegExp?i.test(n):n===i)?!1:n[1]?.trim()!=="")).length!==0,status:t}},on=async(e=process.cwd())=>{try{return await x("git",["diff","--staged","--quiet"],{cwd:e}),!1}catch{return!0}},Se=async(e=process.cwd())=>{let{stdout:t}=await x("git",["rev-parse","HEAD"],{cwd:e});return String(t??"").trim()},qt=async(e=process.cwd())=>{let{stdout:t}=await x("git",["rev-list","--max-parents=0","HEAD"],{cwd:e});return String(t??"").trim()},rt=async(e,t=process.cwd())=>{e||=await tt(t);let r=[".netlify","node_modules","dist",".next","out",".nuxt",".output",".cache",".turbo",".parcel-cache","coverage",".nyc_output","storybook-static","public/build"],o=[];return e.split(`
|
|
10
10
|
`).forEach(n=>{r.forEach(i=>{let a=n===`?? ${i}`,c=n.startsWith(`?? ${i}/`)||n.startsWith(`?? ${i}${tn.sep}`);(a||c)&&o.push(`:!${i}`)});let s=n.match(Bt)?.[1];s&&o.push(`:!${s}.log`)}),o},nt=async(e=process.cwd())=>{await x("git",["reset","--hard","HEAD"],{cwd:e})},sn=e=>{let t=e.split(`
|
|
11
11
|
`).reduce((r,o)=>{if(!o)return r;let[n,s,,...i]=o,a=i.join(""),c=n.trim(),u=s.trim();return r[a]?r[a].change=u:r[a]={filePath:a,stage:c,change:u},r},{});return Object.values(t)},an=async(e,t=process.cwd())=>{let r=e.filter(o=>o.stage&&!o.change).map(o=>o.filePath);r.length!==0&&await x("git",["restore","--staged","--worktree","--pathspec-from-file=-"],{cwd:t,input:r.join(`
|
package/dist/bin.js
CHANGED
|
@@ -5,7 +5,7 @@ import Et from"process";import wo from"minimist";import{createRequire as co}from
|
|
|
5
5
|
${s}
|
|
6
6
|
</extracted_error_chunk>`).join(`
|
|
7
7
|
|
|
8
|
-
`);return i.length>e.length*.8?e:i}import{execSync as Pn}from"child_process";import fr from"fs/promises";import On from"path";import Q from"process";import{getTracer as kn}from"@netlify/otel";import we from"process";var oe=class extends Error{constructor(r,o,n){super(r);this.statusCode=o;this.userMessage=n;this.name="GracefulShutdownError"}},Rt=e=>e instanceof oe;var Ae=we.env.NETLIFY_API_URL,Ce=we.env.NETLIFY_API_TOKEN,H=_("api"),Ne=()=>we.env.NETLIFY_LOCAL_MODE==="true",Te=async(e,t={})=>{if(!Ae||!Ce)throw new Error("No API URL or token");let r=new URL(e,Ae),o={...t,headers:{...t.headers,Authorization:`Bearer ${Ce}`}};we.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),i=n.ok&&n.status<=299;if(we.env.AGENT_RUNNERS_DEBUG==="true")H.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{H.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");H.log(`Request ID for ${r}: ${a||"N/A"}`)}if(i||H.error(`Got status ${n.status} for request ${r}`),t.raw){if(!i)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!i){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new oe(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===403&&t.gracefulOn403?new oe(`API request failed: 403 - ${a}`,403,"Credit limit reached. Please add more credits to continue using Agent Runners."):new Error(`API request failed: ${n.status} - ${a}`)}return s},St=e=>{H.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Ae=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Ce=e.constants.NETLIFY_API_TOKEN)},vt=()=>({apiUrl:Ae,token:Ce}),xe=async(e,t)=>Ne()?(H.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):Te(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),$=async(e,t,r)=>Ne()?(H.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var At=async(e,t)=>Ne()?(H.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`),Ct=(e,t,r)=>Te(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn403:!0}),Nt=async(e,t)=>Ne()?(H.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):Te(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),qe=async(e,t)=>{H.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var se=_("ai_gateway"),We=null;var bt=async()=>{if(We)return We;se.log("Fetching available AI gateway providers");let e=await fetch(`${vt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return We=t,se.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},Gr=async(e,t)=>{let o=(await bt()).providers[e];if(!o)return se.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return se.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Pt=async({netlify:e,config:t})=>{let r,o,n,i,s=e.constants?.SITE_ID;if(!s)throw new Error("No site id");let a=async()=>{clearTimeout(n),se.log("Requesting AI gateway information");let c=await Ct(s,t.id,t.sessionId);if({token:r,url:i}=c,o=c.expires_at?c.expires_at*1e3:void 0,se.log("Got AI gateway information",{token:!!r,expiresAt:o,url:i}),o){let u=o-Date.now()-6e4;u>0&&(n=setTimeout(()=>{a()},u))}};return await Promise.all([a(),bt()]),{get url(){return i},get token(){return r},isModelAvailableForProvider:Gr}};import W from"process";import X from"path";import be from"fs";import{fileURLToPath as Kr}from"url";import{createRequire as Vr}from"module";import{execa as Jr,execaCommand as Zo}from"execa";import{Transform as jr}from"stream";var Yr=new Set(["NODE_ENV","PATH","HOME","USER","USERNAME","SHELL","PWD","OLDPWD","TMPDIR","TMP","TEMP","LANG","TERM","EDITOR","PAGER","OS","PROCESSOR_ARCHITECTURE","PROCESSOR_IDENTIFIER","SYSTEMROOT","WINDIR","PROGRAMFILES","PROGRAMFILES(X86)","PROGRAMDATA","APPDATA","LOCALAPPDATA","NODE_OPTIONS","NODE_PATH","NODE_DEBUG","NODE_NO_WARNINGS","npm_config_registry","npm_config_cache","npm_execpath","npm_node_execpath","CI","GITHUB_ACTIONS","GITHUB_WORKSPACE","GITHUB_REPOSITORY","GITHUB_REF","BUILDKITE","BUILDKITE_BRANCH","BUILDKITE_COMMIT","BUILDKITE_BUILD_NUMBER","JENKINS_URL","TRAVIS","CIRCLECI","DISPLAY","COLORTERM","TERM_PROGRAM","TERM_PROGRAM_VERSION","COLUMNS","LINES","HISTSIZE","HISTFILE","NETLIFY_AGENT_RUNNER_ID","NETLIFY_AGENT_RUNNER_SESSION_ID","NETLIFY_AGENT_RUNNER_PROMPT","NETLIFY_AGENT_RUNNER_AGENT","NETLIFY_AGENT_RUNNER_MODEL","NETLIFY_AGENT_RUNNER_CONTEXT","NETLIFY_AGENT_RUNNER_HAS_REPO","NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED","NETLIFY_AGENT_RUNNER_SHA","NETLIFY_TEAM_TYPE","AGENT_RUNNERS_DEBUG","NETLIFY_TEAM_ID","NETLIFY_AGENT_RUNNER_USER_ID","SITE_NAME"]),Br=new Set(["true","false","undefined","null","deploy","project","claude","gemini","codex",""]);function Hr(){return Object.entries(process.env).filter(([e,t])=>!(!t||Yr.has(e)||Br.has(t)||!isNaN(Number(t))||t.length<5)).map(([,e])=>e).filter(Boolean)}function J(e){if(typeof e!="string")return e;let t=Hr();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(qr(o),"g");r=r.replace(n,"******")}),r}function qr(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var ie=class extends jr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),i=J(n);o(null,i)}};function Ot(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?t(s,n):t(s,n,i)},process.stderr.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?r(s,n):r(s,n,i)}}var Ie=null,kt=e=>(Ie&&Ie.destroy(),Ie=new z({totalAllowedTime:e}),Ie),Lt=()=>Ie;var z=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),i=null,s=null;o!==void 0&&(s=new Promise((a,c)=>{i=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),i&&clearTimeout(i)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Ft="netlify-agent-runner-context.md",Ke="task-history",q=".netlify",te="results.md",Ve="assets",Je="other",Xe="personal";var ze="enterprise",Ze="free",$t=[Xe,"pro",ze,Ze],Dt=["normal","redeploy","create","ask","dtn-prod-iteration","rebase"],Mt="The production deploy has changed since you started working. Please reapply the changes to the current codebase, resolving any conflicts that arise. Use the attached diff file if present, otherwise review the previous session context to reproduce the changes.",re=1800*1e3;var Ut={name:"@netlify/agent-runner-cli",type:"module",version:"1.68.1",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.37","@anthropic-ai/sdk":"0.72.1","@google/gemini-cli":"0.27.3","@netlify/otel":"^5.1.1","@openai/codex":"0.93.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.208.0",execa:"^9.6.1",kaddidlehopper:"^0.1.2",minimist:"^1.2.8",openai:"6.17.0"}};var Xr=Kr(import.meta.url),zr=X.dirname(Xr),Zr=Vr(import.meta.url),ae=_("shell"),Qe=new Set,Qr={preferLocal:!0},A=(e,t,r)=>{let[o,n]=en(t,r),i={...Qr,...n},s=Jr(e,o,i);tn(s,i),nn(s);let a=r?.idleTimeout;return a&&a>0&&rn(s,a),s};var en=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},tn=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(W.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new ie).pipe(W.stdout),e.stdout?.pipe(new ie).pipe(W.stdout),e.stderr?.pipe(new ie).pipe(W.stderr);return}e.stdout?.pipe(W.stdout),e.stderr?.pipe(W.stderr)},et=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(W.kill(-e.pid,t),ae.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ae.error("Error killing process:",r),!1}},Gt=e=>et(e,"SIGKILL"),rn=(e,t)=>{let r=null,o=()=>{ae.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),et(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ae.log(`Force killing idle process ${e.pid}`),Gt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let i=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",i),e.on("error",i)},nn=e=>{Qe.add(e);let t=Lt();if(t){let r=t.onTimesUp(()=>{ae.log(`Global timer expired, killing process ${e.pid}`),et(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ae.log(`Force killing process ${e.pid} after timeout`),Gt(e))},5e3)});e.on("exit",()=>{Qe.delete(e),r()}),e.on("error",()=>{Qe.delete(e),r()})}};function Z(e,t){if(!W.env.NETLIFY_LOCAL_MODE)try{let n=Zr.resolve(Ut.name),i=X.dirname(n);for(;i!==X.dirname(i);){let s=X.dirname(i);if(X.basename(s)==="node_modules"){let a=X.join(s,".bin",t);if(be.existsSync(a))return a;break}i=s}}catch(n){console.error("Could not resolve package.json",n)}if(W.env.NODE_PATH){let n=X.join(W.env.NODE_PATH,".bin",t);if(be.existsSync(n))return n}let r=X.join(e,"node_modules",".bin",t);if(be.existsSync(r))return r;let o=X.join(zr,"..","node_modules",".bin",t);if(be.existsSync(o))return o}var jt=_("utils"),on=e=>new Promise(t=>{setTimeout(t,e)}),Yt=(e,t=3e3)=>{let r=!1,o=null,n=[],i=null,s=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,u=new Promise(d=>{c=d});return i=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await on(t),!o)return r=!1,i=null,d;let p=o,m=n;o=null,n=[],d=await e(...p),m.forEach(g=>{g(d)})}})(),u};return s.flush=async()=>{if((r||o)&&i)return await i,s.flush()},s},le=(e,t,r=!1)=>{let o=null,n=null,i=null,s=function(...a){n=a,i=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(i,n),n=null,i=null)},t),c&&(e.apply(i,n),n=null,i=null)};return s.cancel=()=>{clearTimeout(o),o=null,n=null,i=null},s.flush=()=>{if(o){clearTimeout(o);let a=n,c=i;o=null,n=null,i=null,e.apply(c,a)}},s},Pe=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):jt.error("Could not parse JSON",o))}},Bt=(e,t)=>{let n=".netlify.app",i="agent-";if(!t)return`${i}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=i.length+6){let u=Math.min(c-i.length,e.length);return`${i}${e.slice(0,u)}`}return e.slice(0,c)},sn=e=>!e||typeof e!="object"||Array.isArray(e)||Object.keys(e).length===0?!1:!!$t.some(t=>t in e),Ht=()=>{let e={},t={codex:process.env.NETLIFY_FF_AGENT_RUNNER_CODEX_VERSION,claude:process.env.NETLIFY_FF_AGENT_RUNNER_CLAUDE_VERSION,gemini:process.env.NETLIFY_FF_AGENT_RUNNER_GEMINI_VERSION};return Object.entries(t).forEach(([r,o])=>{if(o){let n=`NETLIFY_FF_AGENT_RUNNER_${r.toUpperCase()}_VERSION`;try{let i=JSON.parse(o);sn(i)&&(e[r]=i)}catch(i){let a=i instanceof SyntaxError?"Invalid JSON":i.message;jt.error(`Could not parse ${r} model version override from ${n}: ${a}`)}}}),e},an=50*1024,tt=(e,t=an)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as qt}from"buffer";import ln from"path";var Wt=_("repo"),Vt=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{Wt.info("Getting runner diffs");let o=await un(r),{hasChanges:n}=o,{status:i}=o;if(!n)return{hasChanges:!1};if(!t){let w=dn(i);await mn(w,r)}Wt.info("Changes after processing"),await nt(r);let s=await st(i,r);if(await rt(s,r),n=await pn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await A("git",["diff",e.runSha,"HEAD"],a),u=String(c.stdout??"");if(n=!!u,!n)return await Kt(r),{hasChanges:!1,ignored:s};let d=await A("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),m,g;if(e.sha){let w=await A("git",["diff",e.sha,"HEAD"],a);m=String(w.stdout??"");let T=await A("git",["diff",e.sha,"HEAD","--binary"],a),E=String(T.stdout??"");m!==E&&(g=qt.from(E).toString("base64"))}await Kt(r);let h={hasChanges:!0,diff:u,resultDiff:m,ignored:s};return u!==p&&(h.diffBinary=qt.from(p).toString("base64")),g&&(h.resultDiffBinary=g),h},Kt=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await A("git",["reset","--soft","HEAD~1"],{cwd:e})},rt=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},nt=async(e=process.cwd())=>{let t=await A("git",["status","-s"],{cwd:e});return String(t.stdout??"")},Jt=/.. (.+)?\.log$/,cn=[Jt],un=async(e=process.cwd())=>{let t=await nt(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
8
|
+
`);return i.length>e.length*.8?e:i}import{execSync as Pn}from"child_process";import fr from"fs/promises";import On from"path";import Q from"process";import{getTracer as kn}from"@netlify/otel";import we from"process";var oe=class extends Error{constructor(r,o,n){super(r);this.statusCode=o;this.userMessage=n;this.name="GracefulShutdownError"}},Rt=e=>e instanceof oe;var Ae=we.env.NETLIFY_API_URL,Ce=we.env.NETLIFY_API_TOKEN,H=_("api"),Ne=()=>we.env.NETLIFY_LOCAL_MODE==="true",Te=async(e,t={})=>{if(!Ae||!Ce)throw new Error("No API URL or token");let r=new URL(e,Ae),o={...t,headers:{...t.headers,Authorization:`Bearer ${Ce}`}};we.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),i=n.ok&&n.status<=299;if(we.env.AGENT_RUNNERS_DEBUG==="true")H.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{H.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");H.log(`Request ID for ${r}: ${a||"N/A"}`)}if(i||H.error(`Got status ${n.status} for request ${r}`),t.raw){if(!i)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!i){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new oe(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===403&&t.gracefulOn403?new oe(`API request failed: 403 - ${a}`,403,"Credit limit reached. Please add more credits to continue using Agent Runners."):new Error(`API request failed: ${n.status} - ${a}`)}return s},St=e=>{H.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Ae=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Ce=e.constants.NETLIFY_API_TOKEN)},vt=()=>({apiUrl:Ae,token:Ce}),xe=async(e,t)=>Ne()?(H.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):Te(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),$=async(e,t,r)=>Ne()?(H.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var At=async(e,t)=>Ne()?(H.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):Te(`/api/v1/agent_runners/${e}/sessions/${t}`),Ct=(e,t,r)=>Te(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn403:!0}),Nt=async(e,t)=>Ne()?(H.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):Te(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),qe=async(e,t)=>{H.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var se=_("ai_gateway"),We=null;var bt=async()=>{if(We)return We;se.log("Fetching available AI gateway providers");let e=await fetch(`${vt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return We=t,se.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},Gr=async(e,t)=>{let o=(await bt()).providers[e];if(!o)return se.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return se.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Pt=async({netlify:e,config:t})=>{let r,o,n,i,s=e.constants?.SITE_ID;if(!s)throw new Error("No site id");let a=async()=>{clearTimeout(n),se.log("Requesting AI gateway information");let c=await Ct(s,t.id,t.sessionId);if({token:r,url:i}=c,o=c.expires_at?c.expires_at*1e3:void 0,se.log("Got AI gateway information",{token:!!r,expiresAt:o,url:i}),o){let u=o-Date.now()-6e4;u>0&&(n=setTimeout(()=>{a()},u))}};return await Promise.all([a(),bt()]),{get url(){return i},get token(){return r},isModelAvailableForProvider:Gr}};import W from"process";import X from"path";import be from"fs";import{fileURLToPath as Kr}from"url";import{createRequire as Vr}from"module";import{execa as Jr,execaCommand as Zo}from"execa";import{Transform as jr}from"stream";var Yr=new Set(["NODE_ENV","PATH","HOME","USER","USERNAME","SHELL","PWD","OLDPWD","TMPDIR","TMP","TEMP","LANG","TERM","EDITOR","PAGER","OS","PROCESSOR_ARCHITECTURE","PROCESSOR_IDENTIFIER","SYSTEMROOT","WINDIR","PROGRAMFILES","PROGRAMFILES(X86)","PROGRAMDATA","APPDATA","LOCALAPPDATA","NODE_OPTIONS","NODE_PATH","NODE_DEBUG","NODE_NO_WARNINGS","npm_config_registry","npm_config_cache","npm_execpath","npm_node_execpath","CI","GITHUB_ACTIONS","GITHUB_WORKSPACE","GITHUB_REPOSITORY","GITHUB_REF","BUILDKITE","BUILDKITE_BRANCH","BUILDKITE_COMMIT","BUILDKITE_BUILD_NUMBER","JENKINS_URL","TRAVIS","CIRCLECI","DISPLAY","COLORTERM","TERM_PROGRAM","TERM_PROGRAM_VERSION","COLUMNS","LINES","HISTSIZE","HISTFILE","NETLIFY_AGENT_RUNNER_ID","NETLIFY_AGENT_RUNNER_SESSION_ID","NETLIFY_AGENT_RUNNER_PROMPT","NETLIFY_AGENT_RUNNER_AGENT","NETLIFY_AGENT_RUNNER_MODEL","NETLIFY_AGENT_RUNNER_CONTEXT","NETLIFY_AGENT_RUNNER_HAS_REPO","NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED","NETLIFY_AGENT_RUNNER_SHA","NETLIFY_TEAM_TYPE","AGENT_RUNNERS_DEBUG","NETLIFY_TEAM_ID","NETLIFY_AGENT_RUNNER_USER_ID","SITE_NAME"]),Br=new Set(["true","false","undefined","null","deploy","project","claude","gemini","codex",""]);function Hr(){return Object.entries(process.env).filter(([e,t])=>!(!t||Yr.has(e)||Br.has(t)||!isNaN(Number(t))||t.length<5)).map(([,e])=>e).filter(Boolean)}function J(e){if(typeof e!="string")return e;let t=Hr();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(qr(o),"g");r=r.replace(n,"******")}),r}function qr(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var ie=class extends jr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),i=J(n);o(null,i)}};function Ot(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?t(s,n):t(s,n,i)},process.stderr.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?r(s,n):r(s,n,i)}}var Ie=null,kt=e=>(Ie&&Ie.destroy(),Ie=new z({totalAllowedTime:e}),Ie),Lt=()=>Ie;var z=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),i=null,s=null;o!==void 0&&(s=new Promise((a,c)=>{i=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),i&&clearTimeout(i)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Ft="netlify-agent-runner-context.md",Ke="task-history",q=".netlify",te="results.md",Ve="assets",Je="other",Xe="personal";var ze="enterprise",Ze="free",$t=[Xe,"pro",ze,Ze],Dt=["normal","redeploy","create","ask","dtn-prod-iteration","rebase"],Mt="The production deploy has changed since you started working. Please reapply the changes to the current codebase, resolving any conflicts that arise. Use the attached diff file if present, otherwise review the previous session context to reproduce the changes.",re=1800*1e3;var Ut={name:"@netlify/agent-runner-cli",type:"module",version:"1.69.0",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.37","@anthropic-ai/sdk":"0.72.1","@google/gemini-cli":"0.27.3","@netlify/otel":"^5.1.1","@openai/codex":"0.93.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.208.0",execa:"^9.6.1",kaddidlehopper:"^0.1.2",minimist:"^1.2.8",openai:"6.17.0"}};var Xr=Kr(import.meta.url),zr=X.dirname(Xr),Zr=Vr(import.meta.url),ae=_("shell"),Qe=new Set,Qr={preferLocal:!0},A=(e,t,r)=>{let[o,n]=en(t,r),i={...Qr,...n},s=Jr(e,o,i);tn(s,i),nn(s);let a=r?.idleTimeout;return a&&a>0&&rn(s,a),s};var en=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},tn=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(W.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new ie).pipe(W.stdout),e.stdout?.pipe(new ie).pipe(W.stdout),e.stderr?.pipe(new ie).pipe(W.stderr);return}e.stdout?.pipe(W.stdout),e.stderr?.pipe(W.stderr)},et=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(W.kill(-e.pid,t),ae.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ae.error("Error killing process:",r),!1}},Gt=e=>et(e,"SIGKILL"),rn=(e,t)=>{let r=null,o=()=>{ae.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),et(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ae.log(`Force killing idle process ${e.pid}`),Gt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let i=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",i),e.on("error",i)},nn=e=>{Qe.add(e);let t=Lt();if(t){let r=t.onTimesUp(()=>{ae.log(`Global timer expired, killing process ${e.pid}`),et(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ae.log(`Force killing process ${e.pid} after timeout`),Gt(e))},5e3)});e.on("exit",()=>{Qe.delete(e),r()}),e.on("error",()=>{Qe.delete(e),r()})}};function Z(e,t){if(!W.env.NETLIFY_LOCAL_MODE)try{let n=Zr.resolve(Ut.name),i=X.dirname(n);for(;i!==X.dirname(i);){let s=X.dirname(i);if(X.basename(s)==="node_modules"){let a=X.join(s,".bin",t);if(be.existsSync(a))return a;break}i=s}}catch(n){console.error("Could not resolve package.json",n)}if(W.env.NODE_PATH){let n=X.join(W.env.NODE_PATH,".bin",t);if(be.existsSync(n))return n}let r=X.join(e,"node_modules",".bin",t);if(be.existsSync(r))return r;let o=X.join(zr,"..","node_modules",".bin",t);if(be.existsSync(o))return o}var jt=_("utils"),on=e=>new Promise(t=>{setTimeout(t,e)}),Yt=(e,t=3e3)=>{let r=!1,o=null,n=[],i=null,s=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,u=new Promise(d=>{c=d});return i=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await on(t),!o)return r=!1,i=null,d;let p=o,m=n;o=null,n=[],d=await e(...p),m.forEach(g=>{g(d)})}})(),u};return s.flush=async()=>{if((r||o)&&i)return await i,s.flush()},s},le=(e,t,r=!1)=>{let o=null,n=null,i=null,s=function(...a){n=a,i=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(i,n),n=null,i=null)},t),c&&(e.apply(i,n),n=null,i=null)};return s.cancel=()=>{clearTimeout(o),o=null,n=null,i=null},s.flush=()=>{if(o){clearTimeout(o);let a=n,c=i;o=null,n=null,i=null,e.apply(c,a)}},s},Pe=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):jt.error("Could not parse JSON",o))}},Bt=(e,t)=>{let n=".netlify.app",i="agent-";if(!t)return`${i}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=i.length+6){let u=Math.min(c-i.length,e.length);return`${i}${e.slice(0,u)}`}return e.slice(0,c)},sn=e=>!e||typeof e!="object"||Array.isArray(e)||Object.keys(e).length===0?!1:!!$t.some(t=>t in e),Ht=()=>{let e={},t={codex:process.env.NETLIFY_FF_AGENT_RUNNER_CODEX_VERSION,claude:process.env.NETLIFY_FF_AGENT_RUNNER_CLAUDE_VERSION,gemini:process.env.NETLIFY_FF_AGENT_RUNNER_GEMINI_VERSION};return Object.entries(t).forEach(([r,o])=>{if(o){let n=`NETLIFY_FF_AGENT_RUNNER_${r.toUpperCase()}_VERSION`;try{let i=JSON.parse(o);sn(i)&&(e[r]=i)}catch(i){let a=i instanceof SyntaxError?"Invalid JSON":i.message;jt.error(`Could not parse ${r} model version override from ${n}: ${a}`)}}}),e},an=50*1024,tt=(e,t=an)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as qt}from"buffer";import ln from"path";var Wt=_("repo"),Vt=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{Wt.info("Getting runner diffs");let o=await un(r),{hasChanges:n}=o,{status:i}=o;if(!n)return{hasChanges:!1};if(!t){let w=dn(i);await mn(w,r)}Wt.info("Changes after processing"),await nt(r);let s=await st(i,r);if(await rt(s,r),n=await pn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await A("git",["diff",e.runSha,"HEAD"],a),u=String(c.stdout??"");if(n=!!u,!n)return await Kt(r),{hasChanges:!1,ignored:s};let d=await A("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),m,g;if(e.sha){let w=await A("git",["diff",e.sha,"HEAD"],a);m=String(w.stdout??"");let T=await A("git",["diff",e.sha,"HEAD","--binary"],a),E=String(T.stdout??"");m!==E&&(g=qt.from(E).toString("base64"))}await Kt(r);let h={hasChanges:!0,diff:u,resultDiff:m,ignored:s};return u!==p&&(h.diffBinary=qt.from(p).toString("base64")),g&&(h.resultDiffBinary=g),h},Kt=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await A("git",["reset","--soft","HEAD~1"],{cwd:e})},rt=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},nt=async(e=process.cwd())=>{let t=await A("git",["status","-s"],{cwd:e});return String(t.stdout??"")},Jt=/.. (.+)?\.log$/,cn=[Jt],un=async(e=process.cwd())=>{let t=await nt(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
9
9
|
`).filter(n=>cn.some(s=>s instanceof RegExp?s.test(n):n===s)?!1:n[1]?.trim()!=="")).length!==0,status:t}},pn=async(e=process.cwd())=>{try{return await A("git",["diff","--staged","--quiet"],{cwd:e}),!1}catch{return!0}},ot=async(e=process.cwd())=>{let{stdout:t}=await A("git",["rev-parse","HEAD"],{cwd:e});return String(t??"").trim()},Xt=async(e=process.cwd())=>{let{stdout:t}=await A("git",["rev-list","--max-parents=0","HEAD"],{cwd:e});return String(t??"").trim()},st=async(e,t=process.cwd())=>{e||=await nt(t);let r=[".netlify","node_modules","dist",".next","out",".nuxt",".output",".cache",".turbo",".parcel-cache","coverage",".nyc_output","storybook-static","public/build"],o=[];return e.split(`
|
|
10
10
|
`).forEach(n=>{r.forEach(s=>{let a=n===`?? ${s}`,c=n.startsWith(`?? ${s}/`)||n.startsWith(`?? ${s}${ln.sep}`);(a||c)&&o.push(`:!${s}`)});let i=n.match(Jt)?.[1];i&&o.push(`:!${i}.log`)}),o},it=async(e=process.cwd())=>{await A("git",["reset","--hard","HEAD"],{cwd:e})},dn=e=>{let t=e.split(`
|
|
11
11
|
`).reduce((r,o)=>{if(!o)return r;let[n,i,,...s]=o,a=s.join(""),c=n.trim(),u=i.trim();return r[a]?r[a].change=u:r[a]={filePath:a,stage:c,change:u},r},{});return Object.values(t)},mn=async(e,t=process.cwd())=>{let r=e.filter(o=>o.stage&&!o.change).map(o=>o.filePath);r.length!==0&&await A("git",["restore","--staged","--worktree","--pathspec-from-file=-"],{cwd:t,input:r.join(`
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import{createRequire as Kn}from"module";import{createTracerProvider as _r}from"@
|
|
|
4
4
|
${s}
|
|
5
5
|
</extracted_error_chunk>`).join(`
|
|
6
6
|
|
|
7
|
-
`);return i.length>e.length*.8?e:i}import{execSync as gn}from"child_process";import rr from"fs/promises";import hn from"path";import Z from"process";import{getTracer as yn}from"@netlify/otel";import _e from"process";var ne=class extends Error{constructor(r,o,n){super(r);this.statusCode=o;this.userMessage=n;this.name="GracefulShutdownError"}},ht=e=>e instanceof ne;var Se=_e.env.NETLIFY_API_URL,ve=_e.env.NETLIFY_API_TOKEN,B=E("api"),Ae=()=>_e.env.NETLIFY_LOCAL_MODE==="true",Ee=async(e,t={})=>{if(!Se||!ve)throw new Error("No API URL or token");let r=new URL(e,Se),o={...t,headers:{...t.headers,Authorization:`Bearer ${ve}`}};_e.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),i=n.ok&&n.status<=299;if(_e.env.AGENT_RUNNERS_DEBUG==="true")B.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{B.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");B.log(`Request ID for ${r}: ${a||"N/A"}`)}if(i||B.error(`Got status ${n.status} for request ${r}`),t.raw){if(!i)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!i){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new ne(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===403&&t.gracefulOn403?new ne(`API request failed: 403 - ${a}`,403,"Credit limit reached. Please add more credits to continue using Agent Runners."):new Error(`API request failed: ${n.status} - ${a}`)}return s},yt=e=>{B.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Se=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(ve=e.constants.NETLIFY_API_TOKEN)},_t=()=>({apiUrl:Se,token:ve}),we=async(e,t)=>Ae()?(B.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):Ee(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),L=async(e,t,r)=>Ae()?(B.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):Ee(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Et=async(e,t)=>Ae()?(B.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):Ee(`/api/v1/agent_runners/${e}/sessions/${t}`),wt=(e,t,r)=>Ee(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn403:!0}),Tt=async(e,t)=>Ae()?(B.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):Ee(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Ye=async(e,t)=>{B.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var oe=E("ai_gateway"),Be=null;var It=async()=>{if(Be)return Be;oe.log("Fetching available AI gateway providers");let e=await fetch(`${_t().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return Be=t,oe.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},Rr=async(e,t)=>{let o=(await It()).providers[e];if(!o)return oe.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return oe.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},xt=async({netlify:e,config:t})=>{let r,o,n,i,s=e.constants?.SITE_ID;if(!s)throw new Error("No site id");let a=async()=>{clearTimeout(n),oe.log("Requesting AI gateway information");let c=await wt(s,t.id,t.sessionId);if({token:r,url:i}=c,o=c.expires_at?c.expires_at*1e3:void 0,oe.log("Got AI gateway information",{token:!!r,expiresAt:o,url:i}),o){let u=o-Date.now()-6e4;u>0&&(n=setTimeout(()=>{a()},u))}};return await Promise.all([a(),It()]),{get url(){return i},get token(){return r},isModelAvailableForProvider:Rr}};import H from"process";import V from"path";import be from"fs";import{fileURLToPath as Pr}from"url";import{createRequire as kr}from"module";import{execa as Or,execaCommand as bo}from"execa";import{Transform as Sr}from"stream";var vr=new Set(["NODE_ENV","PATH","HOME","USER","USERNAME","SHELL","PWD","OLDPWD","TMPDIR","TMP","TEMP","LANG","TERM","EDITOR","PAGER","OS","PROCESSOR_ARCHITECTURE","PROCESSOR_IDENTIFIER","SYSTEMROOT","WINDIR","PROGRAMFILES","PROGRAMFILES(X86)","PROGRAMDATA","APPDATA","LOCALAPPDATA","NODE_OPTIONS","NODE_PATH","NODE_DEBUG","NODE_NO_WARNINGS","npm_config_registry","npm_config_cache","npm_execpath","npm_node_execpath","CI","GITHUB_ACTIONS","GITHUB_WORKSPACE","GITHUB_REPOSITORY","GITHUB_REF","BUILDKITE","BUILDKITE_BRANCH","BUILDKITE_COMMIT","BUILDKITE_BUILD_NUMBER","JENKINS_URL","TRAVIS","CIRCLECI","DISPLAY","COLORTERM","TERM_PROGRAM","TERM_PROGRAM_VERSION","COLUMNS","LINES","HISTSIZE","HISTFILE","NETLIFY_AGENT_RUNNER_ID","NETLIFY_AGENT_RUNNER_SESSION_ID","NETLIFY_AGENT_RUNNER_PROMPT","NETLIFY_AGENT_RUNNER_AGENT","NETLIFY_AGENT_RUNNER_MODEL","NETLIFY_AGENT_RUNNER_CONTEXT","NETLIFY_AGENT_RUNNER_HAS_REPO","NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED","NETLIFY_AGENT_RUNNER_SHA","NETLIFY_TEAM_TYPE","AGENT_RUNNERS_DEBUG","NETLIFY_TEAM_ID","NETLIFY_AGENT_RUNNER_USER_ID","SITE_NAME"]),Ar=new Set(["true","false","undefined","null","deploy","project","claude","gemini","codex",""]);function br(){return Object.entries(process.env).filter(([e,t])=>!(!t||vr.has(e)||Ar.has(t)||!isNaN(Number(t))||t.length<5)).map(([,e])=>e).filter(Boolean)}function J(e){if(typeof e!="string")return e;let t=br();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(Cr(o),"g");r=r.replace(n,"******")}),r}function Cr(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var se=class extends Sr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),i=J(n);o(null,i)}};function Rt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?t(s,n):t(s,n,i)},process.stderr.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?r(s,n):r(s,n,i)}}var Te=null,St=e=>(Te&&Te.destroy(),Te=new X({totalAllowedTime:e}),Te),vt=()=>Te;var X=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),i=null,s=null;o!==void 0&&(s=new Promise((a,c)=>{i=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),i&&clearTimeout(i)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var At="netlify-agent-runner-context.md",qe="task-history",q=".netlify",ee="results.md",He="assets";var te=1800*1e3;var bt={name:"@netlify/agent-runner-cli",type:"module",version:"1.68.1",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.37","@anthropic-ai/sdk":"0.72.1","@google/gemini-cli":"0.27.3","@netlify/otel":"^5.1.1","@openai/codex":"0.93.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.208.0",execa:"^9.6.1",kaddidlehopper:"^0.1.2",minimist:"^1.2.8",openai:"6.17.0"}};var $r=Pr(import.meta.url),Fr=V.dirname($r),Lr=kr(import.meta.url),ie=E("shell"),We=new Set,Dr={preferLocal:!0},A=(e,t,r)=>{let[o,n]=Mr(t,r),i={...Dr,...n},s=Or(e,o,i);Ur(s,i),jr(s);let a=r?.idleTimeout;return a&&a>0&&Gr(s,a),s};var Mr=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},Ur=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(H.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new se).pipe(H.stdout),e.stdout?.pipe(new se).pipe(H.stdout),e.stderr?.pipe(new se).pipe(H.stderr);return}e.stdout?.pipe(H.stdout),e.stderr?.pipe(H.stderr)},Ke=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(H.kill(-e.pid,t),ie.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ie.error("Error killing process:",r),!1}},Ct=e=>Ke(e,"SIGKILL"),Gr=(e,t)=>{let r=null,o=()=>{ie.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),Ke(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ie.log(`Force killing idle process ${e.pid}`),Ct(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let i=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",i),e.on("error",i)},jr=e=>{We.add(e);let t=vt();if(t){let r=t.onTimesUp(()=>{ie.log(`Global timer expired, killing process ${e.pid}`),Ke(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ie.log(`Force killing process ${e.pid} after timeout`),Ct(e))},5e3)});e.on("exit",()=>{We.delete(e),r()}),e.on("error",()=>{We.delete(e),r()})}};function z(e,t){if(!H.env.NETLIFY_LOCAL_MODE)try{let n=Lr.resolve(bt.name),i=V.dirname(n);for(;i!==V.dirname(i);){let s=V.dirname(i);if(V.basename(s)==="node_modules"){let a=V.join(s,".bin",t);if(be.existsSync(a))return a;break}i=s}}catch(n){console.error("Could not resolve package.json",n)}if(H.env.NODE_PATH){let n=V.join(H.env.NODE_PATH,".bin",t);if(be.existsSync(n))return n}let r=V.join(e,"node_modules",".bin",t);if(be.existsSync(r))return r;let o=V.join(Fr,"..","node_modules",".bin",t);if(be.existsSync(o))return o}var Yr=E("utils"),Br=e=>new Promise(t=>{setTimeout(t,e)}),Nt=(e,t=3e3)=>{let r=!1,o=null,n=[],i=null,s=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,u=new Promise(d=>{c=d});return i=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Br(t),!o)return r=!1,i=null,d;let p=o,m=n;o=null,n=[],d=await e(...p),m.forEach(g=>{g(d)})}})(),u};return s.flush=async()=>{if((r||o)&&i)return await i,s.flush()},s},ae=(e,t,r=!1)=>{let o=null,n=null,i=null,s=function(...a){n=a,i=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(i,n),n=null,i=null)},t),c&&(e.apply(i,n),n=null,i=null)};return s.cancel=()=>{clearTimeout(o),o=null,n=null,i=null},s.flush=()=>{if(o){clearTimeout(o);let a=n,c=i;o=null,n=null,i=null,e.apply(c,a)}},s},Pt=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):Yr.error("Could not parse JSON",o))}},kt=(e,t)=>{let n=".netlify.app",i="agent-";if(!t)return`${i}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=i.length+6){let u=Math.min(c-i.length,e.length);return`${i}${e.slice(0,u)}`}return e.slice(0,c)};var qr=50*1024,Je=(e,t=qr)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as Ot}from"buffer";import Hr from"path";var $t=E("repo"),Lt=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{$t.info("Getting runner diffs");let o=await Kr(r),{hasChanges:n}=o,{status:i}=o;if(!n)return{hasChanges:!1};if(!t){let w=Vr(i);await Xr(w,r)}$t.info("Changes after processing"),await Xe(r);let s=await Ze(i,r);if(await Ve(s,r),n=await Jr(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await A("git",["diff",e.runSha,"HEAD"],a),u=String(c.stdout??"");if(n=!!u,!n)return await Ft(r),{hasChanges:!1,ignored:s};let d=await A("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),m,g;if(e.sha){let w=await A("git",["diff",e.sha,"HEAD"],a);m=String(w.stdout??"");let T=await A("git",["diff",e.sha,"HEAD","--binary"],a),_=String(T.stdout??"");m!==_&&(g=Ot.from(_).toString("base64"))}await Ft(r);let h={hasChanges:!0,diff:u,resultDiff:m,ignored:s};return u!==p&&(h.diffBinary=Ot.from(p).toString("base64")),g&&(h.resultDiffBinary=g),h},Ft=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await A("git",["reset","--soft","HEAD~1"],{cwd:e})},Ve=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},Xe=async(e=process.cwd())=>{let t=await A("git",["status","-s"],{cwd:e});return String(t.stdout??"")},Dt=/.. (.+)?\.log$/,Wr=[Dt],Kr=async(e=process.cwd())=>{let t=await Xe(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
7
|
+
`);return i.length>e.length*.8?e:i}import{execSync as gn}from"child_process";import rr from"fs/promises";import hn from"path";import Z from"process";import{getTracer as yn}from"@netlify/otel";import _e from"process";var ne=class extends Error{constructor(r,o,n){super(r);this.statusCode=o;this.userMessage=n;this.name="GracefulShutdownError"}},ht=e=>e instanceof ne;var Se=_e.env.NETLIFY_API_URL,ve=_e.env.NETLIFY_API_TOKEN,B=E("api"),Ae=()=>_e.env.NETLIFY_LOCAL_MODE==="true",Ee=async(e,t={})=>{if(!Se||!ve)throw new Error("No API URL or token");let r=new URL(e,Se),o={...t,headers:{...t.headers,Authorization:`Bearer ${ve}`}};_e.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),i=n.ok&&n.status<=299;if(_e.env.AGENT_RUNNERS_DEBUG==="true")B.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{B.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");B.log(`Request ID for ${r}: ${a||"N/A"}`)}if(i||B.error(`Got status ${n.status} for request ${r}`),t.raw){if(!i)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!i){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new ne(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===403&&t.gracefulOn403?new ne(`API request failed: 403 - ${a}`,403,"Credit limit reached. Please add more credits to continue using Agent Runners."):new Error(`API request failed: ${n.status} - ${a}`)}return s},yt=e=>{B.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Se=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(ve=e.constants.NETLIFY_API_TOKEN)},_t=()=>({apiUrl:Se,token:ve}),we=async(e,t)=>Ae()?(B.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):Ee(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),L=async(e,t,r)=>Ae()?(B.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):Ee(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Et=async(e,t)=>Ae()?(B.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):Ee(`/api/v1/agent_runners/${e}/sessions/${t}`),wt=(e,t,r)=>Ee(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn403:!0}),Tt=async(e,t)=>Ae()?(B.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):Ee(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Ye=async(e,t)=>{B.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var oe=E("ai_gateway"),Be=null;var It=async()=>{if(Be)return Be;oe.log("Fetching available AI gateway providers");let e=await fetch(`${_t().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return Be=t,oe.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},Rr=async(e,t)=>{let o=(await It()).providers[e];if(!o)return oe.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return oe.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},xt=async({netlify:e,config:t})=>{let r,o,n,i,s=e.constants?.SITE_ID;if(!s)throw new Error("No site id");let a=async()=>{clearTimeout(n),oe.log("Requesting AI gateway information");let c=await wt(s,t.id,t.sessionId);if({token:r,url:i}=c,o=c.expires_at?c.expires_at*1e3:void 0,oe.log("Got AI gateway information",{token:!!r,expiresAt:o,url:i}),o){let u=o-Date.now()-6e4;u>0&&(n=setTimeout(()=>{a()},u))}};return await Promise.all([a(),It()]),{get url(){return i},get token(){return r},isModelAvailableForProvider:Rr}};import H from"process";import V from"path";import be from"fs";import{fileURLToPath as Pr}from"url";import{createRequire as kr}from"module";import{execa as Or,execaCommand as bo}from"execa";import{Transform as Sr}from"stream";var vr=new Set(["NODE_ENV","PATH","HOME","USER","USERNAME","SHELL","PWD","OLDPWD","TMPDIR","TMP","TEMP","LANG","TERM","EDITOR","PAGER","OS","PROCESSOR_ARCHITECTURE","PROCESSOR_IDENTIFIER","SYSTEMROOT","WINDIR","PROGRAMFILES","PROGRAMFILES(X86)","PROGRAMDATA","APPDATA","LOCALAPPDATA","NODE_OPTIONS","NODE_PATH","NODE_DEBUG","NODE_NO_WARNINGS","npm_config_registry","npm_config_cache","npm_execpath","npm_node_execpath","CI","GITHUB_ACTIONS","GITHUB_WORKSPACE","GITHUB_REPOSITORY","GITHUB_REF","BUILDKITE","BUILDKITE_BRANCH","BUILDKITE_COMMIT","BUILDKITE_BUILD_NUMBER","JENKINS_URL","TRAVIS","CIRCLECI","DISPLAY","COLORTERM","TERM_PROGRAM","TERM_PROGRAM_VERSION","COLUMNS","LINES","HISTSIZE","HISTFILE","NETLIFY_AGENT_RUNNER_ID","NETLIFY_AGENT_RUNNER_SESSION_ID","NETLIFY_AGENT_RUNNER_PROMPT","NETLIFY_AGENT_RUNNER_AGENT","NETLIFY_AGENT_RUNNER_MODEL","NETLIFY_AGENT_RUNNER_CONTEXT","NETLIFY_AGENT_RUNNER_HAS_REPO","NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED","NETLIFY_AGENT_RUNNER_SHA","NETLIFY_TEAM_TYPE","AGENT_RUNNERS_DEBUG","NETLIFY_TEAM_ID","NETLIFY_AGENT_RUNNER_USER_ID","SITE_NAME"]),Ar=new Set(["true","false","undefined","null","deploy","project","claude","gemini","codex",""]);function br(){return Object.entries(process.env).filter(([e,t])=>!(!t||vr.has(e)||Ar.has(t)||!isNaN(Number(t))||t.length<5)).map(([,e])=>e).filter(Boolean)}function J(e){if(typeof e!="string")return e;let t=br();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(Cr(o),"g");r=r.replace(n,"******")}),r}function Cr(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var se=class extends Sr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),i=J(n);o(null,i)}};function Rt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?t(s,n):t(s,n,i)},process.stderr.write=function(o,n,i){let s=typeof o=="string"?J(o):o;return typeof n=="function"?r(s,n):r(s,n,i)}}var Te=null,St=e=>(Te&&Te.destroy(),Te=new X({totalAllowedTime:e}),Te),vt=()=>Te;var X=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),i=null,s=null;o!==void 0&&(s=new Promise((a,c)=>{i=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),i&&clearTimeout(i)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var At="netlify-agent-runner-context.md",qe="task-history",q=".netlify",ee="results.md",He="assets";var te=1800*1e3;var bt={name:"@netlify/agent-runner-cli",type:"module",version:"1.69.0",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.37","@anthropic-ai/sdk":"0.72.1","@google/gemini-cli":"0.27.3","@netlify/otel":"^5.1.1","@openai/codex":"0.93.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.208.0",execa:"^9.6.1",kaddidlehopper:"^0.1.2",minimist:"^1.2.8",openai:"6.17.0"}};var $r=Pr(import.meta.url),Fr=V.dirname($r),Lr=kr(import.meta.url),ie=E("shell"),We=new Set,Dr={preferLocal:!0},A=(e,t,r)=>{let[o,n]=Mr(t,r),i={...Dr,...n},s=Or(e,o,i);Ur(s,i),jr(s);let a=r?.idleTimeout;return a&&a>0&&Gr(s,a),s};var Mr=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},Ur=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(H.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new se).pipe(H.stdout),e.stdout?.pipe(new se).pipe(H.stdout),e.stderr?.pipe(new se).pipe(H.stderr);return}e.stdout?.pipe(H.stdout),e.stderr?.pipe(H.stderr)},Ke=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(H.kill(-e.pid,t),ie.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ie.error("Error killing process:",r),!1}},Ct=e=>Ke(e,"SIGKILL"),Gr=(e,t)=>{let r=null,o=()=>{ie.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),Ke(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ie.log(`Force killing idle process ${e.pid}`),Ct(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let i=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",i),e.on("error",i)},jr=e=>{We.add(e);let t=vt();if(t){let r=t.onTimesUp(()=>{ie.log(`Global timer expired, killing process ${e.pid}`),Ke(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ie.log(`Force killing process ${e.pid} after timeout`),Ct(e))},5e3)});e.on("exit",()=>{We.delete(e),r()}),e.on("error",()=>{We.delete(e),r()})}};function z(e,t){if(!H.env.NETLIFY_LOCAL_MODE)try{let n=Lr.resolve(bt.name),i=V.dirname(n);for(;i!==V.dirname(i);){let s=V.dirname(i);if(V.basename(s)==="node_modules"){let a=V.join(s,".bin",t);if(be.existsSync(a))return a;break}i=s}}catch(n){console.error("Could not resolve package.json",n)}if(H.env.NODE_PATH){let n=V.join(H.env.NODE_PATH,".bin",t);if(be.existsSync(n))return n}let r=V.join(e,"node_modules",".bin",t);if(be.existsSync(r))return r;let o=V.join(Fr,"..","node_modules",".bin",t);if(be.existsSync(o))return o}var Yr=E("utils"),Br=e=>new Promise(t=>{setTimeout(t,e)}),Nt=(e,t=3e3)=>{let r=!1,o=null,n=[],i=null,s=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,u=new Promise(d=>{c=d});return i=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Br(t),!o)return r=!1,i=null,d;let p=o,m=n;o=null,n=[],d=await e(...p),m.forEach(g=>{g(d)})}})(),u};return s.flush=async()=>{if((r||o)&&i)return await i,s.flush()},s},ae=(e,t,r=!1)=>{let o=null,n=null,i=null,s=function(...a){n=a,i=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(i,n),n=null,i=null)},t),c&&(e.apply(i,n),n=null,i=null)};return s.cancel=()=>{clearTimeout(o),o=null,n=null,i=null},s.flush=()=>{if(o){clearTimeout(o);let a=n,c=i;o=null,n=null,i=null,e.apply(c,a)}},s},Pt=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):Yr.error("Could not parse JSON",o))}},kt=(e,t)=>{let n=".netlify.app",i="agent-";if(!t)return`${i}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=i.length+6){let u=Math.min(c-i.length,e.length);return`${i}${e.slice(0,u)}`}return e.slice(0,c)};var qr=50*1024,Je=(e,t=qr)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as Ot}from"buffer";import Hr from"path";var $t=E("repo"),Lt=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{$t.info("Getting runner diffs");let o=await Kr(r),{hasChanges:n}=o,{status:i}=o;if(!n)return{hasChanges:!1};if(!t){let w=Vr(i);await Xr(w,r)}$t.info("Changes after processing"),await Xe(r);let s=await Ze(i,r);if(await Ve(s,r),n=await Jr(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await A("git",["diff",e.runSha,"HEAD"],a),u=String(c.stdout??"");if(n=!!u,!n)return await Ft(r),{hasChanges:!1,ignored:s};let d=await A("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),m,g;if(e.sha){let w=await A("git",["diff",e.sha,"HEAD"],a);m=String(w.stdout??"");let T=await A("git",["diff",e.sha,"HEAD","--binary"],a),_=String(T.stdout??"");m!==_&&(g=Ot.from(_).toString("base64"))}await Ft(r);let h={hasChanges:!0,diff:u,resultDiff:m,ignored:s};return u!==p&&(h.diffBinary=Ot.from(p).toString("base64")),g&&(h.resultDiffBinary=g),h},Ft=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await A("git",["reset","--soft","HEAD~1"],{cwd:e})},Ve=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},Xe=async(e=process.cwd())=>{let t=await A("git",["status","-s"],{cwd:e});return String(t.stdout??"")},Dt=/.. (.+)?\.log$/,Wr=[Dt],Kr=async(e=process.cwd())=>{let t=await Xe(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
8
8
|
`).filter(n=>Wr.some(s=>s instanceof RegExp?s.test(n):n===s)?!1:n[1]?.trim()!=="")).length!==0,status:t}},Jr=async(e=process.cwd())=>{try{return await A("git",["diff","--staged","--quiet"],{cwd:e}),!1}catch{return!0}},ze=async(e=process.cwd())=>{let{stdout:t}=await A("git",["rev-parse","HEAD"],{cwd:e});return String(t??"").trim()},Mt=async(e=process.cwd())=>{let{stdout:t}=await A("git",["rev-list","--max-parents=0","HEAD"],{cwd:e});return String(t??"").trim()},Ze=async(e,t=process.cwd())=>{e||=await Xe(t);let r=[".netlify","node_modules","dist",".next","out",".nuxt",".output",".cache",".turbo",".parcel-cache","coverage",".nyc_output","storybook-static","public/build"],o=[];return e.split(`
|
|
9
9
|
`).forEach(n=>{r.forEach(s=>{let a=n===`?? ${s}`,c=n.startsWith(`?? ${s}/`)||n.startsWith(`?? ${s}${Hr.sep}`);(a||c)&&o.push(`:!${s}`)});let i=n.match(Dt)?.[1];i&&o.push(`:!${i}.log`)}),o},Qe=async(e=process.cwd())=>{await A("git",["reset","--hard","HEAD"],{cwd:e})},Vr=e=>{let t=e.split(`
|
|
10
10
|
`).reduce((r,o)=>{if(!o)return r;let[n,i,,...s]=o,a=s.join(""),c=n.trim(),u=i.trim();return r[a]?r[a].change=u:r[a]={filePath:a,stage:c,change:u},r},{});return Object.values(t)},Xr=async(e,t=process.cwd())=>{let r=e.filter(o=>o.stage&&!o.change).map(o=>o.filePath);r.length!==0&&await A("git",["restore","--staged","--worktree","--pathspec-from-file=-"],{cwd:t,input:r.join(`
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netlify-inference
|
|
3
|
+
description: Use Netlify AI Gateway for AI inference in Netlify Functions and server-side code. Use when adding AI/LLM features with OpenAI, Anthropic, or Google Gemini without managing API keys.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Netlify AI Gateway
|
|
7
|
+
|
|
8
|
+
Zero-config AI inference for Netlify projects. Netlify automatically injects environment variables so official
|
|
9
|
+
provider SDKs work without API keys or configuration.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
Netlify sets provider-specific environment variables in all compute contexts (Functions, Edge Functions, server-side
|
|
14
|
+
framework code). Official SDKs auto-detect these variables, so a default constructor like `new OpenAI()` works
|
|
15
|
+
out of the box. Requests are proxied through AI Gateway and billed to your Netlify account credits.
|
|
16
|
+
|
|
17
|
+
Netlify **never overrides** environment variables you have already set. The check is per-provider: if you set your own
|
|
18
|
+
`OPENAI_API_KEY`, Netlify will not set `OPENAI_API_KEY` or `OPENAI_BASE_URL`, but will still inject Anthropic and
|
|
19
|
+
Gemini variables independently.
|
|
20
|
+
|
|
21
|
+
AI Gateway requires a **credit-based plan** (Free, Personal, or Pro). It is not available on legacy pricing plans.
|
|
22
|
+
|
|
23
|
+
### Environment Variables
|
|
24
|
+
|
|
25
|
+
| Provider | Variables |
|
|
26
|
+
|----------|-----------|
|
|
27
|
+
| **Anthropic** | `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL` |
|
|
28
|
+
| **OpenAI** | `OPENAI_API_KEY`, `OPENAI_BASE_URL` |
|
|
29
|
+
| **Google Gemini** | `GEMINI_API_KEY`, `GOOGLE_GEMINI_BASE_URL` |
|
|
30
|
+
| **Gateway (always set)** | `NETLIFY_AI_GATEWAY_KEY`, `NETLIFY_AI_GATEWAY_BASE_URL` |
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
Install the SDK for your provider and use a zero-config constructor. No API key or base URL needed.
|
|
35
|
+
|
|
36
|
+
### Anthropic
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @anthropic-ai/sdk
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
44
|
+
|
|
45
|
+
const anthropic = new Anthropic()
|
|
46
|
+
|
|
47
|
+
const message = await anthropic.messages.create({
|
|
48
|
+
model: 'claude-opus-4-6',
|
|
49
|
+
max_tokens: 1024,
|
|
50
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
console.log(message.content[0].text)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### OpenAI
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install openai
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import OpenAI from 'openai'
|
|
64
|
+
|
|
65
|
+
const openai = new OpenAI()
|
|
66
|
+
|
|
67
|
+
const completion = await openai.chat.completions.create({
|
|
68
|
+
model: 'gpt-5.2',
|
|
69
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
console.log(completion.choices[0].message.content)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
OpenAI also supports the newer Responses API:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import OpenAI from 'openai'
|
|
79
|
+
|
|
80
|
+
const openai = new OpenAI()
|
|
81
|
+
|
|
82
|
+
const response = await openai.responses.create({
|
|
83
|
+
model: 'gpt-5.2',
|
|
84
|
+
input: [{ role: 'user', content: 'Hello!' }],
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
console.log(response.output_text)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Google Gemini
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install @google/genai
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { GoogleGenAI } from '@google/genai'
|
|
98
|
+
|
|
99
|
+
const ai = new GoogleGenAI({})
|
|
100
|
+
|
|
101
|
+
const response = await ai.models.generateContent({
|
|
102
|
+
model: 'gemini-3-flash-preview',
|
|
103
|
+
contents: 'Hello!',
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
console.log(response.text)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Streaming
|
|
110
|
+
|
|
111
|
+
### Anthropic Streaming
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
115
|
+
|
|
116
|
+
const anthropic = new Anthropic()
|
|
117
|
+
|
|
118
|
+
const stream = await anthropic.messages.create({
|
|
119
|
+
model: 'claude-opus-4-6',
|
|
120
|
+
max_tokens: 1024,
|
|
121
|
+
messages: [{ role: 'user', content: 'Tell me a story.' }],
|
|
122
|
+
stream: true,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
for await (const event of stream) {
|
|
126
|
+
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
|
|
127
|
+
process.stdout.write(event.delta.text)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### OpenAI Streaming
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import OpenAI from 'openai'
|
|
136
|
+
|
|
137
|
+
const openai = new OpenAI()
|
|
138
|
+
|
|
139
|
+
const stream = await openai.chat.completions.create({
|
|
140
|
+
model: 'gpt-5.2',
|
|
141
|
+
messages: [{ role: 'user', content: 'Tell me a story.' }],
|
|
142
|
+
stream: true,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
for await (const chunk of stream) {
|
|
146
|
+
if (chunk.choices[0]?.delta?.content) {
|
|
147
|
+
process.stdout.write(chunk.choices[0].delta.content)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Gemini Streaming
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { GoogleGenAI } from '@google/genai'
|
|
156
|
+
|
|
157
|
+
const ai = new GoogleGenAI({})
|
|
158
|
+
|
|
159
|
+
const stream = await ai.models.generateContentStream({
|
|
160
|
+
model: 'gemini-3-flash-preview',
|
|
161
|
+
contents: 'Tell me a story.',
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
for await (const chunk of stream) {
|
|
165
|
+
if (chunk.text) {
|
|
166
|
+
process.stdout.write(chunk.text)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Image Generation
|
|
172
|
+
|
|
173
|
+
### OpenAI (gpt-image-1)
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import OpenAI from 'openai'
|
|
177
|
+
|
|
178
|
+
const openai = new OpenAI()
|
|
179
|
+
|
|
180
|
+
const result = await openai.images.generate({
|
|
181
|
+
model: 'gpt-image-1',
|
|
182
|
+
prompt: 'A cute otter in a river',
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const imageBase64 = result.data[0].b64_json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Image generation is also available via the Responses API with the `image_generation` tool:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const response = await openai.responses.create({
|
|
192
|
+
model: 'gpt-4o',
|
|
193
|
+
input: 'Create a simple logo',
|
|
194
|
+
tools: [{ type: 'image_generation' }],
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
for (const item of response.output) {
|
|
198
|
+
if (item.type === 'image_generation_call') {
|
|
199
|
+
const imageBase64 = item.result.data
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Google Gemini (Imagen)
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { GoogleGenAI } from '@google/genai'
|
|
208
|
+
|
|
209
|
+
const ai = new GoogleGenAI({})
|
|
210
|
+
|
|
211
|
+
const response = await ai.models.generateImages({
|
|
212
|
+
model: 'imagen-4.0-generate-001',
|
|
213
|
+
prompt: 'A cute otter in a river',
|
|
214
|
+
config: { numberOfImages: 1 },
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
for (const image of response.generatedImages) {
|
|
218
|
+
const imageBytes = image.image.imageBytes
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Netlify Functions
|
|
223
|
+
|
|
224
|
+
Full example of a Netlify Function using AI Gateway:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// netlify/functions/chat.mts
|
|
228
|
+
import type { Context } from '@netlify/functions'
|
|
229
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
230
|
+
|
|
231
|
+
const anthropic = new Anthropic()
|
|
232
|
+
|
|
233
|
+
export default async (req: Request, context: Context) => {
|
|
234
|
+
const { prompt } = await req.json()
|
|
235
|
+
|
|
236
|
+
const message = await anthropic.messages.create({
|
|
237
|
+
model: 'claude-opus-4-6',
|
|
238
|
+
max_tokens: 1024,
|
|
239
|
+
messages: [{ role: 'user', content: prompt }],
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
return Response.json({ response: message.content[0].text })
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export const config = {
|
|
246
|
+
path: '/api/chat',
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
For streaming responses from a function:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// netlify/functions/stream.mts
|
|
254
|
+
import type { Context } from '@netlify/functions'
|
|
255
|
+
import OpenAI from 'openai'
|
|
256
|
+
|
|
257
|
+
const openai = new OpenAI()
|
|
258
|
+
|
|
259
|
+
export default async (req: Request, context: Context) => {
|
|
260
|
+
const { prompt } = await req.json()
|
|
261
|
+
|
|
262
|
+
const stream = await openai.chat.completions.create({
|
|
263
|
+
model: 'gpt-5.2',
|
|
264
|
+
messages: [{ role: 'user', content: prompt }],
|
|
265
|
+
stream: true,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
return new Response(
|
|
269
|
+
new ReadableStream({
|
|
270
|
+
async start(controller) {
|
|
271
|
+
for await (const chunk of stream) {
|
|
272
|
+
const text = chunk.choices[0]?.delta?.content
|
|
273
|
+
if (text) {
|
|
274
|
+
controller.enqueue(new TextEncoder().encode(text))
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
controller.close()
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
280
|
+
{ headers: { 'Content-Type': 'text/plain; charset=utf-8' } },
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export const config = {
|
|
285
|
+
path: '/api/stream',
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Framework Server-Side Code
|
|
290
|
+
|
|
291
|
+
AI Gateway env vars are available in any server-side context, not just Netlify Functions.
|
|
292
|
+
|
|
293
|
+
### Next.js Route Handler
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// app/api/chat/route.ts
|
|
297
|
+
import OpenAI from 'openai'
|
|
298
|
+
|
|
299
|
+
const openai = new OpenAI()
|
|
300
|
+
|
|
301
|
+
export async function POST(request: Request) {
|
|
302
|
+
const { prompt } = await request.json()
|
|
303
|
+
|
|
304
|
+
const completion = await openai.chat.completions.create({
|
|
305
|
+
model: 'gpt-5.2',
|
|
306
|
+
messages: [{ role: 'user', content: prompt }],
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
return Response.json({ response: completion.choices[0].message.content })
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
This also works in server components, API routes in Astro, Remix loaders/actions, and SvelteKit server routes.
|
|
314
|
+
|
|
315
|
+
## REST API / Direct Fetch
|
|
316
|
+
|
|
317
|
+
For HTTP-level control, use the gateway variables directly:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const response = await fetch(`${process.env.NETLIFY_AI_GATEWAY_BASE_URL}/anthropic/v1/messages`, {
|
|
321
|
+
method: 'POST',
|
|
322
|
+
headers: {
|
|
323
|
+
'Content-Type': 'application/json',
|
|
324
|
+
'Authorization': `Bearer ${process.env.NETLIFY_AI_GATEWAY_KEY}`,
|
|
325
|
+
'anthropic-version': '2023-06-01',
|
|
326
|
+
},
|
|
327
|
+
body: JSON.stringify({
|
|
328
|
+
model: 'claude-opus-4-6',
|
|
329
|
+
max_tokens: 1024,
|
|
330
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
331
|
+
}),
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
const data = await response.json()
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Or using the provider-specific variables with `fetch`:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const response = await fetch(`${process.env.ANTHROPIC_BASE_URL}/v1/messages`, {
|
|
341
|
+
method: 'POST',
|
|
342
|
+
headers: {
|
|
343
|
+
'Content-Type': 'application/json',
|
|
344
|
+
'x-api-key': process.env.ANTHROPIC_API_KEY,
|
|
345
|
+
'anthropic-version': '2023-06-01',
|
|
346
|
+
},
|
|
347
|
+
body: JSON.stringify({
|
|
348
|
+
model: 'claude-opus-4-6',
|
|
349
|
+
max_tokens: 1024,
|
|
350
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
351
|
+
}),
|
|
352
|
+
})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Local Development
|
|
356
|
+
|
|
357
|
+
### Netlify CLI
|
|
358
|
+
|
|
359
|
+
Run `netlify dev` to start a local development server with AI Gateway env vars injected automatically.
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
netlify dev
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Vite Plugin
|
|
366
|
+
|
|
367
|
+
Alternatively, use the `@netlify/vite-plugin` for Vite-based projects:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
npm install @netlify/vite-plugin
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
// vite.config.js
|
|
375
|
+
import { defineConfig } from 'vite'
|
|
376
|
+
import netlify from '@netlify/vite-plugin'
|
|
377
|
+
|
|
378
|
+
export default defineConfig({
|
|
379
|
+
plugins: [netlify()],
|
|
380
|
+
})
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Then run your normal dev command (`npm run dev`).
|
|
384
|
+
|
|
385
|
+
**Requirement:** The site must have at least one production deploy before AI Gateway env vars become available locally.
|
|
386
|
+
|
|
387
|
+
## Available Models
|
|
388
|
+
|
|
389
|
+
Key models per provider. For the latest list, see https://docs.netlify.com/build/ai-gateway/overview/.
|
|
390
|
+
|
|
391
|
+
### Anthropic
|
|
392
|
+
|
|
393
|
+
- `claude-opus-4-6`
|
|
394
|
+
- `claude-opus-4-5-20251101`
|
|
395
|
+
- `claude-opus-4-1-20250805`
|
|
396
|
+
- `claude-sonnet-4-5-20250929`
|
|
397
|
+
- `claude-sonnet-4-20250514`
|
|
398
|
+
- `claude-haiku-4-5-20251001`
|
|
399
|
+
- `claude-3-7-sonnet-20250219`
|
|
400
|
+
- `claude-3-5-haiku-20241022`
|
|
401
|
+
|
|
402
|
+
### OpenAI
|
|
403
|
+
|
|
404
|
+
- `gpt-5.2`
|
|
405
|
+
- `gpt-5.1`
|
|
406
|
+
- `gpt-5.1-codex`
|
|
407
|
+
- `gpt-5`
|
|
408
|
+
- `gpt-5-mini`
|
|
409
|
+
- `gpt-5-nano`
|
|
410
|
+
- `gpt-4.1`
|
|
411
|
+
- `gpt-4.1-mini`
|
|
412
|
+
- `gpt-4.1-nano`
|
|
413
|
+
- `gpt-4o`
|
|
414
|
+
- `gpt-4o-mini`
|
|
415
|
+
- `o3`
|
|
416
|
+
- `o3-mini`
|
|
417
|
+
- `o4-mini`
|
|
418
|
+
- `gpt-image-1` (image generation)
|
|
419
|
+
- `codex-mini-latest`
|
|
420
|
+
|
|
421
|
+
### Google Gemini
|
|
422
|
+
|
|
423
|
+
- `gemini-3-pro-preview`
|
|
424
|
+
- `gemini-3-flash-preview`
|
|
425
|
+
- `gemini-2.5-pro`
|
|
426
|
+
- `gemini-2.5-flash`
|
|
427
|
+
- `gemini-2.5-flash-lite`
|
|
428
|
+
- `gemini-2.0-flash`
|
|
429
|
+
- `gemini-2.0-flash-lite`
|
|
430
|
+
- `imagen-4.0-generate-001` (image generation)
|
|
431
|
+
- `veo-3.0-generate-preview` (video generation)
|
|
432
|
+
|
|
433
|
+
## Rate Limits
|
|
434
|
+
|
|
435
|
+
Tokens per minute (TPM) are scoped per **account** across all projects. Both input and output tokens count.
|
|
436
|
+
For Anthropic, cached tokens are excluded; for other providers, cached tokens are included.
|
|
437
|
+
For the latest limits, see https://docs.netlify.com/build/ai-gateway/overview/.
|
|
438
|
+
|
|
439
|
+
| Model | Free | Personal | Pro |
|
|
440
|
+
|-------|------|----------|-----|
|
|
441
|
+
| claude-sonnet-4-5-20250929 | 18,000 | 90,000 | 180,000 |
|
|
442
|
+
| gpt-5 | 18,000 | 90,000 | 180,000 |
|
|
443
|
+
| gpt-4o-mini | 250,000 | 500,000 | 750,000 |
|
|
444
|
+
| gemini-2.5-pro | 24,000 | 120,000 | 240,000 |
|
|
445
|
+
|
|
446
|
+
Set up rate limiting rules on your functions to prevent abuse from client-side callers.
|
|
447
|
+
|
|
448
|
+
## Limitations
|
|
449
|
+
|
|
450
|
+
- **Context window:** Max 200k tokens per request
|
|
451
|
+
- **Batch inference:** Not supported
|
|
452
|
+
- **Custom headers:** Cannot send custom request headers to providers
|
|
453
|
+
- **Prompt caching:** Anthropic supports 5-minute ephemeral caching only. Gemini caching is not supported. OpenAI sets `prompt_cache_key` per-account automatically.
|
|
454
|
+
- **Priority processing (OpenAI):** Not supported
|
|
455
|
+
- **Production deploy required:** At least one production deploy must exist before AI Gateway activates
|
|
456
|
+
|
|
457
|
+
## Disabling AI Gateway
|
|
458
|
+
|
|
459
|
+
To prevent Netlify from injecting any AI-related environment variables, disable AI features in the Netlify UI under
|
|
460
|
+
**Project configuration > Build & deploy > Build with AI > Manage AI features**.
|
|
461
|
+
|
|
462
|
+
## Monitoring Usage
|
|
463
|
+
|
|
464
|
+
View AI Gateway token usage and costs in the Netlify UI under **Team settings > Billing > Usage**. Each request's
|
|
465
|
+
token consumption is tracked per model and converted to Netlify credits.
|
|
466
|
+
|
|
467
|
+
## Common Errors & Solutions
|
|
468
|
+
|
|
469
|
+
### "API key not found" or env var is undefined
|
|
470
|
+
|
|
471
|
+
**Cause:** AI Gateway env vars are not injected.
|
|
472
|
+
|
|
473
|
+
**Fix:**
|
|
474
|
+
|
|
475
|
+
1. Ensure the site has at least one production deploy
|
|
476
|
+
2. Use `netlify dev` (not a bare `npm run dev`) for local development
|
|
477
|
+
3. Check that you are reading the env var in server-side code, not client-side browser code
|
|
478
|
+
|
|
479
|
+
### Rate limit exceeded (429)
|
|
480
|
+
|
|
481
|
+
**Cause:** Account hit the tokens-per-minute limit for that model.
|
|
482
|
+
|
|
483
|
+
**Fix:**
|
|
484
|
+
|
|
485
|
+
1. Wait briefly and retry — TPM limits reset each minute
|
|
486
|
+
2. Switch to a smaller/cheaper model (e.g. `gpt-4.1-mini` instead of `gpt-4.1`)
|
|
487
|
+
3. Reduce prompt length or `max_tokens`
|
|
488
|
+
4. Upgrade plan tier for higher limits
|
|
489
|
+
|
|
490
|
+
### Model not available
|
|
491
|
+
|
|
492
|
+
**Cause:** The requested model is not supported through AI Gateway.
|
|
493
|
+
|
|
494
|
+
**Fix:**
|
|
495
|
+
|
|
496
|
+
1. Check the available models list above
|
|
497
|
+
2. Use the exact model ID string (e.g. `claude-sonnet-4-5-20250929`, not `claude-sonnet`)
|
|
498
|
+
|
|
499
|
+
## Packages
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
# Anthropic
|
|
503
|
+
npm install @anthropic-ai/sdk
|
|
504
|
+
|
|
505
|
+
# OpenAI
|
|
506
|
+
npm install openai
|
|
507
|
+
|
|
508
|
+
# Google Gemini
|
|
509
|
+
npm install @google/genai
|
|
510
|
+
```
|