@north-light/cli 0.3.564 → 0.3.565

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.
@@ -1 +1 @@
1
- var he=Object.defineProperty;var o=(e,t)=>he(e,"name",{value:t,configurable:!0});import{createHash as z,randomBytes as V,randomUUID as le}from"node:crypto";import{createServer as _e}from"node:http";import{RunError as p}from"../runs/operations.js";import{openDb as O}from"../canvas/db.js";import{personIdAsync as R,runAsPerson as fe}from"../canvas/person.js";import{loadSecretsKey as N,open as B,seal as ge}from"../secrets/index.js";import{describeOutboundFailure as me}from"../events/outbound.js";import{ANTHROPIC_PROVIDER_ID as m,OPENAI_CHATGPT_PROVIDER_ID as w,OPENAI_CODEX_PROVIDER_ID as W,commitManagedLogin as we,openAIDeviceId as ye,readSubscriptionPool as U}from"../subscription-state.js";const K=900,X="9d1c250a-e61b-44d9-88ed-5944d1962f5e",ve="https://claude.ai/oauth/authorize",Y="https://console.anthropic.com/oauth/code/callback",Z={port:53692,path:"/callback",redirect:"http://localhost:53692/callback"},be="org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",$="app_EMoamEEZ73f0CkXaXp7hrann",Ae="https://auth.openai.com/codex/device",Q="https://auth.openai.com/deviceauth/callback",ee={port:1455,path:"/auth/callback",redirect:"http://localhost:1455/auth/callback"},xe="openid profile email offline_access",Pe=900,te="https://api.openai.com/auth",Se="dynamic_agent_client",Ee="crtr",Te="https://auth.openai.com/api/accounts/authorize",ne="https://api.openai.com/v1",b={port:1455,path:"/auth/callback",redirect:"http://127.0.0.1:1455/auth/callback"},M="chatgpt.tokens.use.direct",ke=`openid profile email offline_access resource.invoke ${M}`,Oe=180*1e3,Re=o(()=>process.env.CRTR_MODEL_AUTH_ANTHROPIC_TOKEN_URL||"https://platform.claude.com/v1/oauth/token","anthropicTokenUrl"),I=o(()=>(process.env.CRTR_MODEL_AUTH_OPENAI_BASE||"https://auth.openai.com").replace(/\/$/,""),"openaiBase");class A extends p{static{o(this,"VendorSignInError")}category;step;url;httpStatus;constructor(t,n,r,a,i,s){super(502,"upstream_refused",a),this.category=t,this.step=n,this.url=r,this.httpStatus=s,i!==void 0&&(this.cause=i)}logFields(){return{category:this.category,...this.httpStatus===void 0?{}:{http_status:this.httpStatus},...me(this.cause??this,{step:this.step,url:this.url})}}}async function x(e,t,n){try{return await fetch(t,n)}catch(r){throw new A("network",e,t,`could not reach ${new URL(t).host}${new URL(t).pathname}: ${r instanceof Error?r.message:String(r)}`,r)}}o(x,"vendorFetch");async function P(e,t,n){let r;try{r=await n.text()}catch(a){throw new A("network",e,t,`connection to ${new URL(t).host}${new URL(t).pathname} dropped while reading the answer: ${a instanceof Error?a.message:String(a)}`,a)}try{return JSON.parse(r)}catch{throw f(e,t,`${new URL(t).host}${new URL(t).pathname} answered with a body that is not JSON`,n.status)}}o(P,"vendorJson");const f=o((e,t,n,r)=>new A("refused",e,t,n,void 0,r),"refusal"),g=new Map,S=new Map,C=o((e,t)=>`${e}|${t}`,"providerKey"),F=o((e,t)=>`model_sign_in_flows|${e}|${t}`,"flowAad"),re=o(e=>z("sha256").update(`state:${e}`).digest("hex").slice(0,32),"openaiState");async function Ie(e){const t=e.person,{sealed:n,keyId:r}=ge(JSON.stringify({verifier:e.verifier,...e.label?{label:e.label}:{},...e.jti?{jti:e.jti}:{}}),N(),F(t,e.id)),a=await O();await a.prepare("DELETE FROM model_sign_in_flows WHERE person = ? AND expires_at < ?").run(t,new Date().toISOString()),await a.prepare("INSERT INTO model_sign_in_flows (flow_id, provider, person, verifier_sealed, key_id, expires_at) VALUES (?, ?, ?, ?, ?, ?)").run(e.id,e.provider,t,n,r,new Date(e.expiresAt).toISOString())}o(Ie,"writeFlowRow");async function Ce(e){const t=await R(),n=await(await O()).prepare("SELECT provider, verifier_sealed, key_id, expires_at FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").get(e,t);if(!n||Date.parse(n.expires_at)<=Date.now())return;const r=JSON.parse(B(n.verifier_sealed,n.key_id,N(),F(t,e))),a=n.provider;if(a!==m&&a!==w)return;const i={id:e,person:t,provider:a,kind:a===m?"code_page":"redirect_paste",state:a===m?r.verifier:re(r.verifier),verifier:r.verifier,redirectUri:a===m?Y:b.redirect,expiresAt:Date.parse(n.expires_at),status:"pending",...r.jti?{jti:r.jti}:{},...r.label?{label:r.label}:{},lastRateLimitedAt:Math.max(0,...(await U(a)).map(s=>s.lastRateLimitedAt)),abort:new AbortController,timer:setTimeout(()=>_(i,"expired","the sign-in expired"),Math.max(0,Date.parse(n.expires_at)-Date.now())),waiters:new Set,exchanging:!1};return i.timer.unref(),i}o(Ce,"readFlowRow");function H(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}o(H,"base64url");function Le(){const e=H(V(32));return{verifier:e,challenge:H(z("sha256").update(e).digest())}}o(Le,"pkce");const ie="openai-chatgpt";function ae(e){if(e==="openai")return W;if(e===ie)return w;if(e===m||e===W)return e;throw new p(400,"invalid_request","provider must be anthropic, openai-codex, or openai-chatgpt","provider")}o(ae,"managedProvider");function Ze(e){return e===w?ie:e}o(Ze,"modelAuthApiProvider");function Qe(e){return ae(e)}o(Qe,"modelAuthProvider");function _(e,t,n,r){if(e.status==="pending"){e.status=t,n!==void 0&&(e.message=n),r instanceof A&&(e.failure=r),clearTimeout(e.timer),e.abort.abort(),e.server?.close(),e.server?.closeIdleConnections(),S.get(C(e.person,e.provider))===e.id&&S.delete(C(e.person,e.provider)),O().then(async a=>{await a.prepare("DELETE FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").run(e.id,e.person)}).catch(()=>{});for(const a of e.waiters)a();e.waiters.clear(),setTimeout(()=>{g.get(e.id)===e&&g.delete(e.id)},6e4).unref()}}o(_,"settle");async function G(e,t){if(e.status!=="pending")throw new Error("the sign-in ended before the credential was written");await fe(e.person,()=>we(e.provider,e.label,{access:t.access,refresh:t.refresh,expires:t.expires,...t.accountId?{accountId:t.accountId}:{},...t.clientId?{clientId:t.clientId}:{},...t.email?{email:t.email}:{},lastRateLimitedAt:e.lastRateLimitedAt,autoLabel:e.label===void 0}))}o(G,"writeCredential");async function oe(e,t,n){const r=Re(),a=await x("token_exchange",r,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({grant_type:"authorization_code",client_id:X,code:t,state:n,redirect_uri:e.redirectUri,code_verifier:e.verifier}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!a.ok)throw f("token_exchange",r,`Anthropic token exchange failed (${a.status})`,a.status);const i=await P("token_exchange",r,a);if(typeof i.access_token!="string"||typeof i.refresh_token!="string"||typeof i.expires_in!="number"||!Number.isFinite(i.expires_in))throw f("token_exchange",r,"Anthropic token exchange returned no credential");await G(e,{access:i.access_token,refresh:i.refresh_token,expires:Date.now()+i.expires_in*1e3-300*1e3})}o(oe,"exchangeAnthropic");function De(e){const t=e.split(".")[1];let n;try{n=JSON.parse(Buffer.from(t??"","base64url").toString("utf8"))[te]?.chatgpt_account_id}catch{n=void 0}if(typeof n!="string"||n==="")throw new Error("OpenAI token carries no ChatGPT account id");return n}o(De,"openaiAccountId");async function se(e,t,n,r){const a=`${I()}/oauth/token`,i=await x("token_exchange",a,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:$,code:t,code_verifier:n,redirect_uri:r}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!i.ok)throw f("token_exchange",a,`OpenAI token exchange failed (${i.status})`,i.status);const s=await P("token_exchange",a,i);if(typeof s.access_token!="string"||typeof s.refresh_token!="string"||typeof s.expires_in!="number")throw f("token_exchange",a,"OpenAI token exchange returned no credential");await G(e,{access:s.access_token,refresh:s.refresh_token,expires:Date.now()+s.expires_in*1e3,accountId:De(s.access_token)})}o(se,"exchangeOpenAI");function ce(e){try{const t=JSON.parse(Buffer.from(e?.split(".")[1]??"","base64url").toString("utf8"));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:void 0}catch{return}}o(ce,"jwtPayload");function Ne(...e){for(const t of e){const n=ce(t)?.[te];if(typeof n?.chatgpt_account_id=="string"&&n.chatgpt_account_id)return n.chatgpt_account_id}}o(Ne,"chatgptAccountId");function de(e,t){const n=e.searchParams.get("error");if(n)throw new p(400,"invalid_request",`ChatGPT authorization failed: ${n}`,"input");const r=e.searchParams.get("code");if(!r)throw new p(400,"invalid_request","the redirect URL has no authorization code","input");if(e.searchParams.get("state")!==t)throw new p(400,"invalid_request","the pasted redirect URL belongs to a different sign-in","state");const a=e.searchParams.get("client_id")?.trim();if(!a)throw new p(400,"invalid_request","the redirect URL carries no issued client id","input");return{code:r,clientId:a}}o(de,"chatgptCallback");async function ue(e,t,n){const r=`${I()}/api/accounts/oauth/token`,a=await x("token_exchange",r,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:n,code:t,code_verifier:e.verifier,redirect_uri:e.redirectUri,resource:ne}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!a.ok)throw f("token_exchange",r,`ChatGPT token exchange failed (${a.status})`,a.status);const i=await P("token_exchange",r,a);if(typeof i.access_token!="string"||!i.access_token||typeof i.refresh_token!="string"||!i.refresh_token||typeof i.expires_in!="number"||!Number.isFinite(i.expires_in)||i.expires_in<=0||typeof i.id_token!="string"||!i.id_token)throw f("token_exchange",r,"ChatGPT token exchange returned no credential");if(!(typeof i.scope=="string"?i.scope.trim().split(/\s+/):[]).includes(M))throw f("token_exchange",r,`ChatGPT sign-in did not grant ${M}`);const l=Ne(i.access_token,i.id_token),d=ce(i.id_token)?.email;await G(e,{access:i.access_token,refresh:i.refresh_token,expires:Date.now()+i.expires_in*1e3-Oe,clientId:n,...l?{accountId:l}:{},...typeof d=="string"&&d?{email:d}:{}})}o(ue,"exchangeChatGPT");function Ue(e,t){return new Promise((n,r)=>{if(t.aborted){r(new Error("flow ended"));return}const a=o(()=>{clearTimeout(i),r(new Error("flow ended"))},"onAbort"),i=setTimeout(()=>{t.removeEventListener("abort",a),n()},e);t.addEventListener("abort",a,{once:!0})})}o(Ue,"sleep");async function $e(e,t){const n=Math.min(e.expiresAt,Date.now()+Pe*1e3);let r=Math.max(1e3,Math.floor(t.intervalS*1e3));try{for(;Date.now()<n&&(await Ue(Math.min(r,Math.max(0,n-Date.now())),e.abort.signal),!(Date.now()>=n));){const a=`${I()}/api/accounts/deviceauth/token`,i=await x("poll",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_auth_id:t.deviceAuthId,user_code:t.userCode}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(i.ok){const d=await P("poll",a,i);if(typeof d.authorization_code!="string"||typeof d.code_verifier!="string")throw f("poll",a,"OpenAI device approval returned no code");await se(e,d.authorization_code,d.code_verifier,Q),_(e,"connected");return}if(i.status===403||i.status===404)continue;let s;try{s=await i.text()}catch(d){throw new A("network","poll",a,`connection to ${new URL(a).host}${new URL(a).pathname} dropped while reading the answer: ${d instanceof Error?d.message:String(d)}`,d)}let l;try{const d=JSON.parse(s).error;l=typeof d=="object"&&d!==null?d.code:d}catch{l=void 0}if(l!=="deviceauth_authorization_pending"){if(l==="slow_down"){r+=5e3;continue}throw f("poll",a,`OpenAI device sign-in failed (${i.status})`,i.status)}}_(e,"expired","the OpenAI code expired")}catch(a){e.status==="pending"&&_(e,"failed",a instanceof Error?a.message:String(a),a)}}o($e,"pollDevice");function j(e,t){const n=_e((r,a)=>{const i=new URL(r.url??"/","http://localhost"),s=o((c,v)=>{a.writeHead(c,{"Content-Type":"text/plain; charset=utf-8",Connection:"close"}),a.end(v)},"reply");if(i.pathname!==t.path){s(404,"Not found.");return}const l=i.searchParams.get("code");if(i.searchParams.get("state")!==e.state||!l){s(400,"Sign-in could not be completed: state mismatch or missing code.");return}let d="";if(e.provider===w)try{d=de(i,e.state).clientId}catch(c){s(400,`Sign-in could not be completed: ${c instanceof Error?c.message:String(c)}.`);return}if(e.status!=="pending"||e.exchanging){s(409,"This sign-in has already finished.");return}e.exchanging=!0,(e.provider===m?oe(e,l,e.state):e.provider===w?ue(e,l,d):se(e,l,e.verifier,e.redirectUri)).then(()=>{s(200,"Signed in. You can close this window."),_(e,"connected")},c=>{s(502,"The vendor refused the sign-in."),_(e,"failed",c instanceof Error?c.message:String(c),c)})});return e.server=n,new Promise((r,a)=>{n.once("error",i=>a(new p(409,"conflict",`cannot listen on localhost:${t.port} for the vendor redirect (${i.code??i.message})`,"redirect"))),n.listen(t.port,"127.0.0.1",()=>r())})}o(j,"listenLoopback");async function et(e){const t=ae(e.provider);if(e.redirect!==void 0&&e.redirect!=="loopback")throw new p(400,"invalid_request",'redirect must be "loopback" when present',"redirect");if(e.label!==void 0&&typeof e.label!="string")throw new p(400,"invalid_request","label must be a string when present","label");const n=typeof e.label=="string"&&e.label.trim()?e.label.trim():void 0,r=await R(),a=Math.max(0,...(await U(t)).map(h=>h.lastRateLimitedAt)),i=S.get(C(r,t));if(i!==void 0){const h=g.get(i);h&&_(h,"failed","replaced by a newer sign-in")}const{verifier:s,challenge:l}=Le(),d=e.redirect==="loopback",D=Date.now()+K*1e3,c={id:le(),person:r,provider:t,kind:d?"loopback":t===m?"code_page":t===w?"redirect_paste":"device",state:t===m?s:re(s),verifier:s,redirectUri:t===m?d?Z.redirect:Y:t===w?b.redirect:d?ee.redirect:Q,expiresAt:D,status:"pending",...e.jti?{jti:e.jti}:{},...n?{label:n}:{},lastRateLimitedAt:a,abort:new AbortController,timer:setTimeout(()=>_(c,"expired","the sign-in expired"),K*1e3),waiters:new Set,exchanging:!1};c.timer.unref(),pe(c),await Ie(c);const v=o(()=>new p(409,"conflict","a newer sign-in for this provider replaced this one","provider"),"replacedDuringStart"),E={flow_id:c.id,provider:t,expires_at:new Date(D).toISOString()};try{if(t===m){const u=new URLSearchParams;if(u.set("code","true"),u.set("client_id",X),u.set("response_type","code"),u.set("redirect_uri",c.redirectUri),u.set("scope",be),u.set("code_challenge",l),u.set("code_challenge_method","S256"),u.set("state",c.state),d&&await j(c,Z),c.status!=="pending")throw v();return{...E,url:`${ve}?${u.toString()}`,instructions:d?"Sign in to Claude in your browser; this finishes by itself.":"Sign in to Claude, then copy the code Claude shows and paste it here."}}if(t===w){const u=new URL(Te);if(u.search=new URLSearchParams({client_id:Se,agent_name_hint:Ee,ext_agent_host_id:`urn:uuid:${await ye()}`,response_type:"code",redirect_uri:c.redirectUri,resource:ne,scope:ke,state:c.state,code_challenge:l,code_challenge_method:"S256",nonce:H(V(32))}).toString(),d&&await j(c,b),c.status!=="pending")throw v();return{...E,url:u.toString(),instructions:d?"Sign in with ChatGPT in your browser; this finishes by itself. If it does not, paste the final redirect URL here.":`Sign in with ChatGPT, then copy the full URL your browser lands on (it starts with ${b.redirect} and may show a connection error) and paste it here.`}}if(d){const u=new URL("https://auth.openai.com/oauth/authorize");if(u.searchParams.set("response_type","code"),u.searchParams.set("client_id",$),u.searchParams.set("redirect_uri",c.redirectUri),u.searchParams.set("scope",xe),u.searchParams.set("code_challenge",l),u.searchParams.set("code_challenge_method","S256"),u.searchParams.set("state",c.state),u.searchParams.set("id_token_add_organizations","true"),u.searchParams.set("codex_cli_simplified_flow","true"),u.searchParams.set("originator","pi"),await j(c,ee),c.status!=="pending")throw v();return{...E,url:u.toString(),instructions:"Sign in to ChatGPT in your browser; this finishes by itself."}}const h=`${I()}/api/accounts/deviceauth/usercode`,T=await x("device_code_start",h,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:$}),signal:AbortSignal.timeout(3e4)});if(!T.ok)throw f("device_code_start",h,`OpenAI refused to issue a device code (${T.status})`,T.status);const y=await P("device_code_start",h,T),k=typeof y.interval=="string"?Number(y.interval.trim()):y.interval;if(typeof y.device_auth_id!="string"||typeof y.user_code!="string"||typeof k!="number"||!Number.isFinite(k)||k<0)throw f("device_code_start",h,"OpenAI returned an invalid device code");if(c.status!=="pending")throw v();return $e(c,{deviceAuthId:y.device_auth_id,userCode:y.user_code,intervalS:k}),{...E,url:Ae,user_code:y.user_code,instructions:"Open the link, sign in to ChatGPT, and enter the code. The code expires in 15 minutes."}}catch(h){throw _(c,"failed",h instanceof Error?h.message:String(h),h),g.delete(c.id),h instanceof p?h:new p(502,"upstream_refused",`vendor sign-in could not start: ${h instanceof Error?h.message:String(h)}`)}}o(et,"startModelAuthFlow");async function L(e){const t=g.get(e);return t&&t.person===await R()?t:void 0}o(L,"ownFlow");function pe(e){g.set(e.id,e),S.set(C(e.person,e.provider),e.id)}o(pe,"register");async function tt(e){const t=await L(e);if(t)return _(t,"failed","cancelled"),q(t)}o(tt,"cancelModelAuthFlow");function Me(e){const t=e.trim();if(!t)return{};try{const n=new URL(t);return{code:n.searchParams.get("code")??void 0,state:n.searchParams.get("state")??void 0}}catch{}if(t.includes("#")){const[n,r]=t.split("#",2);return{code:n,state:r}}if(t.includes("code=")){const n=new URLSearchParams(t);return{code:n.get("code")??void 0,state:n.get("state")??void 0}}return{code:t}}o(Me,"parseCodeInput");async function Fe(e){let t=typeof e=="string"?await L(e):void 0;if(!t&&typeof e=="string"&&!g.has(e)&&(t=await Ce(e),t&&pe(t)),!t||t.status!=="pending")throw new p(400,"invalid_request","no in-flight sign-in has this flow_id","flow_id");return t}o(Fe,"pendingFlow");async function nt(e,t){const n=await Fe(e);if(n.provider===w)return He(n,t);if(n.kind!=="code_page")throw new p(400,"invalid_request","this sign-in completes without a pasted code","flow_id");if(n.exchanging)throw new p(400,"invalid_request","this sign-in is already being completed","flow_id");if(typeof t!="string")throw new p(400,"invalid_request","input must be the code Claude shows","input");const{code:r,state:a}=Me(t);if(!r)throw new p(400,"invalid_request","input must be the code Claude shows","input");if(a!==void 0&&a!==n.state)throw new p(400,"invalid_request","the pasted code belongs to a different sign-in","state");n.exchanging=!0;try{await oe(n,r,a??n.state)}catch(i){throw _(n,"failed",i instanceof Error?i.message:String(i),i),new p(502,"upstream_refused","Anthropic refused the code")}return _(n,"connected"),{flow_id:n.id,provider:n.provider,status:"connected"}}o(nt,"completeModelAuthFlow");async function He(e,t){if(e.exchanging)throw new p(400,"invalid_request","this sign-in is already being completed","flow_id");const n=new URL(b.redirect);let r;try{r=typeof t=="string"?new URL(t.trim()):void 0}catch{r=void 0}if(!r||r.origin!==n.origin||r.pathname!==n.pathname)throw new p(400,"invalid_request",`input must be the full redirect URL starting with ${b.redirect}`,"input");const{code:a,clientId:i}=de(r,e.state);e.exchanging=!0;try{await ue(e,a,i)}catch(s){throw _(e,"failed",s instanceof Error?s.message:String(s),s),new p(502,"upstream_refused","OpenAI refused the ChatGPT sign-in")}return _(e,"connected"),{flow_id:e.id,provider:e.provider,status:"connected"}}o(He,"completeChatGPTFlow");function q(e){return{flow_id:e.id,provider:e.provider,status:e.status,...e.message?{message:e.message}:{},...e.jti?{jti:e.jti}:{}}}o(q,"view");async function J(e){const t=await R(),n=await(await O()).prepare("SELECT provider, verifier_sealed, key_id, expires_at FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").get(e,t);if(!n||Date.parse(n.expires_at)<=Date.now())return;const r=JSON.parse(B(n.verifier_sealed,n.key_id,N(),F(t,e)));return{flow_id:e,provider:n.provider,status:"pending",...r.jti?{jti:r.jti}:{}}}o(J,"pendingRowView");async function rt(e){const t=await L(e);return t?q(t):g.has(e)?void 0:await J(e)}o(rt,"readModelAuthFlow");function it(e){const t=g.get(e);return t?.status==="expired"?{category:"expired",fields:{category:"expired"}}:t?.failure?{category:t.failure.category,step:t.failure.step,fields:t.failure.logFields()}:{category:"runtime",fields:{category:"runtime"}}}o(it,"modelAuthFlowFailure");async function at(e,t,n){const r=await L(e);return r?(r.status==="pending"&&t>0&&!n?.aborted&&await new Promise(a=>{const i=o(()=>{clearTimeout(s),n?.removeEventListener("abort",i),r.waiters.delete(i),a()},"done"),s=setTimeout(i,t);n?.addEventListener("abort",i,{once:!0}),r.waiters.add(i)}),q(r)):g.has(e)?void 0:await je(e,t,n)}o(at,"waitModelAuthFlow");const Ge=1e3;async function je(e,t,n){const r=await J(e);if(!r)return;const a=Date.now()+t;for(;!n?.aborted&&Date.now()<a&&(await new Promise(i=>setTimeout(i,Math.min(Ge,Math.max(0,a-Date.now())))),!n?.aborted);)if(!await J(e)){const i=(await U(r.provider)).length>0;return{...r,status:i?"connected":"failed"}}return r}o(je,"waitRowFlow");function ot(){for(const e of g.values())_(e,"failed","reset");g.clear(),S.clear()}o(ot,"resetModelAuthFlowsForTest");export{K as MODEL_AUTH_FLOW_TTL_S,ie as OPENAI_CHATGPT_API_PROVIDER,A as VendorSignInError,tt as cancelModelAuthFlow,nt as completeModelAuthFlow,Ze as modelAuthApiProvider,it as modelAuthFlowFailure,Qe as modelAuthProvider,rt as readModelAuthFlow,ot as resetModelAuthFlowsForTest,et as startModelAuthFlow,at as waitModelAuthFlow};
1
+ var le=Object.defineProperty;var o=(e,t)=>le(e,"name",{value:t,configurable:!0});import{createHash as V,randomBytes as B,randomUUID as _e}from"node:crypto";import{createServer as fe}from"node:http";import{RunError as p}from"../runs/operations.js";import{openDb as R}from"../canvas/db.js";import{isCloudRole as ge,personIdAsync as O,runAsPerson as me}from"../canvas/person.js";import{loadSecretsKey as U,open as W,seal as we}from"../secrets/index.js";import{describeOutboundFailure as ye}from"../events/outbound.js";import{ANTHROPIC_PROVIDER_ID as m,OPENAI_CHATGPT_PROVIDER_ID as w,OPENAI_CODEX_PROVIDER_ID as K,commitManagedLogin as ve,openAIDeviceId as be,readSubscriptionPool as $}from"../subscription-state.js";const X=900,Y="9d1c250a-e61b-44d9-88ed-5944d1962f5e",Ae="https://claude.ai/oauth/authorize",Z="https://console.anthropic.com/oauth/code/callback",Q={port:53692,path:"/callback",redirect:"http://localhost:53692/callback"},xe="org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",M="app_EMoamEEZ73f0CkXaXp7hrann",Pe="https://auth.openai.com/codex/device",ee="https://auth.openai.com/deviceauth/callback",te={port:1455,path:"/auth/callback",redirect:"http://localhost:1455/auth/callback"},Se="openid profile email offline_access",Ee=900,ne="https://api.openai.com/auth",Te="dynamic_agent_client",ke="crtr",Re="https://auth.openai.com/api/accounts/authorize",re="https://api.openai.com/v1",b={port:1455,path:"/auth/callback",redirect:"http://127.0.0.1:1455/auth/callback"},F="chatgpt.tokens.use.direct",Oe=`openid profile email offline_access resource.invoke ${F}`,Ce=180*1e3,Ie=o(()=>process.env.CRTR_MODEL_AUTH_ANTHROPIC_TOKEN_URL||"https://platform.claude.com/v1/oauth/token","anthropicTokenUrl"),C=o(()=>(process.env.CRTR_MODEL_AUTH_OPENAI_BASE||"https://auth.openai.com").replace(/\/$/,""),"openaiBase");class A extends p{static{o(this,"VendorSignInError")}category;step;url;httpStatus;constructor(t,n,r,a,i,s){super(502,"upstream_refused",a),this.category=t,this.step=n,this.url=r,this.httpStatus=s,i!==void 0&&(this.cause=i)}logFields(){return{category:this.category,...this.httpStatus===void 0?{}:{http_status:this.httpStatus},...ye(this.cause??this,{step:this.step,url:this.url})}}}async function x(e,t,n){try{return await fetch(t,n)}catch(r){throw new A("network",e,t,`could not reach ${new URL(t).host}${new URL(t).pathname}: ${r instanceof Error?r.message:String(r)}`,r)}}o(x,"vendorFetch");async function P(e,t,n){let r;try{r=await n.text()}catch(a){throw new A("network",e,t,`connection to ${new URL(t).host}${new URL(t).pathname} dropped while reading the answer: ${a instanceof Error?a.message:String(a)}`,a)}try{return JSON.parse(r)}catch{throw f(e,t,`${new URL(t).host}${new URL(t).pathname} answered with a body that is not JSON`,n.status)}}o(P,"vendorJson");const f=o((e,t,n,r)=>new A("refused",e,t,n,void 0,r),"refusal"),g=new Map,S=new Map,I=o((e,t)=>`${e}|${t}`,"providerKey"),H=o((e,t)=>`model_sign_in_flows|${e}|${t}`,"flowAad"),ie=o(e=>V("sha256").update(`state:${e}`).digest("hex").slice(0,32),"openaiState"),L=o(()=>ge(),"rowsAreShared");async function Le(e){if(!L())return;const t=e.person,{sealed:n,keyId:r}=we(JSON.stringify({verifier:e.verifier,...e.label?{label:e.label}:{},...e.jti?{jti:e.jti}:{}}),U(),H(t,e.id)),a=await R();await a.prepare("DELETE FROM model_sign_in_flows WHERE person = ? AND expires_at < ?").run(t,new Date().toISOString()),await a.prepare("INSERT INTO model_sign_in_flows (flow_id, provider, person, verifier_sealed, key_id, expires_at) VALUES (?, ?, ?, ?, ?, ?)").run(e.id,e.provider,t,n,r,new Date(e.expiresAt).toISOString())}o(Le,"writeFlowRow");async function De(e){if(!L())return;const t=await O(),n=await(await R()).prepare("SELECT provider, verifier_sealed, key_id, expires_at FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").get(e,t);if(!n||Date.parse(n.expires_at)<=Date.now())return;const r=JSON.parse(W(n.verifier_sealed,n.key_id,U(),H(t,e))),a=n.provider;if(a!==m&&a!==w)return;const i={id:e,person:t,provider:a,kind:a===m?"code_page":"redirect_paste",state:a===m?r.verifier:ie(r.verifier),verifier:r.verifier,redirectUri:a===m?Z:b.redirect,expiresAt:Date.parse(n.expires_at),status:"pending",...r.jti?{jti:r.jti}:{},...r.label?{label:r.label}:{},lastRateLimitedAt:Math.max(0,...(await $(a)).map(s=>s.lastRateLimitedAt)),abort:new AbortController,timer:setTimeout(()=>_(i,"expired","the sign-in expired"),Math.max(0,Date.parse(n.expires_at)-Date.now())),waiters:new Set,exchanging:!1};return i.timer.unref(),i}o(De,"readFlowRow");function G(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}o(G,"base64url");function Ne(){const e=G(B(32));return{verifier:e,challenge:G(V("sha256").update(e).digest())}}o(Ne,"pkce");const ae="openai-chatgpt";function oe(e){if(e==="openai")return K;if(e===ae)return w;if(e===m||e===K)return e;throw new p(400,"invalid_request","provider must be anthropic, openai-codex, or openai-chatgpt","provider")}o(oe,"managedProvider");function et(e){return e===w?ae:e}o(et,"modelAuthApiProvider");function tt(e){return oe(e)}o(tt,"modelAuthProvider");function _(e,t,n,r){if(e.status==="pending"){e.status=t,n!==void 0&&(e.message=n),r instanceof A&&(e.failure=r),clearTimeout(e.timer),e.abort.abort(),e.server?.close(),e.server?.closeIdleConnections(),S.get(I(e.person,e.provider))===e.id&&S.delete(I(e.person,e.provider)),L()&&R().then(async a=>{await a.prepare("DELETE FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").run(e.id,e.person)}).catch(()=>{});for(const a of e.waiters)a();e.waiters.clear(),setTimeout(()=>{g.get(e.id)===e&&g.delete(e.id)},6e4).unref()}}o(_,"settle");async function j(e,t){if(e.status!=="pending")throw new Error("the sign-in ended before the credential was written");await me(e.person,()=>ve(e.provider,e.label,{access:t.access,refresh:t.refresh,expires:t.expires,...t.accountId?{accountId:t.accountId}:{},...t.clientId?{clientId:t.clientId}:{},...t.email?{email:t.email}:{},lastRateLimitedAt:e.lastRateLimitedAt,autoLabel:e.label===void 0}))}o(j,"writeCredential");async function se(e,t,n){const r=Ie(),a=await x("token_exchange",r,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({grant_type:"authorization_code",client_id:Y,code:t,state:n,redirect_uri:e.redirectUri,code_verifier:e.verifier}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!a.ok)throw f("token_exchange",r,`Anthropic token exchange failed (${a.status})`,a.status);const i=await P("token_exchange",r,a);if(typeof i.access_token!="string"||typeof i.refresh_token!="string"||typeof i.expires_in!="number"||!Number.isFinite(i.expires_in))throw f("token_exchange",r,"Anthropic token exchange returned no credential");await j(e,{access:i.access_token,refresh:i.refresh_token,expires:Date.now()+i.expires_in*1e3-300*1e3})}o(se,"exchangeAnthropic");function Ue(e){const t=e.split(".")[1];let n;try{n=JSON.parse(Buffer.from(t??"","base64url").toString("utf8"))[ne]?.chatgpt_account_id}catch{n=void 0}if(typeof n!="string"||n==="")throw new Error("OpenAI token carries no ChatGPT account id");return n}o(Ue,"openaiAccountId");async function ce(e,t,n,r){const a=`${C()}/oauth/token`,i=await x("token_exchange",a,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:M,code:t,code_verifier:n,redirect_uri:r}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!i.ok)throw f("token_exchange",a,`OpenAI token exchange failed (${i.status})`,i.status);const s=await P("token_exchange",a,i);if(typeof s.access_token!="string"||typeof s.refresh_token!="string"||typeof s.expires_in!="number")throw f("token_exchange",a,"OpenAI token exchange returned no credential");await j(e,{access:s.access_token,refresh:s.refresh_token,expires:Date.now()+s.expires_in*1e3,accountId:Ue(s.access_token)})}o(ce,"exchangeOpenAI");function de(e){try{const t=JSON.parse(Buffer.from(e?.split(".")[1]??"","base64url").toString("utf8"));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:void 0}catch{return}}o(de,"jwtPayload");function $e(...e){for(const t of e){const n=de(t)?.[ne];if(typeof n?.chatgpt_account_id=="string"&&n.chatgpt_account_id)return n.chatgpt_account_id}}o($e,"chatgptAccountId");function ue(e,t){const n=e.searchParams.get("error");if(n)throw new p(400,"invalid_request",`ChatGPT authorization failed: ${n}`,"input");const r=e.searchParams.get("code");if(!r)throw new p(400,"invalid_request","the redirect URL has no authorization code","input");if(e.searchParams.get("state")!==t)throw new p(400,"invalid_request","the pasted redirect URL belongs to a different sign-in","state");const a=e.searchParams.get("client_id")?.trim();if(!a)throw new p(400,"invalid_request","the redirect URL carries no issued client id","input");return{code:r,clientId:a}}o(ue,"chatgptCallback");async function pe(e,t,n){const r=`${C()}/api/accounts/oauth/token`,a=await x("token_exchange",r,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:n,code:t,code_verifier:e.verifier,redirect_uri:e.redirectUri,resource:re}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!a.ok)throw f("token_exchange",r,`ChatGPT token exchange failed (${a.status})`,a.status);const i=await P("token_exchange",r,a);if(typeof i.access_token!="string"||!i.access_token||typeof i.refresh_token!="string"||!i.refresh_token||typeof i.expires_in!="number"||!Number.isFinite(i.expires_in)||i.expires_in<=0||typeof i.id_token!="string"||!i.id_token)throw f("token_exchange",r,"ChatGPT token exchange returned no credential");if(!(typeof i.scope=="string"?i.scope.trim().split(/\s+/):[]).includes(F))throw f("token_exchange",r,`ChatGPT sign-in did not grant ${F}`);const l=$e(i.access_token,i.id_token),d=de(i.id_token)?.email;await j(e,{access:i.access_token,refresh:i.refresh_token,expires:Date.now()+i.expires_in*1e3-Ce,clientId:n,...l?{accountId:l}:{},...typeof d=="string"&&d?{email:d}:{}})}o(pe,"exchangeChatGPT");function Me(e,t){return new Promise((n,r)=>{if(t.aborted){r(new Error("flow ended"));return}const a=o(()=>{clearTimeout(i),r(new Error("flow ended"))},"onAbort"),i=setTimeout(()=>{t.removeEventListener("abort",a),n()},e);t.addEventListener("abort",a,{once:!0})})}o(Me,"sleep");async function Fe(e,t){const n=Math.min(e.expiresAt,Date.now()+Ee*1e3);let r=Math.max(1e3,Math.floor(t.intervalS*1e3));try{for(;Date.now()<n&&(await Me(Math.min(r,Math.max(0,n-Date.now())),e.abort.signal),!(Date.now()>=n));){const a=`${C()}/api/accounts/deviceauth/token`,i=await x("poll",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_auth_id:t.deviceAuthId,user_code:t.userCode}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(i.ok){const d=await P("poll",a,i);if(typeof d.authorization_code!="string"||typeof d.code_verifier!="string")throw f("poll",a,"OpenAI device approval returned no code");await ce(e,d.authorization_code,d.code_verifier,ee),_(e,"connected");return}if(i.status===403||i.status===404)continue;let s;try{s=await i.text()}catch(d){throw new A("network","poll",a,`connection to ${new URL(a).host}${new URL(a).pathname} dropped while reading the answer: ${d instanceof Error?d.message:String(d)}`,d)}let l;try{const d=JSON.parse(s).error;l=typeof d=="object"&&d!==null?d.code:d}catch{l=void 0}if(l!=="deviceauth_authorization_pending"){if(l==="slow_down"){r+=5e3;continue}throw f("poll",a,`OpenAI device sign-in failed (${i.status})`,i.status)}}_(e,"expired","the OpenAI code expired")}catch(a){e.status==="pending"&&_(e,"failed",a instanceof Error?a.message:String(a),a)}}o(Fe,"pollDevice");function q(e,t){const n=fe((r,a)=>{const i=new URL(r.url??"/","http://localhost"),s=o((c,v)=>{a.writeHead(c,{"Content-Type":"text/plain; charset=utf-8",Connection:"close"}),a.end(v)},"reply");if(i.pathname!==t.path){s(404,"Not found.");return}const l=i.searchParams.get("code");if(i.searchParams.get("state")!==e.state||!l){s(400,"Sign-in could not be completed: state mismatch or missing code.");return}let d="";if(e.provider===w)try{d=ue(i,e.state).clientId}catch(c){s(400,`Sign-in could not be completed: ${c instanceof Error?c.message:String(c)}.`);return}if(e.status!=="pending"||e.exchanging){s(409,"This sign-in has already finished.");return}e.exchanging=!0,(e.provider===m?se(e,l,e.state):e.provider===w?pe(e,l,d):ce(e,l,e.verifier,e.redirectUri)).then(()=>{s(200,"Signed in. You can close this window."),_(e,"connected")},c=>{s(502,"The vendor refused the sign-in."),_(e,"failed",c instanceof Error?c.message:String(c),c)})});return e.server=n,new Promise((r,a)=>{n.once("error",i=>a(new p(409,"conflict",`cannot listen on localhost:${t.port} for the vendor redirect (${i.code??i.message})`,"redirect"))),n.listen(t.port,"127.0.0.1",()=>r())})}o(q,"listenLoopback");async function nt(e){const t=oe(e.provider);if(e.redirect!==void 0&&e.redirect!=="loopback")throw new p(400,"invalid_request",'redirect must be "loopback" when present',"redirect");if(e.label!==void 0&&typeof e.label!="string")throw new p(400,"invalid_request","label must be a string when present","label");const n=typeof e.label=="string"&&e.label.trim()?e.label.trim():void 0,r=await O(),a=Math.max(0,...(await $(t)).map(h=>h.lastRateLimitedAt)),i=S.get(I(r,t));if(i!==void 0){const h=g.get(i);h&&_(h,"failed","replaced by a newer sign-in")}const{verifier:s,challenge:l}=Ne(),d=e.redirect==="loopback",N=Date.now()+X*1e3,c={id:_e(),person:r,provider:t,kind:d?"loopback":t===m?"code_page":t===w?"redirect_paste":"device",state:t===m?s:ie(s),verifier:s,redirectUri:t===m?d?Q.redirect:Z:t===w?b.redirect:d?te.redirect:ee,expiresAt:N,status:"pending",...e.jti?{jti:e.jti}:{},...n?{label:n}:{},lastRateLimitedAt:a,abort:new AbortController,timer:setTimeout(()=>_(c,"expired","the sign-in expired"),X*1e3),waiters:new Set,exchanging:!1};c.timer.unref(),he(c),await Le(c);const v=o(()=>new p(409,"conflict","a newer sign-in for this provider replaced this one","provider"),"replacedDuringStart"),E={flow_id:c.id,provider:t,expires_at:new Date(N).toISOString()};try{if(t===m){const u=new URLSearchParams;if(u.set("code","true"),u.set("client_id",Y),u.set("response_type","code"),u.set("redirect_uri",c.redirectUri),u.set("scope",xe),u.set("code_challenge",l),u.set("code_challenge_method","S256"),u.set("state",c.state),d&&await q(c,Q),c.status!=="pending")throw v();return{...E,url:`${Ae}?${u.toString()}`,instructions:d?"Sign in to Claude in your browser; this finishes by itself.":"Sign in to Claude, then copy the code Claude shows and paste it here."}}if(t===w){const u=new URL(Re);if(u.search=new URLSearchParams({client_id:Te,agent_name_hint:ke,ext_agent_host_id:`urn:uuid:${await be()}`,response_type:"code",redirect_uri:c.redirectUri,resource:re,scope:Oe,state:c.state,code_challenge:l,code_challenge_method:"S256",nonce:G(B(32))}).toString(),d&&await q(c,b),c.status!=="pending")throw v();return{...E,url:u.toString(),instructions:d?"Sign in with ChatGPT in your browser; this finishes by itself. If it does not, paste the final redirect URL here.":`Sign in with ChatGPT, then copy the full URL your browser lands on (it starts with ${b.redirect} and may show a connection error) and paste it here.`}}if(d){const u=new URL("https://auth.openai.com/oauth/authorize");if(u.searchParams.set("response_type","code"),u.searchParams.set("client_id",M),u.searchParams.set("redirect_uri",c.redirectUri),u.searchParams.set("scope",Se),u.searchParams.set("code_challenge",l),u.searchParams.set("code_challenge_method","S256"),u.searchParams.set("state",c.state),u.searchParams.set("id_token_add_organizations","true"),u.searchParams.set("codex_cli_simplified_flow","true"),u.searchParams.set("originator","pi"),await q(c,te),c.status!=="pending")throw v();return{...E,url:u.toString(),instructions:"Sign in to ChatGPT in your browser; this finishes by itself."}}const h=`${C()}/api/accounts/deviceauth/usercode`,T=await x("device_code_start",h,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:M}),signal:AbortSignal.timeout(3e4)});if(!T.ok)throw f("device_code_start",h,`OpenAI refused to issue a device code (${T.status})`,T.status);const y=await P("device_code_start",h,T),k=typeof y.interval=="string"?Number(y.interval.trim()):y.interval;if(typeof y.device_auth_id!="string"||typeof y.user_code!="string"||typeof k!="number"||!Number.isFinite(k)||k<0)throw f("device_code_start",h,"OpenAI returned an invalid device code");if(c.status!=="pending")throw v();return Fe(c,{deviceAuthId:y.device_auth_id,userCode:y.user_code,intervalS:k}),{...E,url:Pe,user_code:y.user_code,instructions:"Open the link, sign in to ChatGPT, and enter the code. The code expires in 15 minutes."}}catch(h){throw _(c,"failed",h instanceof Error?h.message:String(h),h),g.delete(c.id),h instanceof p?h:new p(502,"upstream_refused",`vendor sign-in could not start: ${h instanceof Error?h.message:String(h)}`)}}o(nt,"startModelAuthFlow");async function D(e){const t=g.get(e);return t&&t.person===await O()?t:void 0}o(D,"ownFlow");function he(e){g.set(e.id,e),S.set(I(e.person,e.provider),e.id)}o(he,"register");async function rt(e){const t=await D(e);if(t)return _(t,"failed","cancelled"),J(t)}o(rt,"cancelModelAuthFlow");function He(e){const t=e.trim();if(!t)return{};try{const n=new URL(t);return{code:n.searchParams.get("code")??void 0,state:n.searchParams.get("state")??void 0}}catch{}if(t.includes("#")){const[n,r]=t.split("#",2);return{code:n,state:r}}if(t.includes("code=")){const n=new URLSearchParams(t);return{code:n.get("code")??void 0,state:n.get("state")??void 0}}return{code:t}}o(He,"parseCodeInput");async function Ge(e){let t=typeof e=="string"?await D(e):void 0;if(!t&&typeof e=="string"&&!g.has(e)&&(t=await De(e),t&&he(t)),!t||t.status!=="pending")throw new p(400,"invalid_request","no in-flight sign-in has this flow_id","flow_id");return t}o(Ge,"pendingFlow");async function it(e,t){const n=await Ge(e);if(n.provider===w)return je(n,t);if(n.kind!=="code_page")throw new p(400,"invalid_request","this sign-in completes without a pasted code","flow_id");if(n.exchanging)throw new p(400,"invalid_request","this sign-in is already being completed","flow_id");if(typeof t!="string")throw new p(400,"invalid_request","input must be the code Claude shows","input");const{code:r,state:a}=He(t);if(!r)throw new p(400,"invalid_request","input must be the code Claude shows","input");if(a!==void 0&&a!==n.state)throw new p(400,"invalid_request","the pasted code belongs to a different sign-in","state");n.exchanging=!0;try{await se(n,r,a??n.state)}catch(i){throw _(n,"failed",i instanceof Error?i.message:String(i),i),new p(502,"upstream_refused","Anthropic refused the code")}return _(n,"connected"),{flow_id:n.id,provider:n.provider,status:"connected"}}o(it,"completeModelAuthFlow");async function je(e,t){if(e.exchanging)throw new p(400,"invalid_request","this sign-in is already being completed","flow_id");const n=new URL(b.redirect);let r;try{r=typeof t=="string"?new URL(t.trim()):void 0}catch{r=void 0}if(!r||r.origin!==n.origin||r.pathname!==n.pathname)throw new p(400,"invalid_request",`input must be the full redirect URL starting with ${b.redirect}`,"input");const{code:a,clientId:i}=ue(r,e.state);e.exchanging=!0;try{await pe(e,a,i)}catch(s){throw _(e,"failed",s instanceof Error?s.message:String(s),s),new p(502,"upstream_refused","OpenAI refused the ChatGPT sign-in")}return _(e,"connected"),{flow_id:e.id,provider:e.provider,status:"connected"}}o(je,"completeChatGPTFlow");function J(e){return{flow_id:e.id,provider:e.provider,status:e.status,...e.message?{message:e.message}:{},...e.jti?{jti:e.jti}:{}}}o(J,"view");async function z(e){if(!L())return;const t=await O(),n=await(await R()).prepare("SELECT provider, verifier_sealed, key_id, expires_at FROM model_sign_in_flows WHERE flow_id = ? AND person = ?").get(e,t);if(!n||Date.parse(n.expires_at)<=Date.now())return;const r=JSON.parse(W(n.verifier_sealed,n.key_id,U(),H(t,e)));return{flow_id:e,provider:n.provider,status:"pending",...r.jti?{jti:r.jti}:{}}}o(z,"pendingRowView");async function at(e){const t=await D(e);return t?J(t):g.has(e)?void 0:await z(e)}o(at,"readModelAuthFlow");function ot(e){const t=g.get(e);return t?.status==="expired"?{category:"expired",fields:{category:"expired"}}:t?.failure?{category:t.failure.category,step:t.failure.step,fields:t.failure.logFields()}:{category:"runtime",fields:{category:"runtime"}}}o(ot,"modelAuthFlowFailure");async function st(e,t,n){const r=await D(e);return r?(r.status==="pending"&&t>0&&!n?.aborted&&await new Promise(a=>{const i=o(()=>{clearTimeout(s),n?.removeEventListener("abort",i),r.waiters.delete(i),a()},"done"),s=setTimeout(i,t);n?.addEventListener("abort",i,{once:!0}),r.waiters.add(i)}),J(r)):g.has(e)?void 0:await Je(e,t,n)}o(st,"waitModelAuthFlow");const qe=1e3;async function Je(e,t,n){const r=await z(e);if(!r)return;const a=Date.now()+t;for(;!n?.aborted&&Date.now()<a&&(await new Promise(i=>setTimeout(i,Math.min(qe,Math.max(0,a-Date.now())))),!n?.aborted);)if(!await z(e)){const i=(await $(r.provider)).length>0;return{...r,status:i?"connected":"failed"}}return r}o(Je,"waitRowFlow");function ct(){for(const e of g.values())_(e,"failed","reset");g.clear(),S.clear()}o(ct,"resetModelAuthFlowsForTest");export{X as MODEL_AUTH_FLOW_TTL_S,ae as OPENAI_CHATGPT_API_PROVIDER,A as VendorSignInError,rt as cancelModelAuthFlow,it as completeModelAuthFlow,et as modelAuthApiProvider,ot as modelAuthFlowFailure,tt as modelAuthProvider,at as readModelAuthFlow,ct as resetModelAuthFlowsForTest,nt as startModelAuthFlow,st as waitModelAuthFlow};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.564",
3
+ "version": "0.3.565",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -144,7 +144,7 @@
144
144
  },
145
145
  "license": "UNLICENSED",
146
146
  "dependencies": {
147
- "@north-light/identity": "0.3.564",
147
+ "@north-light/identity": "0.3.565",
148
148
  "@earendil-works/pi-agent-core": "1.1.0",
149
149
  "@earendil-works/pi-ai": "1.1.0",
150
150
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.564",
3
+ "version": "0.3.565",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.564",
3
+ "version": "0.3.565",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.564",
9
+ "version": "0.3.565",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -24,7 +24,7 @@
24
24
  "@milkdown/preset-gfm": "^7.22.3",
25
25
  "@milkdown/prose": "^7.22.3",
26
26
  "@milkdown/utils": "^7.22.3",
27
- "@north-light/identity": "0.3.564",
27
+ "@north-light/identity": "0.3.565",
28
28
  "cron-parser": "^5.6.0",
29
29
  "esbuild": "^0.27.7",
30
30
  "jose": "^6.2.12",
@@ -12455,17 +12455,17 @@
12455
12455
  },
12456
12456
  "packages/crouter-api": {
12457
12457
  "name": "@north-light/api",
12458
- "version": "0.3.564",
12458
+ "version": "0.3.565",
12459
12459
  "license": "UNLICENSED"
12460
12460
  },
12461
12461
  "packages/crouter-env-docker": {
12462
12462
  "name": "@north-light/env-docker",
12463
- "version": "0.3.564",
12463
+ "version": "0.3.565",
12464
12464
  "license": "UNLICENSED"
12465
12465
  },
12466
12466
  "packages/crouter-identity": {
12467
12467
  "name": "@north-light/identity",
12468
- "version": "0.3.564",
12468
+ "version": "0.3.565",
12469
12469
  "license": "UNLICENSED",
12470
12470
  "dependencies": {
12471
12471
  "jose": "^6.2.1"
@@ -12473,21 +12473,21 @@
12473
12473
  },
12474
12474
  "packages/crouter-plugin": {
12475
12475
  "name": "@north-light/plugin",
12476
- "version": "0.3.564",
12476
+ "version": "0.3.565",
12477
12477
  "license": "UNLICENSED",
12478
12478
  "dependencies": {
12479
- "@north-light/api": "0.3.564",
12480
- "@north-light/identity": "0.3.564"
12479
+ "@north-light/api": "0.3.565",
12480
+ "@north-light/identity": "0.3.565"
12481
12481
  }
12482
12482
  },
12483
12483
  "packages/crouter-sdk": {
12484
12484
  "name": "@north-light/sdk",
12485
- "version": "0.3.564",
12485
+ "version": "0.3.565",
12486
12486
  "license": "UNLICENSED",
12487
12487
  "dependencies": {
12488
- "@north-light/api": "0.3.564",
12489
- "@north-light/identity": "0.3.564",
12490
- "@north-light/plugin": "0.3.564",
12488
+ "@north-light/api": "0.3.565",
12489
+ "@north-light/identity": "0.3.565",
12490
+ "@north-light/plugin": "0.3.565",
12491
12491
  "jose": "^6.2.1"
12492
12492
  },
12493
12493
  "bin": {