@north-light/cli 0.3.540 → 0.3.542
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/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/builtin-memory/crouter-sdk/release-notes.md +8 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +1 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +0 -4
- package/dist/clients/attach/viewer.js +406 -406
- package/dist/core/events/outbound.d.ts +21 -0
- package/dist/core/events/outbound.js +1 -0
- package/dist/core/grants/sync.js +1 -1
- package/dist/core/model-auth/flows.d.ts +20 -0
- package/dist/core/model-auth/flows.js +1 -1
- package/dist/core/providers/bundles.js +2 -2
- package/dist/core/providers/errors.d.ts +2 -0
- package/dist/core/providers/errors.js +1 -1
- package/dist/core/providers/mint.js +1 -1
- package/dist/core/subscription-state.d.ts +7 -1
- package/dist/core/subscription-state.js +4 -4
- package/dist/daemon/api/app-listener.js +13 -13
- package/dist/daemon/api/handlers/vendor-status.d.ts +14 -1
- package/dist/daemon/api/handlers/vendor-status.js +1 -1
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/vendor-signin.js +25 -25
- package/dist/daemon/run-notices.js +1 -1
- package/docs/sdk/release-notes.md +8 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EventField } from './types.js';
|
|
2
|
+
/** A failed outbound request: where it was going and which step of the caller's work it was. The thrown fetch error is the `cause`. */
|
|
3
|
+
export declare class OutboundError extends Error {
|
|
4
|
+
readonly step: string;
|
|
5
|
+
readonly url: string;
|
|
6
|
+
constructor(step: string, url: string, cause: unknown);
|
|
7
|
+
}
|
|
8
|
+
/** `fetch` whose thrown error (DNS, socket, timeout) is rethrown as an `OutboundError` naming the step and destination, with the original as its cause. */
|
|
9
|
+
export declare function outboundFetch(step: string, url: string, init?: RequestInit): Promise<Response>;
|
|
10
|
+
/** A request that completed with a failure status: names the step and destination, with the status as the cause. */
|
|
11
|
+
export declare function outboundRefusal(step: string, url: string, message: string): OutboundError;
|
|
12
|
+
/** Read a response body as JSON; a failed read (connection dropped after the headers) is an `OutboundError`, and a body that is not JSON is a `SyntaxError` as before. */
|
|
13
|
+
export declare function outboundJson(step: string, url: string, response: Response): Promise<unknown>;
|
|
14
|
+
/** Log fields for a caught outbound-request error: the step, the destination host and path (never the query string, headers or bodies), and the
|
|
15
|
+
* `Error.cause` chain, including the members of an `AggregateError` (what Node's `fetch` reports for a refused connection), each with its name, message,
|
|
16
|
+
* code, errno, syscall and hostname. The step and destination come from the first `OutboundError` met; `target` supplies them when the caller never wrapped
|
|
17
|
+
* the request. */
|
|
18
|
+
export declare function describeOutboundFailure(error: unknown, target?: {
|
|
19
|
+
step: string;
|
|
20
|
+
url?: string;
|
|
21
|
+
}): Record<string, EventField>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var d=Object.defineProperty;var u=(t,e)=>d(t,"name",{value:e,configurable:!0});class a extends Error{static{u(this,"OutboundError")}step;url;constructor(e,n,r){super(r instanceof Error?r.message:String(r),{cause:r}),this.step=e,this.url=n,this.name="OutboundError"}}async function w(t,e,n){try{return await fetch(e,n)}catch(r){throw new a(t,e,r)}}u(w,"outboundFetch");function b(t,e,n){return new a(t,e,new Error(n))}u(b,"outboundRefusal");async function E(t,e,n){let r;try{r=await n.text()}catch(o){throw new a(t,e,o)}return JSON.parse(r)}u(E,"outboundJson");const p=8,g=["code","errno","syscall","hostname"];function y(t){if(typeof t!="object"||t===null)return{name:"Error",message:String(t)};const e=t,n={name:typeof e.name=="string"?e.name:"Error",message:typeof e.message=="string"?e.message:String(t)};for(const r of g){const o=e[r];(typeof o=="string"||typeof o=="number")&&(n[r]=o)}return n}u(y,"link");function x(t,e){let n=e,r=!1;const o=[],f=new Set,i=[t];for(;i.length>0&&o.length<p;){const s=i.shift();if(s===void 0||f.has(s)||(f.add(s),s instanceof a?(r||(n={step:s.step,url:s.url}),r=!0):o.push(y(s)),typeof s!="object"||s===null))continue;const{cause:h,errors:l}=s;i.push(h),s instanceof AggregateError&&Array.isArray(l)&&i.push(...l.slice(0,p))}const c={cause_chain:o};if(n&&(c.step=n.step,n.url!==void 0)){const s=new URL(n.url);c.host=s.host,c.path=s.pathname}return c}u(x,"describeOutboundFailure");export{a as OutboundError,x as describeOutboundFailure,w as outboundFetch,E as outboundJson,b as outboundRefusal};
|
package/dist/core/grants/sync.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var S=Object.defineProperty;var r=(e,n)=>S(e,"name",{value:n,configurable:!0});import{createPrivateKey as E,randomUUID as j,sign as x}from"node:crypto";import{readFileSync as $}from"node:fs";import{join as T}from"node:path";import{crtrHome as A}from"../canvas/paths.js";import{isUnsandboxedMachine as g}from"../runtime/unsandboxed.js";import{emitEvent as p}from"../events/emit.js";import{describeOutboundFailure as N,outboundFetch as v,outboundJson as h,outboundRefusal as k}from"../events/outbound.js";import{allGrants as O,dependenciesOf as P,inferenceEndpointOf as R,replaceGrants as G}from"./mirror.js";import{onGrantSync as J}from"./on-sync.js";import{PersonUnresolvedError as B,personId as D,resolvePerson as F,retireLocalPerson as M,setPersonId as U}from"../canvas/person.js";const ie=300;function C(e,n){const t=Math.floor(Date.now()/1e3),o=Buffer.from(JSON.stringify({alg:"ES256",typ:"JWT",kid:e.kid})).toString("base64url"),u=Buffer.from(JSON.stringify({iss:e.client_id,sub:e.client_id,aud:n,iat:t,exp:t+300,jti:j()})).toString("base64url"),i=`${o}.${u}`,s=x("sha256",Buffer.from(i),{key:E({key:e.jwk,format:"jwk"}),dsaEncoding:"ieee-p1363"});return`${i}.${s.toString("base64url")}`}r(C,"assertion");function y(){const e=JSON.parse($(T(A(),"secrets","runtime-client.json"),"utf8"));if(!e.jwk||e.alg!=="ES256"||!e.kid||!e.client_id||!e.runtime||e.jwk.kid!==e.kid||e.jwk.kty!=="EC"||e.jwk.crv!=="P-256"||!e.jwk.d)throw new TypeError("invalid runtime client key");return e}r(y,"runtimeClient");function se(e=y()){return e.runtime.startsWith("runtime:")?e.runtime.slice(8):e.runtime}r(se,"runtimeId");function I(e){return`This machine was removed from your Northlight account. To use it again, create a join token at ${e.replace(/\/$/,"")}/account/machines, and run \`crtr sys join\` from a plain terminal.`}r(I,"machineRevokedMessage");class w extends Error{static{r(this,"MachineRevokedError")}constructor(n){super(I(n)),this.name="MachineRevokedError"}}async function L(e,n){const t=y(),o=`${e.replace(/\/$/,"")}/token`,u=new URLSearchParams({grant_type:"client_credentials",client_id:t.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:C(t,o),...n===void 0?{}:{audience:n}}),i=await v("directory_token",o,{method:"POST",body:u,signal:AbortSignal.timeout(1e4)});if(!i.ok)throw i.status===401&&g()&&await W(i)?new w(e):k("directory_token",o,`directory token request failed: ${i.status}`);const s=await h("directory_token",o,i);if(typeof s.access_token!="string")throw new TypeError("directory token response has no access_token");return{access_token:s.access_token,expires_at:q(s.access_token,s.expires_in)}}r(L,"mintDirectoryToken");async function W(e){try{return(await e.json()).error==="invalid_client"}catch{return!1}}r(W,"isInvalidClient");function q(e,n){if(typeof n=="number"&&Number.isFinite(n)&&n>0)return Date.now()+n*1e3;try{const t=JSON.parse(Buffer.from(e.split(".")[1]??"","base64url").toString("utf8"));if(typeof t.exp=="number")return t.exp*1e3}catch{}return Date.now()+3e5}r(q,"tokenExpiry");function z(e,n){if(!e||typeof e!="object")throw new TypeError("invalid grant sync response");const t=e,o=n.runtime.startsWith("runtime:")?n.runtime:`runtime:${n.runtime}`;if(!(t.runtime===o||g()&&typeof t.runtime=="string"&&t.runtime.startsWith("runtime:")&&t.grantee!=="app:terminal")||typeof t.id!="string"||typeof t.sub!="string"||typeof t.grantee!="string"||!Number.isInteger(t.rev)||!Array.isArray(t.scopes)||!Array.isArray(t.resolved_scopes)||!["active","revoked","removed"].includes(t.status??"")||typeof t.synced_at!="string")throw new TypeError("invalid grant sync response");const s=e.revisions?.find(a=>a.rev===t.rev)?.approved_at;if(typeof s!="string")throw new TypeError("grant revision has no approval time");const l=t.payer==="app"?"app":"user";return{...t,spend_cap:t.spend_cap??null,expires_at:t.expires_at??null,revoked_at:t.revoked_at??null,approved_at:s,payer:l,dependencies:P(t.dependencies),inference_endpoint:l==="app"?R(t.inference_endpoint):null,run_notice_url:typeof t.run_notice_url=="string"&&t.run_notice_url!==""?t.run_notice_url:null}}r(z,"responseGrant");async function H(){try{D();return}catch(t){if(!(t instanceof B))throw t}const e=await F();if(e===null)return;U(e.id);const n=await M();p({level:"info",event:"daemon.person.resolved",fields:{person_id:e.id,source:e.source}}),p({level:"info",event:"daemon.person.retired",fields:{person_id:n.person,retired:n.retired,columns:n.columns}})}r(H,"resolveLatePerson");let _;function ae(e){return _?_(e):Promise.resolve()}r(ae,"requestGrantSync");function ce(e,n=t=>{}){let t;const o=e.replace(/\/$/,""),u=r(async()=>{const a=y(),c=await L(o),f=await v("grant_sync",`${o}/v1/runtime/grants`,{headers:{Authorization:`Bearer ${c.access_token}`},signal:AbortSignal.timeout(1e4)});if(!f.ok)throw k("grant_sync",`${o}/v1/runtime/grants`,`directory grant sync failed: ${f.status}`);const d=await h("grant_sync",`${o}/v1/runtime/grants`,f);if(!Array.isArray(d.grants)||typeof d.synced_at!="string")throw new TypeError("invalid grant sync response");const b=await O();await G(d.grants.map(m=>z(m,a)),d.synced_at,o),p({level:"info",event:"daemon.grants.synced",fields:{grants:d.grants.length,synced_at:d.synced_at}}),await H();try{await J(b,n)}catch(m){p({level:"error",event:"daemon.grants.on_sync_failed",error:m})}},"run");let i;const s=r(()=>{const a=u().catch(c=>{throw c instanceof w?p({level:"error",event:"daemon.grants.machine_revoked",error:c}):p({level:"error",event:"daemon.grants.sync_failed",error:c,fields:N(c)}),c}).finally(()=>{t===a&&(t=void 0)});return t=a,a},"start"),l=r((a={})=>t?a.fresh?(i??=t.catch(()=>{}).then(()=>(i=void 0,t??s())),i):t:s(),"sync");return _=l,l}r(ce,"createGrantSync");export{ie as GRANT_SYNC_INTERVAL_S,w as MachineRevokedError,C as assertion,ce as createGrantSync,I as machineRevokedMessage,L as mintDirectoryToken,ae as requestGrantSync,y as runtimeClient,se as runtimeId};
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import { RunError } from '../runs/operations.js';
|
|
2
|
+
import type { EventField } from '../events/types.js';
|
|
1
3
|
import { type ManagedProviderId } from '../subscription-state.js';
|
|
2
4
|
export declare const MODEL_AUTH_FLOW_TTL_S = 900;
|
|
5
|
+
/** Why a sign-in failed, as the directory's signup page is told: our runtime could not reach the provider (`network`), the code expired (`expired`),
|
|
6
|
+
* the provider answered with a rejection (`refused`), or a lost flow or any other runtime fault (`runtime`). */
|
|
7
|
+
export type SignInFailureCategory = 'network' | 'expired' | 'refused' | 'runtime';
|
|
8
|
+
/** A failed request to a vendor during sign-in, with the step and destination for the log. */
|
|
9
|
+
export declare class VendorSignInError extends RunError {
|
|
10
|
+
readonly category: 'network' | 'refused';
|
|
11
|
+
readonly step: string;
|
|
12
|
+
readonly url: string;
|
|
13
|
+
readonly httpStatus?: number | undefined;
|
|
14
|
+
constructor(category: 'network' | 'refused', step: string, url: string, message: string, cause?: unknown, httpStatus?: number | undefined);
|
|
15
|
+
logFields(): Record<string, EventField>;
|
|
16
|
+
}
|
|
3
17
|
export type ModelAuthFlowStatus = 'pending' | 'connected' | 'failed' | 'expired';
|
|
4
18
|
export interface StartedFlow {
|
|
5
19
|
flow_id: string;
|
|
@@ -44,6 +58,12 @@ export interface FlowView {
|
|
|
44
58
|
}
|
|
45
59
|
/** A flow's current state, or undefined when it is unknown or long finished. */
|
|
46
60
|
export declare function readModelAuthFlow(flowId: string): FlowView | undefined;
|
|
61
|
+
/** How a finished flow failed, for the log and the redirect to the directory. An unknown or lost flow is a `runtime` failure. */
|
|
62
|
+
export declare function modelAuthFlowFailure(flowId: string): {
|
|
63
|
+
category: SignInFailureCategory;
|
|
64
|
+
step?: string;
|
|
65
|
+
fields: Record<string, EventField>;
|
|
66
|
+
};
|
|
47
67
|
/** Wait up to `waitMs` for a flow to leave `pending`; answers its state either way. */
|
|
48
68
|
export declare function waitModelAuthFlow(flowId: string, waitMs: number, signal?: AbortSignal): Promise<FlowView | undefined>;
|
|
49
69
|
/** Test seam: forget every flow (a daemon restart does this by construction). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var J=Object.defineProperty;var a=(e,t)=>J(e,"name",{value:t,configurable:!0});import{createHash as V,randomBytes as T,randomUUID as K}from"node:crypto";import{createServer as X}from"node:http";import{RunError as u}from"../runs/operations.js";import{ANTHROPIC_PROVIDER_ID as w,OPENAI_CHATGPT_PROVIDER_ID as m,OPENAI_CODEX_PROVIDER_ID as k,commitManagedLogin as Y,openAIDeviceId as Z,readSubscriptionPool as W}from"../subscription-state.js";const R=900,L="9d1c250a-e61b-44d9-88ed-5944d1962f5e",Q="https://claude.ai/oauth/authorize",ee="https://console.anthropic.com/oauth/code/callback",N={port:53692,path:"/callback",redirect:"http://localhost:53692/callback"},te="org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",I="app_EMoamEEZ73f0CkXaXp7hrann",ne="https://auth.openai.com/codex/device",U="https://auth.openai.com/deviceauth/callback",D={port:1455,path:"/auth/callback",redirect:"http://localhost:1455/auth/callback"},re="openid profile email offline_access",ie=900,G="https://api.openai.com/auth",oe="dynamic_agent_client",ae="crtr",se="https://auth.openai.com/api/accounts/authorize",H="https://api.openai.com/v1",b={port:1455,path:"/auth/callback",redirect:"http://127.0.0.1:1455/auth/callback"},E="chatgpt.tokens.use.direct",ce=`openid profile email offline_access resource.invoke ${E}`,de=180*1e3,ue=a(()=>process.env.CRTR_MODEL_AUTH_ANTHROPIC_TOKEN_URL||"https://platform.claude.com/v1/oauth/token","anthropicTokenUrl"),P=a(()=>(process.env.CRTR_MODEL_AUTH_OPENAI_BASE||"https://auth.openai.com").replace(/\/$/,""),"openaiBase"),_=new Map,y=new Map;function C(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}a(C,"base64url");function he(){const e=C(T(32));return{verifier:e,challenge:C(V("sha256").update(e).digest())}}a(he,"pkce");const M="openai-chatgpt";function $(e){if(e==="openai")return k;if(e===M)return m;if(e===w||e===k)return e;throw new u(400,"invalid_request","provider must be anthropic, openai-codex, or openai-chatgpt","provider")}a($,"managedProvider");function Ie(e){return e===m?M:e}a(Ie,"modelAuthApiProvider");function Ee(e){return $(e)}a(Ee,"modelAuthProvider");function f(e,t,n){if(e.status==="pending"){e.status=t,n!==void 0&&(e.message=n),clearTimeout(e.timer),e.abort.abort(),e.server?.close(),e.server?.closeIdleConnections(),y.get(e.provider)===e.id&&y.delete(e.provider);for(const i of e.waiters)i();e.waiters.clear(),setTimeout(()=>{_.get(e.id)===e&&_.delete(e.id)},6e4).unref()}}a(f,"settle");async function S(e,t){if(e.status!=="pending")throw new Error("the sign-in ended before the credential was written");await Y(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})}a(S,"writeCredential");async function j(e,t,n){const i=await fetch(ue(),{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({grant_type:"authorization_code",client_id:L,code:t,state:n,redirect_uri:e.redirectUri,code_verifier:e.verifier}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!i.ok)throw new Error(`Anthropic token exchange failed (${i.status})`);const r=await i.json();if(typeof r.access_token!="string"||typeof r.refresh_token!="string"||typeof r.expires_in!="number"||!Number.isFinite(r.expires_in))throw new Error("Anthropic token exchange returned no credential");await S(e,{access:r.access_token,refresh:r.refresh_token,expires:Date.now()+r.expires_in*1e3-300*1e3})}a(j,"exchangeAnthropic");function pe(e){const t=e.split(".")[1];let n;try{n=JSON.parse(Buffer.from(t??"","base64url").toString("utf8"))[G]?.chatgpt_account_id}catch{n=void 0}if(typeof n!="string"||n==="")throw new Error("OpenAI token carries no ChatGPT account id");return n}a(pe,"openaiAccountId");async function q(e,t,n,i){const r=await fetch(`${P()}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:I,code:t,code_verifier:n,redirect_uri:i}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!r.ok)throw new Error(`OpenAI token exchange failed (${r.status})`);const o=await r.json();if(typeof o.access_token!="string"||typeof o.refresh_token!="string"||typeof o.expires_in!="number")throw new Error("OpenAI token exchange returned no credential");await S(e,{access:o.access_token,refresh:o.refresh_token,expires:Date.now()+o.expires_in*1e3,accountId:pe(o.access_token)})}a(q,"exchangeOpenAI");function F(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}}a(F,"jwtPayload");function le(...e){for(const t of e){const n=F(t)?.[G];if(typeof n?.chatgpt_account_id=="string"&&n.chatgpt_account_id)return n.chatgpt_account_id}}a(le,"chatgptAccountId");function z(e,t){const n=e.searchParams.get("error");if(n)throw new u(400,"invalid_request",`ChatGPT authorization failed: ${n}`,"input");const i=e.searchParams.get("code");if(!i)throw new u(400,"invalid_request","the redirect URL has no authorization code","input");if(e.searchParams.get("state")!==t)throw new u(400,"invalid_request","the pasted redirect URL belongs to a different sign-in","state");const r=e.searchParams.get("client_id")?.trim();if(!r)throw new u(400,"invalid_request","the redirect URL carries no issued client id","input");return{code:i,clientId:r}}a(z,"chatgptCallback");async function B(e,t,n){const i=await fetch(`${P()}/api/accounts/oauth/token`,{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:H}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!i.ok)throw new Error(`ChatGPT token exchange failed (${i.status})`);const r=await i.json();if(typeof r.access_token!="string"||!r.access_token||typeof r.refresh_token!="string"||!r.refresh_token||typeof r.expires_in!="number"||!Number.isFinite(r.expires_in)||r.expires_in<=0||typeof r.id_token!="string"||!r.id_token)throw new Error("ChatGPT token exchange returned no credential");if(!(typeof r.scope=="string"?r.scope.trim().split(/\s+/):[]).includes(E))throw new Error(`ChatGPT sign-in did not grant ${E}`);const s=le(r.access_token,r.id_token),h=F(r.id_token)?.email;await S(e,{access:r.access_token,refresh:r.refresh_token,expires:Date.now()+r.expires_in*1e3-de,clientId:n,...s?{accountId:s}:{},...typeof h=="string"&&h?{email:h}:{}})}a(B,"exchangeChatGPT");function fe(e,t){return new Promise((n,i)=>{if(t.aborted){i(new Error("flow ended"));return}const r=a(()=>{clearTimeout(o),i(new Error("flow ended"))},"onAbort"),o=setTimeout(()=>{t.removeEventListener("abort",r),n()},e);t.addEventListener("abort",r,{once:!0})})}a(fe,"sleep");async function _e(e,t){const n=Math.min(e.expiresAt,Date.now()+ie*1e3);let i=Math.max(1e3,Math.floor(t.intervalS*1e3));try{for(;Date.now()<n&&(await fe(Math.min(i,Math.max(0,n-Date.now())),e.abort.signal),!(Date.now()>=n));){const r=await fetch(`${P()}/api/accounts/deviceauth/token`,{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(r.ok){const h=await r.json();if(typeof h.authorization_code!="string"||typeof h.code_verifier!="string")throw new Error("OpenAI device approval returned no code");await q(e,h.authorization_code,h.code_verifier,U),f(e,"connected");return}if(r.status===403||r.status===404)continue;const o=await r.text().catch(()=>"");let s;try{const h=JSON.parse(o).error;s=typeof h=="object"&&h!==null?h.code:h}catch{s=void 0}if(s!=="deviceauth_authorization_pending"){if(s==="slow_down"){i+=5e3;continue}throw new Error(`OpenAI device sign-in failed (${r.status})`)}}f(e,"expired","the OpenAI code expired")}catch(r){e.status==="pending"&&f(e,"failed",r instanceof Error?r.message:String(r))}}a(_e,"pollDevice");function O(e,t){const n=X((i,r)=>{const o=new URL(i.url??"/","http://localhost"),s=a((l,p)=>{r.writeHead(l,{"Content-Type":"text/plain; charset=utf-8",Connection:"close"}),r.end(p)},"reply");if(o.pathname!==t.path){s(404,"Not found.");return}const h=o.searchParams.get("code");if(o.searchParams.get("state")!==e.state||!h){s(400,"Sign-in could not be completed: state mismatch or missing code.");return}let d="";if(e.provider===m)try{d=z(o,e.state).clientId}catch(l){s(400,`Sign-in could not be completed: ${l instanceof Error?l.message:String(l)}.`);return}if(e.status!=="pending"||e.exchanging){s(409,"This sign-in has already finished.");return}e.exchanging=!0,(e.provider===w?j(e,h,e.state):e.provider===m?B(e,h,d):q(e,h,e.verifier,e.redirectUri)).then(()=>{s(200,"Signed in. You can close this window."),f(e,"connected")},l=>{s(502,"The vendor refused the sign-in."),f(e,"failed",l instanceof Error?l.message:String(l))})});return e.server=n,new Promise((i,r)=>{n.once("error",o=>r(new u(409,"conflict",`cannot listen on localhost:${t.port} for the vendor redirect (${o.code??o.message})`,"redirect"))),n.listen(t.port,"127.0.0.1",()=>i())})}a(O,"listenLoopback");async function Ce(e){const t=$(e.provider);if(e.redirect!==void 0&&e.redirect!=="loopback")throw new u(400,"invalid_request",'redirect must be "loopback" when present',"redirect");if(e.label!==void 0&&typeof e.label!="string")throw new u(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,i=y.get(t);if(i!==void 0){const p=_.get(i);p&&f(p,"failed","replaced by a newer sign-in")}const{verifier:r,challenge:o}=he(),s=e.redirect==="loopback",h=Date.now()+R*1e3,d={id:K(),provider:t,kind:s?"loopback":t===w?"code_page":t===m?"redirect_paste":"device",state:t===w?r:T(16).toString("hex"),verifier:r,redirectUri:t===w?s?N.redirect:ee:t===m?b.redirect:s?D.redirect:U,expiresAt:h,status:"pending",...e.jti?{jti:e.jti}:{},...n?{label:n}:{},lastRateLimitedAt:Math.max(0,...W(t).map(p=>p.lastRateLimitedAt)),abort:new AbortController,timer:setTimeout(()=>f(d,"expired","the sign-in expired"),R*1e3),waiters:new Set,exchanging:!1};d.timer.unref(),ge(d);const A=a(()=>new u(409,"conflict","a newer sign-in for this provider replaced this one","provider"),"replacedDuringStart"),l={flow_id:d.id,provider:t,expires_at:new Date(h).toISOString()};try{if(t===w){const c=new URLSearchParams;if(c.set("code","true"),c.set("client_id",L),c.set("response_type","code"),c.set("redirect_uri",d.redirectUri),c.set("scope",te),c.set("code_challenge",o),c.set("code_challenge_method","S256"),c.set("state",d.state),s&&await O(d,N),d.status!=="pending")throw A();return{...l,url:`${Q}?${c.toString()}`,instructions:s?"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===m){const c=new URL(se);if(c.search=new URLSearchParams({client_id:oe,agent_name_hint:ae,ext_agent_host_id:`urn:uuid:${Z()}`,response_type:"code",redirect_uri:d.redirectUri,resource:H,scope:ce,state:d.state,code_challenge:o,code_challenge_method:"S256",nonce:C(T(32))}).toString(),s&&await O(d,b),d.status!=="pending")throw A();return{...l,url:c.toString(),instructions:s?"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(s){const c=new URL("https://auth.openai.com/oauth/authorize");if(c.searchParams.set("response_type","code"),c.searchParams.set("client_id",I),c.searchParams.set("redirect_uri",d.redirectUri),c.searchParams.set("scope",re),c.searchParams.set("code_challenge",o),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("state",d.state),c.searchParams.set("id_token_add_organizations","true"),c.searchParams.set("codex_cli_simplified_flow","true"),c.searchParams.set("originator","pi"),await O(d,D),d.status!=="pending")throw A();return{...l,url:c.toString(),instructions:"Sign in to ChatGPT in your browser; this finishes by itself."}}const p=await fetch(`${P()}/api/accounts/deviceauth/usercode`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:I}),signal:AbortSignal.timeout(3e4)});if(!p.ok)throw new u(502,"upstream_refused",`OpenAI refused to issue a device code (${p.status})`);const g=await p.json(),v=typeof g.interval=="string"?Number(g.interval.trim()):g.interval;if(typeof g.device_auth_id!="string"||typeof g.user_code!="string"||typeof v!="number"||!Number.isFinite(v)||v<0)throw new u(502,"upstream_refused","OpenAI returned an invalid device code");if(d.status!=="pending")throw A();return _e(d,{deviceAuthId:g.device_auth_id,userCode:g.user_code,intervalS:v}),{...l,url:ne,user_code:g.user_code,instructions:"Open the link, sign in to ChatGPT, and enter the code. The code expires in 15 minutes."}}catch(p){throw f(d,"failed",p instanceof Error?p.message:String(p)),_.delete(d.id),p instanceof u?p:new u(502,"upstream_refused",`vendor sign-in could not start: ${p instanceof Error?p.message:String(p)}`)}}a(Ce,"startModelAuthFlow");function ge(e){_.set(e.id,e),y.set(e.provider,e.id)}a(ge,"register");function Se(e){const t=_.get(e);if(t)return f(t,"failed","cancelled"),x(t)}a(Se,"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,i]=t.split("#",2);return{code:n,state:i}}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}}a(me,"parseCodeInput");function we(e){const t=typeof e=="string"?_.get(e):void 0;if(!t||t.status!=="pending")throw new u(400,"invalid_request","no in-flight sign-in has this flow_id","flow_id");return t}a(we,"pendingFlow");async function Oe(e,t){const n=we(e);if(n.provider===m)return Ae(n,t);if(n.kind!=="code_page")throw new u(400,"invalid_request","this sign-in completes without a pasted code","flow_id");if(n.exchanging)throw new u(400,"invalid_request","this sign-in is already being completed","flow_id");if(typeof t!="string")throw new u(400,"invalid_request","input must be the code Claude shows","input");const{code:i,state:r}=me(t);if(!i)throw new u(400,"invalid_request","input must be the code Claude shows","input");if(r!==void 0&&r!==n.state)throw new u(400,"invalid_request","the pasted code belongs to a different sign-in","state");n.exchanging=!0;try{await j(n,i,r??n.state)}catch(o){throw f(n,"failed",o instanceof Error?o.message:String(o)),new u(502,"upstream_refused","Anthropic refused the code")}return f(n,"connected"),{flow_id:n.id,provider:n.provider,status:"connected"}}a(Oe,"completeModelAuthFlow");async function Ae(e,t){if(e.exchanging)throw new u(400,"invalid_request","this sign-in is already being completed","flow_id");const n=new URL(b.redirect);let i;try{i=typeof t=="string"?new URL(t.trim()):void 0}catch{i=void 0}if(!i||i.origin!==n.origin||i.pathname!==n.pathname)throw new u(400,"invalid_request",`input must be the full redirect URL starting with ${b.redirect}`,"input");const{code:r,clientId:o}=z(i,e.state);e.exchanging=!0;try{await B(e,r,o)}catch(s){throw f(e,"failed",s instanceof Error?s.message:String(s)),new u(502,"upstream_refused","OpenAI refused the ChatGPT sign-in")}return f(e,"connected"),{flow_id:e.id,provider:e.provider,status:"connected"}}a(Ae,"completeChatGPTFlow");function x(e){return{flow_id:e.id,provider:e.provider,status:e.status,...e.message?{message:e.message}:{},...e.jti?{jti:e.jti}:{}}}a(x,"view");function xe(e){const t=_.get(e);return t?x(t):void 0}a(xe,"readModelAuthFlow");async function ke(e,t,n){const i=_.get(e);if(i)return i.status==="pending"&&t>0&&!n?.aborted&&await new Promise(r=>{const o=a(()=>{clearTimeout(s),n?.removeEventListener("abort",o),i.waiters.delete(o),r()},"done"),s=setTimeout(o,t);n?.addEventListener("abort",o,{once:!0}),i.waiters.add(o)}),x(i)}a(ke,"waitModelAuthFlow");function Re(){for(const e of _.values())f(e,"failed","reset");_.clear(),y.clear()}a(Re,"resetModelAuthFlowsForTest");export{R as MODEL_AUTH_FLOW_TTL_S,M as OPENAI_CHATGPT_API_PROVIDER,Se as cancelModelAuthFlow,Oe as completeModelAuthFlow,Ie as modelAuthApiProvider,Ee as modelAuthProvider,xe as readModelAuthFlow,Re as resetModelAuthFlowsForTest,Ce as startModelAuthFlow,ke as waitModelAuthFlow};
|
|
1
|
+
var Z=Object.defineProperty;var s=(e,t)=>Z(e,"name",{value:t,configurable:!0});import{createHash as W,randomBytes as C,randomUUID as Q}from"node:crypto";import{createServer as ee}from"node:http";import{RunError as u}from"../runs/operations.js";import{describeOutboundFailure as te}from"../events/outbound.js";import{ANTHROPIC_PROVIDER_ID as y,OPENAI_CHATGPT_PROVIDER_ID as w,OPENAI_CODEX_PROVIDER_ID as D,commitManagedLogin as ne,openAIDeviceId as re,readSubscriptionPool as ie}from"../subscription-state.js";const $=900,G="9d1c250a-e61b-44d9-88ed-5944d1962f5e",oe="https://claude.ai/oauth/authorize",ae="https://console.anthropic.com/oauth/code/callback",H={port:53692,path:"/callback",redirect:"http://localhost:53692/callback"},se="org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload",E="app_EMoamEEZ73f0CkXaXp7hrann",ce="https://auth.openai.com/codex/device",M="https://auth.openai.com/deviceauth/callback",F={port:1455,path:"/auth/callback",redirect:"http://localhost:1455/auth/callback"},de="openid profile email offline_access",ue=900,q="https://api.openai.com/auth",he="dynamic_agent_client",pe="crtr",le="https://auth.openai.com/api/accounts/authorize",j="https://api.openai.com/v1",b={port:1455,path:"/auth/callback",redirect:"http://127.0.0.1:1455/auth/callback"},O="chatgpt.tokens.use.direct",fe=`openid profile email offline_access resource.invoke ${O}`,_e=180*1e3,ge=s(()=>process.env.CRTR_MODEL_AUTH_ANTHROPIC_TOKEN_URL||"https://platform.claude.com/v1/oauth/token","anthropicTokenUrl"),S=s(()=>(process.env.CRTR_MODEL_AUTH_OPENAI_BASE||"https://auth.openai.com").replace(/\/$/,""),"openaiBase");class A extends u{static{s(this,"VendorSignInError")}category;step;url;httpStatus;constructor(t,n,i,o,r,c){super(502,"upstream_refused",o),this.category=t,this.step=n,this.url=i,this.httpStatus=c,r!==void 0&&(this.cause=r)}logFields(){return{category:this.category,...this.httpStatus===void 0?{}:{http_status:this.httpStatus},...te(this.cause??this,{step:this.step,url:this.url})}}}async function P(e,t,n){try{return await fetch(t,n)}catch(i){throw new A("network",e,t,`could not reach ${new URL(t).host}${new URL(t).pathname}: ${i instanceof Error?i.message:String(i)}`,i)}}s(P,"vendorFetch");async function x(e,t,n){let i;try{i=await n.text()}catch(o){throw new A("network",e,t,`connection to ${new URL(t).host}${new URL(t).pathname} dropped while reading the answer: ${o instanceof Error?o.message:String(o)}`,o)}try{return JSON.parse(i)}catch{throw _(e,t,`${new URL(t).host}${new URL(t).pathname} answered with a body that is not JSON`,n.status)}}s(x,"vendorJson");const _=s((e,t,n,i)=>new A("refused",e,t,n,void 0,i),"refusal"),g=new Map,T=new Map;function R(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}s(R,"base64url");function me(){const e=R(C(32));return{verifier:e,challenge:R(W("sha256").update(e).digest())}}s(me,"pkce");const z="openai-chatgpt";function J(e){if(e==="openai")return D;if(e===z)return w;if(e===y||e===D)return e;throw new u(400,"invalid_request","provider must be anthropic, openai-codex, or openai-chatgpt","provider")}s(J,"managedProvider");function Re(e){return e===w?z:e}s(Re,"modelAuthApiProvider");function Le(e){return J(e)}s(Le,"modelAuthProvider");function f(e,t,n,i){if(e.status==="pending"){e.status=t,n!==void 0&&(e.message=n),i instanceof A&&(e.failure=i),clearTimeout(e.timer),e.abort.abort(),e.server?.close(),e.server?.closeIdleConnections(),T.get(e.provider)===e.id&&T.delete(e.provider);for(const o of e.waiters)o();e.waiters.clear(),setTimeout(()=>{g.get(e.id)===e&&g.delete(e.id)},6e4).unref()}}s(f,"settle");async function L(e,t){if(e.status!=="pending")throw new Error("the sign-in ended before the credential was written");await ne(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})}s(L,"writeCredential");async function B(e,t,n){const i=ge(),o=await P("token_exchange",i,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({grant_type:"authorization_code",client_id:G,code:t,state:n,redirect_uri:e.redirectUri,code_verifier:e.verifier}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!o.ok)throw _("token_exchange",i,`Anthropic token exchange failed (${o.status})`,o.status);const r=await x("token_exchange",i,o);if(typeof r.access_token!="string"||typeof r.refresh_token!="string"||typeof r.expires_in!="number"||!Number.isFinite(r.expires_in))throw _("token_exchange",i,"Anthropic token exchange returned no credential");await L(e,{access:r.access_token,refresh:r.refresh_token,expires:Date.now()+r.expires_in*1e3-300*1e3})}s(B,"exchangeAnthropic");function we(e){const t=e.split(".")[1];let n;try{n=JSON.parse(Buffer.from(t??"","base64url").toString("utf8"))[q]?.chatgpt_account_id}catch{n=void 0}if(typeof n!="string"||n==="")throw new Error("OpenAI token carries no ChatGPT account id");return n}s(we,"openaiAccountId");async function V(e,t,n,i){const o=`${S()}/oauth/token`,r=await P("token_exchange",o,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:E,code:t,code_verifier:n,redirect_uri:i}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!r.ok)throw _("token_exchange",o,`OpenAI token exchange failed (${r.status})`,r.status);const c=await x("token_exchange",o,r);if(typeof c.access_token!="string"||typeof c.refresh_token!="string"||typeof c.expires_in!="number")throw _("token_exchange",o,"OpenAI token exchange returned no credential");await L(e,{access:c.access_token,refresh:c.refresh_token,expires:Date.now()+c.expires_in*1e3,accountId:we(c.access_token)})}s(V,"exchangeOpenAI");function K(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}}s(K,"jwtPayload");function ye(...e){for(const t of e){const n=K(t)?.[q];if(typeof n?.chatgpt_account_id=="string"&&n.chatgpt_account_id)return n.chatgpt_account_id}}s(ye,"chatgptAccountId");function X(e,t){const n=e.searchParams.get("error");if(n)throw new u(400,"invalid_request",`ChatGPT authorization failed: ${n}`,"input");const i=e.searchParams.get("code");if(!i)throw new u(400,"invalid_request","the redirect URL has no authorization code","input");if(e.searchParams.get("state")!==t)throw new u(400,"invalid_request","the pasted redirect URL belongs to a different sign-in","state");const o=e.searchParams.get("client_id")?.trim();if(!o)throw new u(400,"invalid_request","the redirect URL carries no issued client id","input");return{code:i,clientId:o}}s(X,"chatgptCallback");async function Y(e,t,n){const i=`${S()}/api/accounts/oauth/token`,o=await P("token_exchange",i,{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:j}),signal:AbortSignal.any([e.abort.signal,AbortSignal.timeout(3e4)])});if(!o.ok)throw _("token_exchange",i,`ChatGPT token exchange failed (${o.status})`,o.status);const r=await x("token_exchange",i,o);if(typeof r.access_token!="string"||!r.access_token||typeof r.refresh_token!="string"||!r.refresh_token||typeof r.expires_in!="number"||!Number.isFinite(r.expires_in)||r.expires_in<=0||typeof r.id_token!="string"||!r.id_token)throw _("token_exchange",i,"ChatGPT token exchange returned no credential");if(!(typeof r.scope=="string"?r.scope.trim().split(/\s+/):[]).includes(O))throw _("token_exchange",i,`ChatGPT sign-in did not grant ${O}`);const p=ye(r.access_token,r.id_token),a=K(r.id_token)?.email;await L(e,{access:r.access_token,refresh:r.refresh_token,expires:Date.now()+r.expires_in*1e3-_e,clientId:n,...p?{accountId:p}:{},...typeof a=="string"&&a?{email:a}:{}})}s(Y,"exchangeChatGPT");function ve(e,t){return new Promise((n,i)=>{if(t.aborted){i(new Error("flow ended"));return}const o=s(()=>{clearTimeout(r),i(new Error("flow ended"))},"onAbort"),r=setTimeout(()=>{t.removeEventListener("abort",o),n()},e);t.addEventListener("abort",o,{once:!0})})}s(ve,"sleep");async function be(e,t){const n=Math.min(e.expiresAt,Date.now()+ue*1e3);let i=Math.max(1e3,Math.floor(t.intervalS*1e3));try{for(;Date.now()<n&&(await ve(Math.min(i,Math.max(0,n-Date.now())),e.abort.signal),!(Date.now()>=n));){const o=`${S()}/api/accounts/deviceauth/token`,r=await P("poll",o,{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(r.ok){const a=await x("poll",o,r);if(typeof a.authorization_code!="string"||typeof a.code_verifier!="string")throw _("poll",o,"OpenAI device approval returned no code");await V(e,a.authorization_code,a.code_verifier,M),f(e,"connected");return}if(r.status===403||r.status===404)continue;let c;try{c=await r.text()}catch(a){throw new A("network","poll",o,`connection to ${new URL(o).host}${new URL(o).pathname} dropped while reading the answer: ${a instanceof Error?a.message:String(a)}`,a)}let p;try{const a=JSON.parse(c).error;p=typeof a=="object"&&a!==null?a.code:a}catch{p=void 0}if(p!=="deviceauth_authorization_pending"){if(p==="slow_down"){i+=5e3;continue}throw _("poll",o,`OpenAI device sign-in failed (${r.status})`,r.status)}}f(e,"expired","the OpenAI code expired")}catch(o){e.status==="pending"&&f(e,"failed",o instanceof Error?o.message:String(o),o)}}s(be,"pollDevice");function U(e,t){const n=ee((i,o)=>{const r=new URL(i.url??"/","http://localhost"),c=s((l,h)=>{o.writeHead(l,{"Content-Type":"text/plain; charset=utf-8",Connection:"close"}),o.end(h)},"reply");if(r.pathname!==t.path){c(404,"Not found.");return}const p=r.searchParams.get("code");if(r.searchParams.get("state")!==e.state||!p){c(400,"Sign-in could not be completed: state mismatch or missing code.");return}let a="";if(e.provider===w)try{a=X(r,e.state).clientId}catch(l){c(400,`Sign-in could not be completed: ${l instanceof Error?l.message:String(l)}.`);return}if(e.status!=="pending"||e.exchanging){c(409,"This sign-in has already finished.");return}e.exchanging=!0,(e.provider===y?B(e,p,e.state):e.provider===w?Y(e,p,a):V(e,p,e.verifier,e.redirectUri)).then(()=>{c(200,"Signed in. You can close this window."),f(e,"connected")},l=>{c(502,"The vendor refused the sign-in."),f(e,"failed",l instanceof Error?l.message:String(l),l)})});return e.server=n,new Promise((i,o)=>{n.once("error",r=>o(new u(409,"conflict",`cannot listen on localhost:${t.port} for the vendor redirect (${r.code??r.message})`,"redirect"))),n.listen(t.port,"127.0.0.1",()=>i())})}s(U,"listenLoopback");async function Ue(e){const t=J(e.provider);if(e.redirect!==void 0&&e.redirect!=="loopback")throw new u(400,"invalid_request",'redirect must be "loopback" when present',"redirect");if(e.label!==void 0&&typeof e.label!="string")throw new u(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,i=T.get(t);if(i!==void 0){const h=g.get(i);h&&f(h,"failed","replaced by a newer sign-in")}const{verifier:o,challenge:r}=me(),c=e.redirect==="loopback",p=Date.now()+$*1e3,a={id:Q(),provider:t,kind:c?"loopback":t===y?"code_page":t===w?"redirect_paste":"device",state:t===y?o:C(16).toString("hex"),verifier:o,redirectUri:t===y?c?H.redirect:ae:t===w?b.redirect:c?F.redirect:M,expiresAt:p,status:"pending",...e.jti?{jti:e.jti}:{},...n?{label:n}:{},lastRateLimitedAt:Math.max(0,...ie(t).map(h=>h.lastRateLimitedAt)),abort:new AbortController,timer:setTimeout(()=>f(a,"expired","the sign-in expired"),$*1e3),waiters:new Set,exchanging:!1};a.timer.unref(),Ae(a);const v=s(()=>new u(409,"conflict","a newer sign-in for this provider replaced this one","provider"),"replacedDuringStart"),l={flow_id:a.id,provider:t,expires_at:new Date(p).toISOString()};try{if(t===y){const d=new URLSearchParams;if(d.set("code","true"),d.set("client_id",G),d.set("response_type","code"),d.set("redirect_uri",a.redirectUri),d.set("scope",se),d.set("code_challenge",r),d.set("code_challenge_method","S256"),d.set("state",a.state),c&&await U(a,H),a.status!=="pending")throw v();return{...l,url:`${oe}?${d.toString()}`,instructions:c?"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 d=new URL(le);if(d.search=new URLSearchParams({client_id:he,agent_name_hint:pe,ext_agent_host_id:`urn:uuid:${re()}`,response_type:"code",redirect_uri:a.redirectUri,resource:j,scope:fe,state:a.state,code_challenge:r,code_challenge_method:"S256",nonce:R(C(32))}).toString(),c&&await U(a,b),a.status!=="pending")throw v();return{...l,url:d.toString(),instructions:c?"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(c){const d=new URL("https://auth.openai.com/oauth/authorize");if(d.searchParams.set("response_type","code"),d.searchParams.set("client_id",E),d.searchParams.set("redirect_uri",a.redirectUri),d.searchParams.set("scope",de),d.searchParams.set("code_challenge",r),d.searchParams.set("code_challenge_method","S256"),d.searchParams.set("state",a.state),d.searchParams.set("id_token_add_organizations","true"),d.searchParams.set("codex_cli_simplified_flow","true"),d.searchParams.set("originator","pi"),await U(a,F),a.status!=="pending")throw v();return{...l,url:d.toString(),instructions:"Sign in to ChatGPT in your browser; this finishes by itself."}}const h=`${S()}/api/accounts/deviceauth/usercode`,I=await P("device_code_start",h,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_id:E}),signal:AbortSignal.timeout(3e4)});if(!I.ok)throw _("device_code_start",h,`OpenAI refused to issue a device code (${I.status})`,I.status);const m=await x("device_code_start",h,I),k=typeof m.interval=="string"?Number(m.interval.trim()):m.interval;if(typeof m.device_auth_id!="string"||typeof m.user_code!="string"||typeof k!="number"||!Number.isFinite(k)||k<0)throw _("device_code_start",h,"OpenAI returned an invalid device code");if(a.status!=="pending")throw v();return be(a,{deviceAuthId:m.device_auth_id,userCode:m.user_code,intervalS:k}),{...l,url:ce,user_code:m.user_code,instructions:"Open the link, sign in to ChatGPT, and enter the code. The code expires in 15 minutes."}}catch(h){throw f(a,"failed",h instanceof Error?h.message:String(h),h),g.delete(a.id),h instanceof u?h:new u(502,"upstream_refused",`vendor sign-in could not start: ${h instanceof Error?h.message:String(h)}`)}}s(Ue,"startModelAuthFlow");function Ae(e){g.set(e.id,e),T.set(e.provider,e.id)}s(Ae,"register");function Ne(e){const t=g.get(e);if(t)return f(t,"failed","cancelled"),N(t)}s(Ne,"cancelModelAuthFlow");function Pe(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,i]=t.split("#",2);return{code:n,state:i}}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}}s(Pe,"parseCodeInput");function xe(e){const t=typeof e=="string"?g.get(e):void 0;if(!t||t.status!=="pending")throw new u(400,"invalid_request","no in-flight sign-in has this flow_id","flow_id");return t}s(xe,"pendingFlow");async function De(e,t){const n=xe(e);if(n.provider===w)return Te(n,t);if(n.kind!=="code_page")throw new u(400,"invalid_request","this sign-in completes without a pasted code","flow_id");if(n.exchanging)throw new u(400,"invalid_request","this sign-in is already being completed","flow_id");if(typeof t!="string")throw new u(400,"invalid_request","input must be the code Claude shows","input");const{code:i,state:o}=Pe(t);if(!i)throw new u(400,"invalid_request","input must be the code Claude shows","input");if(o!==void 0&&o!==n.state)throw new u(400,"invalid_request","the pasted code belongs to a different sign-in","state");n.exchanging=!0;try{await B(n,i,o??n.state)}catch(r){throw f(n,"failed",r instanceof Error?r.message:String(r),r),new u(502,"upstream_refused","Anthropic refused the code")}return f(n,"connected"),{flow_id:n.id,provider:n.provider,status:"connected"}}s(De,"completeModelAuthFlow");async function Te(e,t){if(e.exchanging)throw new u(400,"invalid_request","this sign-in is already being completed","flow_id");const n=new URL(b.redirect);let i;try{i=typeof t=="string"?new URL(t.trim()):void 0}catch{i=void 0}if(!i||i.origin!==n.origin||i.pathname!==n.pathname)throw new u(400,"invalid_request",`input must be the full redirect URL starting with ${b.redirect}`,"input");const{code:o,clientId:r}=X(i,e.state);e.exchanging=!0;try{await Y(e,o,r)}catch(c){throw f(e,"failed",c instanceof Error?c.message:String(c),c),new u(502,"upstream_refused","OpenAI refused the ChatGPT sign-in")}return f(e,"connected"),{flow_id:e.id,provider:e.provider,status:"connected"}}s(Te,"completeChatGPTFlow");function N(e){return{flow_id:e.id,provider:e.provider,status:e.status,...e.message?{message:e.message}:{},...e.jti?{jti:e.jti}:{}}}s(N,"view");function $e(e){const t=g.get(e);return t?N(t):void 0}s($e,"readModelAuthFlow");function Ge(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"}}}s(Ge,"modelAuthFlowFailure");async function He(e,t,n){const i=g.get(e);if(i)return i.status==="pending"&&t>0&&!n?.aborted&&await new Promise(o=>{const r=s(()=>{clearTimeout(c),n?.removeEventListener("abort",r),i.waiters.delete(r),o()},"done"),c=setTimeout(r,t);n?.addEventListener("abort",r,{once:!0}),i.waiters.add(r)}),N(i)}s(He,"waitModelAuthFlow");function Me(){for(const e of g.values())f(e,"failed","reset");g.clear(),T.clear()}s(Me,"resetModelAuthFlowsForTest");export{$ as MODEL_AUTH_FLOW_TTL_S,z as OPENAI_CHATGPT_API_PROVIDER,A as VendorSignInError,Ne as cancelModelAuthFlow,De as completeModelAuthFlow,Re as modelAuthApiProvider,Ge as modelAuthFlowFailure,Le as modelAuthProvider,$e as readModelAuthFlow,Me as resetModelAuthFlowsForTest,Ue as startModelAuthFlow,He as waitModelAuthFlow};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
`),$(
|
|
1
|
+
var ue=Object.defineProperty;var a=(e,n)=>ue(e,"name",{value:n,configurable:!0});import{existsSync as g,readdirSync as W,readFileSync as S,rmSync as $,statSync as k}from"node:fs";import{join as d}from"node:path";import{validateCommandManifest as de}from"../../api/command-manifest/manifest.js";import{crtrHome as z}from"../canvas/paths.js";import{withCanvasWrite as G}from"../canvas/db.js";import{bundleVersion as ce,isInstalledGeneration as fe,recordCheckedGeneration as le}from"../command-plugins/bundle.js";import{readConfig as q,readState as pe,updateConfig as me,updateState as ve}from"../config.js";import{emitEvent as m}from"../events/emit.js";import{describeOutboundFailure as he,outboundFetch as we,outboundRefusal as V,OutboundError as ge}from"../events/outbound.js";import{withExclusiveDirectoryLockAsync as Z}from"../exclusive-lock.js";import{atomicWriteText as x,ensureDir as T,pathExists as $e}from"../fs-utils.js";import{reconcilePackages as L}from"../graph/package-reconcile.js";import{readPluginManifest as C}from"../manifest.js";import{bundleSwapLockPath as J}from"../plugin-swap-lock.js";import{pluginsDir as ye,userScopeRoot as K}from"../scope.js";import{listInstalledPlugins as be}from"../resolver.js";import{localGrants as j}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const A="[A-Za-z0-9_-]{1,64}",_e=new RegExp(`^(${A}):(${A})$`),Q=new RegExp(`^${A}$`),Pe=3e4,X=3e4;function Y(e){const n=new Set;for(const r of e)if(r.status==="active")for(const{scope:t}of r.resolved_scopes){const o=_e.exec(t);o&&o[1]!=="crtr"&&n.add(o[1])}return[...n].sort()}a(Y,"providerIdsFromGrants");function v(e){return d(ye("user"),e)}a(v,"pluginRoot");function R(){return d(z(),"providers")}a(R,"providersRoot");function ee(e,n){return d(R(),e,`v${n}`)}a(ee,"majorDir");function B(e){if(!Q.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}a(B,"assertId");function E(){const e=q("user").runtime?.issuer;if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e.replace(/\/+$/,"")}a(E,"directoryBase");async function ne(e,n){const r=`${E()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await we("bundle_fetch",r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(Pe)})}catch(i){return{status:"failed",message:`could not reach ${r}: ${i instanceof Error?i.message:String(i)}`,cause:i}}if(t.status===304){if(n.ifNoneMatch===void 0){const i=`HTTP 304 from ${r} without a conditional request`;return{status:"failed",message:i,cause:V("bundle_fetch",r,i)}}return{status:304}}if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300){await t.body?.cancel().catch(()=>{});{const i=`HTTP ${t.status} from ${r}`;return{status:"failed",message:i,cause:V("bundle_fetch",r,i)}}}let o;try{o=new Uint8Array(await t.arrayBuffer())}catch(i){return{status:"failed",message:`reading ${r} failed: ${i instanceof Error?i.message:String(i)}`,cause:new ge("bundle_fetch",r,i)}}const s=t.headers.get("etag");return{status:200,raw:o,...s!==null?{etag:s}:{}}}a(ne,"fetchProviderBundle");const I=new WeakSet;class M extends Error{static{a(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function re(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(o=>r[o]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}a(re,"wrongBundle");function D(e,n,r){m({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof M?{issues:r.issues}:{}},error:r})}a(D,"logInvalid");function te(e,n,r){m({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r instanceof Error?r.message:String(r),...he(r instanceof Error?r.cause:void 0)}})}a(te,"logFailure");async function oe(){return import("../../commands/pkg/plugin-manage.js")}a(oe,"pluginManage");const y=new Map,b=a(e=>`${z()}\0${e}`,"installKey");function Se(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}a(Se,"publicInstallCause");function Ee(e){if(h(e)===void 0)return y.get(b(e))??{status:"installing"}}a(Ee,"providerInstallState");function Ie(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${Ee(e)?.error??"bundle installation pending"}`)}a(Ie,"unavailableProviderError");function h(e){const n=C(v(e));if(!(n?.provider===void 0||n.transport?.kind!=="http"&&n.transport?.kind!=="machine")&&$e(d(v(e),"commands.json"))){try{if(k(d(v(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:n.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:n.transport.endpoint}}}}a(h,"installedManifest");async function N(e,n,r=!1){const t=K();h(e)===void 0&&y.set(b(e),{status:"installing"});try{return T(d(t,"tmp")),await Z(J(t,e),async()=>{const o=v(e),s=C(o);if(s!==null&&s.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const i=h(e)!==void 0;if(i&&r)return"unchanged";const f=i&&n?pe("user").plugins[e]?.etag:void 0,u=await ne(e,f!==void 0?{ifNoneMatch:f}:{});if(u.status===304)return"unchanged";if(u.status===404||u.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${u.message})`,{param:"provider"});if(u.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${u.message}`,{cause:u.cause});const l=ce(u.raw);if(i&&fe(e,"user",o,l))return le("user",e,u.etag,{swapped:!1}),"unchanged";const{installPluginArchive:w}=await oe();try{await w(e,u.raw,{scope:"user",scopeRootPath:t,root:o,version:l,etag:u.etag,enable:!i,bundle:{endpoint:`${E()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:E()},accept:a(p=>re(e,void 0,p),"accept")})}catch(p){D(e,void 0,p);const U=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw I.add(U),U}try{await G(async p=>await L(p))}catch(p){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return m({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:l,replaced:i}}),y.delete(b(e)),i?"updated":"installed"},{timeoutMs:X,timeoutError:a(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(o){throw h(e)===void 0&&y.set(b(e),{status:"install_failed",error:Se(o)}),o instanceof c?o:(m({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:o}),Ie(e))}}a(N,"refreshInstalled");async function en(e){B(e),h(e)===void 0&&await N(e,!1,!0)}a(en,"ensureProviderBundle");async function nn(e,n){return B(e),n.version!==void 0&&h(e)?.provider.major!==n.version?O(e,n.version,n.conditional):N(e,n.conditional)}a(nn,"refreshProviderBundle");async function ae(e,n,r){const{validatePluginArchive:t}=await oe(),o=await t(r);if(o.bundle===void 0)throw new M(`provider "${e}" major ${n} bundle is invalid: ${o.issues.map(i=>i.message).join("; ")}`,o.issues);const s=re(e,n,o.bundle);if(s!==void 0)throw new M(`provider "${e}" major ${n} bundle is invalid: ${s}`,[]);if(o.bundle.transport===void 0)throw new M(`provider "${e}" major ${n} bundle declares no transport`,[]);return o.bundle}a(ae,"validateArchive");function Me(e){T(e),x(d(e,"retired"),`${new Date().toISOString()}
|
|
2
|
+
`),$(d(e,"bundle.tar"),{force:!0}),$(d(e,"etag"),{force:!0})}a(Me,"retire");function ie(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}a(ie,"retiredError");async function O(e,n,r){const t=ee(e,n),o=d(t,"bundle.tar"),s=g(o),i=s&&r&&g(d(t,"etag"))?S(d(t,"etag"),"utf8"):void 0,f=await ne(e,{version:n,...i!==void 0?{ifNoneMatch:i}:{}});if(f.status===304)return"unchanged";if(f.status===410)throw Me(t),m({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),ie(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`,{cause:f.cause});try{await ae(e,n,f.raw)}catch(l){D(e,n,l);const w=new c(s?"provider_unavailable":"not_found",l instanceof Error?l.message:String(l),{param:"version"});throw I.add(w),w}T(t),$(d(t,"retired"),{force:!0});const u=s&&Buffer.from(f.raw).equals(S(o));return u||x(o,f.raw),f.etag!==void 0?x(d(t,"etag"),f.etag):$(d(t,"etag"),{force:!0}),u?"unchanged":s?"updated":"installed"}a(O,"refreshPinned");function ke(){const e=[],n=R();if(!g(n))return e;for(const r of W(n)){if(!Q.test(r))continue;let t;try{t=W(d(n,r))}catch{continue}for(const o of t){const s=/^v([1-9][0-9]*)$/.exec(o);s&&g(d(n,r,o,"bundle.tar"))&&e.push([r,Number(s[1])])}}return e}a(ke,"cachedMajors");function xe(e){const n=de(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}a(xe,"readCommands");const _=new Map,H=new Map;function Te(e,n){const r=k(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,o=H.get(e);if(o?.stamp===t)return o.commands;const s=xe(JSON.parse(S(n,"utf8")));return H.set(e,{stamp:t,commands:s}),s}a(Te,"installedCommands");async function rn(e,n){B(e);const r=h(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const l=v(e),w=Te(e,d(l,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:w,transport:r.transport,source:"installed",root:l}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=ee(e,n);if(g(d(t,"retired")))throw ie(e,n);const o=d(t,"bundle.tar");g(o)||await O(e,n,!1);const s=`${e}/${n}`,i=k(o),f=`${i.mtimeMs}:${i.size}`;let u=_.get(s)?.stamp===f?_.get(s).bundle:void 0;if(u===void 0){try{u=await ae(e,n,new Uint8Array(S(o)))}catch(l){throw D(e,n,l),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}_.set(s,{stamp:f,bundle:u})}return{id:e,major:n,provider:u.provider,commands:u.commands,transport:u.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:u.transport.endpoint},source:"pinned"}}a(rn,"resolveProviderBundle");let P=null,F=!1;function Ce(e,n){return e?.provider?.id===n&&e.bundle?.endpoint===`${E()}/providers/${encodeURIComponent(n)}/plugin`}a(Ce,"isDirectoryBundle");async function se(e){return Y(await j()).includes(e)}a(se,"grantNames");async function je(e){await Z(J(K(),e),async()=>{if(!(await se(e)||!Ce(C(v(e)),e))){$(v(e),{recursive:!0,force:!0}),me("user",n=>{delete n.plugins[e]}),ve("user",n=>{delete n.plugins[e]}),$(d(R(),e),{recursive:!0,force:!0}),H.delete(e),y.delete(b(e));for(const n of[..._.keys()])n.startsWith(`${e}/`)&&_.delete(n);try{await G(async n=>await L(n))}catch(n){m({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:n})}m({level:"info",event:"daemon.providers.bundle_uninstalled",fields:{provider:e}})}},{timeoutMs:X,timeoutError:a(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}a(je,"uninstallProvider");async function Ae(){if(!(await j()).every(e=>e.status!=="active")){for(const e of be("user"))if(!(e.manifest.provider===void 0||await se(e.name)))try{await je(e.name)}catch(n){m({level:"error",event:"daemon.providers.bundle_uninstall_failed",fields:{provider:e.name},error:n})}}}a(Ae,"uninstallDisconnected");async function Re(){if(!q("user").runtime?.issuer)return;const e=Y(await j());for(const n of e)try{await N(n,!0)}catch(r){r instanceof Object&&I.has(r)||te(n,void 0,r)}await Ae();for(const[n,r]of ke())try{await O(n,r,!0)}catch(t){if(t instanceof c&&t.code==="provider_version_retired"||t instanceof Object&&I.has(t))continue;te(n,r,t)}}a(Re,"syncOnce");function tn(){return P!==null?(F=!0,P):(P=(async()=>{try{do F=!1,await Re();while(F)}finally{P=null}})(),P)}a(tn,"syncProviderBundles");export{en as ensureProviderBundle,Y as providerIdsFromGrants,Ee as providerInstallState,nn as refreshProviderBundle,rn as resolveProviderBundle,tn as syncProviderBundles,Ie as unavailableProviderError};
|
|
@@ -72,6 +72,8 @@ export interface ProviderErrorOptions {
|
|
|
72
72
|
retry_after_s?: number;
|
|
73
73
|
question_id?: string;
|
|
74
74
|
run_id?: string;
|
|
75
|
+
/** The error behind this one, kept for the log. */
|
|
76
|
+
cause?: unknown;
|
|
75
77
|
}
|
|
76
78
|
export declare class ProviderError extends RunError {
|
|
77
79
|
readonly origin: ErrorOrigin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var m=Object.defineProperty;var _=(t,i)=>m(t,"name",{value:i,configurable:!0});import{RunError as d}from"../runs/operations.js";import{errorCodes as r}from"../../api/error-codes.js";const g={provider_error:{type:"invalid_request_error",status:400,retryable:!1},provider_rate_limited:r.provider_rate_limited,provider_unavailable:r.provider_unavailable,provider_refused:r.provider_refused,provider_not_connected:r.provider_not_connected,provider_version_retired:r.provider_version_retired,storage_unavailable:r.storage_unavailable,approval_pending:{...r.approval_pending,user_action:"answer_question"},user_denied:r.user_denied},b={unauthorized:r.unauthorized,grant_revoked:r.grant_revoked,machine_revoked:r.machine_revoked,scope_missing:r.scope_missing,permission_error:r.permission_error,invalid_request:r.invalid_request,not_found:r.not_found,not_implemented:r.not_implemented,runtime_starting:r.runtime_starting,server_error:r.server_error,provider_bad_response:r.provider_bad_response,invalid_argument:r.invalid_argument,machine_required:r.machine_required,device_offline:r.device_offline,device_timeout:r.device_timeout,device_disconnected:r.device_disconnected,peer_version_too_old:r.peer_version_too_old,outcome_unknown:{type:"invalid_request_error",status:409,retryable:!1},wrong_site:{type:"invalid_request_error",status:409,retryable:!1},site_signed_out:{type:"invalid_request_error",status:409,retryable:!1},js_exception:{type:"invalid_request_error",status:409,retryable:!1},lib_broken:{type:"server_error",status:502,retryable:!1}};function a(t,i){return g[t]??b[t]??(Object.prototype.hasOwnProperty.call(r,t)?r[t]:void 0)??{type:i===401?"authentication_error":i===403?"permission_error":i===429?"rate_limit_error":i>=500?"server_error":"invalid_request_error",status:i,retryable:i>=500}}_(a,"codeRow");class u extends d{static{_(this,"ProviderError")}origin;type;retry_after_s;question_id;user_action;run_id;constructor(i,o,n={}){const e=a(i,n.status??500);super(n.status??e.status,i,o,n.param,n.details,n.scope,e.retryable),this.type=e.type,this.origin=n.origin??"runtime",n.retry_after_s!==void 0&&(this.retry_after_s=n.retry_after_s),n.question_id!==void 0&&(this.question_id=n.question_id),n.run_id!==void 0&&(this.run_id=n.run_id),e.user_action!==void 0&&(this.user_action=e.user_action)}}function R(t){return new u("scope_missing",`missing scope ${t}`,{scope:t})}_(R,"scopeMissing");function q(t){if(!(t instanceof Error))return null;const{status:i,code:o,details:n,retryable:e}=t;return typeof i!="number"||typeof o!="string"||i<400||i>599?null:new d(i,o,t.message,void 0,n,void 0,typeof e=="boolean"?e:a(o,i).retryable)}_(q,"shaped");function h(t){if(t.code==="unauthorized"||t.code==="grant_revoked")return"reconnect";if(t.code==="scope_missing"&&t.scope)return`grant_scope:${t.scope}`}_(h,"directoryAction");function w(t,i,o,n){const e=t instanceof d?t:q(t)??new u("server_error","provider request failed"),s=e instanceof u?e:null,v=a(e.code,e.status),c=s?.run_id??n,p=s?.user_action??(e.code==="cap_exhausted"?"raise_cap":o?h(e):void 0),l=e.code==="cap_exhausted"?e.details?.reset_at:void 0,y={code:e.code,message:e.message,...e.details===void 0?{}:{details:e.details},type:s?.type??v.type,...e.param?{param:e.param}:{},origin:s?.origin??"runtime",retryable:e.retryable,...s?.retry_after_s===void 0?{}:{retry_after_s:s.retry_after_s},...typeof l=="string"?{reset_at:l}:{},request_id:i,...c?{run_id:c}:{},...s?.question_id?{question_id:s.question_id}:{},...p?{user_action:p}:{},...e.scope?{scope:e.scope}:{}},f={"Request-Id":i};return s?.retry_after_s!==void 0&&(f["Retry-After"]=String(s.retry_after_s)),{status:e.status,body:{error:y},headers:f}}_(w,"providerErrorBody");export{g as PROVIDER_CODES,u as ProviderError,a as codeRow,w as providerErrorBody,R as scopeMissing};
|
|
1
|
+
var m=Object.defineProperty;var _=(t,i)=>m(t,"name",{value:i,configurable:!0});import{RunError as d}from"../runs/operations.js";import{errorCodes as r}from"../../api/error-codes.js";const g={provider_error:{type:"invalid_request_error",status:400,retryable:!1},provider_rate_limited:r.provider_rate_limited,provider_unavailable:r.provider_unavailable,provider_refused:r.provider_refused,provider_not_connected:r.provider_not_connected,provider_version_retired:r.provider_version_retired,storage_unavailable:r.storage_unavailable,approval_pending:{...r.approval_pending,user_action:"answer_question"},user_denied:r.user_denied},b={unauthorized:r.unauthorized,grant_revoked:r.grant_revoked,machine_revoked:r.machine_revoked,scope_missing:r.scope_missing,permission_error:r.permission_error,invalid_request:r.invalid_request,not_found:r.not_found,not_implemented:r.not_implemented,runtime_starting:r.runtime_starting,server_error:r.server_error,provider_bad_response:r.provider_bad_response,invalid_argument:r.invalid_argument,machine_required:r.machine_required,device_offline:r.device_offline,device_timeout:r.device_timeout,device_disconnected:r.device_disconnected,peer_version_too_old:r.peer_version_too_old,outcome_unknown:{type:"invalid_request_error",status:409,retryable:!1},wrong_site:{type:"invalid_request_error",status:409,retryable:!1},site_signed_out:{type:"invalid_request_error",status:409,retryable:!1},js_exception:{type:"invalid_request_error",status:409,retryable:!1},lib_broken:{type:"server_error",status:502,retryable:!1}};function a(t,i){return g[t]??b[t]??(Object.prototype.hasOwnProperty.call(r,t)?r[t]:void 0)??{type:i===401?"authentication_error":i===403?"permission_error":i===429?"rate_limit_error":i>=500?"server_error":"invalid_request_error",status:i,retryable:i>=500}}_(a,"codeRow");class u extends d{static{_(this,"ProviderError")}origin;type;retry_after_s;question_id;user_action;run_id;constructor(i,o,n={}){const e=a(i,n.status??500);super(n.status??e.status,i,o,n.param,n.details,n.scope,e.retryable),this.type=e.type,this.origin=n.origin??"runtime",n.cause!==void 0&&(this.cause=n.cause),n.retry_after_s!==void 0&&(this.retry_after_s=n.retry_after_s),n.question_id!==void 0&&(this.question_id=n.question_id),n.run_id!==void 0&&(this.run_id=n.run_id),e.user_action!==void 0&&(this.user_action=e.user_action)}}function R(t){return new u("scope_missing",`missing scope ${t}`,{scope:t})}_(R,"scopeMissing");function q(t){if(!(t instanceof Error))return null;const{status:i,code:o,details:n,retryable:e}=t;return typeof i!="number"||typeof o!="string"||i<400||i>599?null:new d(i,o,t.message,void 0,n,void 0,typeof e=="boolean"?e:a(o,i).retryable)}_(q,"shaped");function h(t){if(t.code==="unauthorized"||t.code==="grant_revoked")return"reconnect";if(t.code==="scope_missing"&&t.scope)return`grant_scope:${t.scope}`}_(h,"directoryAction");function w(t,i,o,n){const e=t instanceof d?t:q(t)??new u("server_error","provider request failed"),s=e instanceof u?e:null,v=a(e.code,e.status),c=s?.run_id??n,p=s?.user_action??(e.code==="cap_exhausted"?"raise_cap":o?h(e):void 0),l=e.code==="cap_exhausted"?e.details?.reset_at:void 0,y={code:e.code,message:e.message,...e.details===void 0?{}:{details:e.details},type:s?.type??v.type,...e.param?{param:e.param}:{},origin:s?.origin??"runtime",retryable:e.retryable,...s?.retry_after_s===void 0?{}:{retry_after_s:s.retry_after_s},...typeof l=="string"?{reset_at:l}:{},request_id:i,...c?{run_id:c}:{},...s?.question_id?{question_id:s.question_id}:{},...p?{user_action:p}:{},...e.scope?{scope:e.scope}:{}},f={"Request-Id":i};return s?.retry_after_s!==void 0&&(f["Retry-After"]=String(s.retry_after_s)),{status:e.status,body:{error:y},headers:f}}_(w,"providerErrorBody");export{g as PROVIDER_CODES,u as ProviderError,a as codeRow,w as providerErrorBody,R as scopeMissing};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var E=Object.defineProperty;var c=(r,e)=>E(r,"name",{value:e,configurable:!0});import{createHash as M}from"node:crypto";import{readConfig as j}from"../config.js";import{emitEvent as S}from"../events/emit.js";import{OutboundError as A}from"../events/outbound.js";import{grantSyncStateFor as N}from"../grants/mirror.js";import{assertion as O,machineRevokedMessage as P,requestGrantSync as I,runtimeClient as L}from"../grants/sync.js";import{isUnsandboxedMachine as D}from"../runtime/unsandboxed.js";import{ProviderError as h}from"./errors.js";import{cacheKey as F,cachedToken as G,discardEpoch as U,storeToken as C}from"./token-cache.js";import{discardFile as le}from"./token-cache.js";const J=1e4,Y=[300,900];function B(r){const e=r.issuer??j("user").runtime?.issuer;if(!e)throw new h("not_found","provider calls need a directory-connected runtime");return e.replace(/\/$/,"")}c(B,"issuerOf");function g(r,e,t){return new h(r,e,{origin:"directory",details:t})}c(g,"directoryError");function H(r,e,t,i){const n=typeof e.error=="string"?e.error:null,s=typeof e.error_description=="string"?e.error_description:void 0,o={directory_status:r,...n?{directory_error:n}:{},...s?{directory_description:s}:{}},d=s?`: ${s}`:"";switch(n){case"invalid_client":return D()?{error:g("machine_revoked",P(i),o),grantRefusal:!1}:{error:g("server_error",`the directory refused the outbound token request (${n})${d}`,o),grantRefusal:!1};case"invalid_request":return{error:g("server_error",`the directory refused the outbound token request (${n})${d}`,o),grantRefusal:!1};case"invalid_target":return{error:g("not_found",`the directory knows no provider at ${t.audience}${d}`,o),grantRefusal:!1};case"invalid_grant":return{error:g("grant_revoked",`the directory has no active grant for ${t.grantee}${d}`,o),grantRefusal:!0};case"provider_not_connected":return{error:g("provider_not_connected",s??"The user has not connected this provider.",o),grantRefusal:!1};case"invalid_scope":{const a=typeof e.scope=="string"&&e.scope?e.scope:t.scope.length===1?t.scope[0]:void 0,l=a?`missing scope ${a}`:`the grant does not cover ${t.scope.join(" ")}`;return{error:new h("scope_missing",l,{origin:"directory",details:o,...a?{scope:a}:{}}),grantRefusal:!0}}default:return{error:g("server_error",`the directory token endpoint answered ${r}${n?` ${n}`:""}${d}`,o),grantRefusal:!1}}}c(H,"refusal");function K(r){try{return JSON.parse(Buffer.from(r.split(".")[1]??"","base64url").toString("utf8"))}catch{return{}}}c(K,"claims");function z(r){const e=r.grant_rev,t=r.grant_status;return typeof e!="number"||!Number.isInteger(e)||e<0||typeof t!="string"?null:{rev:e,status:t}}c(z,"refusedGrantState");function Q(r,e){const t=c(i=>i.status==="active"?0:1,"phase");return r.rev>e.rev||r.rev===e.rev&&t(r)>t(e)}c(Q,"newer");async function V(r,e,t){if(!e)return;const i=await N(r.grantee,r.sub)??{rev:0,status:"removed"};if(!Q(e,i))return;const n=t.syncGrants??I;Promise.resolve().then(()=>n({fresh:!0})).catch(()=>{})}c(V,"syncIfNewer");async function W(r,e={}){const t=B(e),i=`${t}/token`,n=L(),s=c(async()=>{const u=new URLSearchParams({grant_type:"client_credentials",client_id:n.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:O(n,i),audience:r.audience,sub:r.sub,grantee:r.grantee,scope:[...new Set(r.scope)].sort().join(" "),initiating_app:r.initiating_app}),p=AbortSignal.timeout(J),w=e.signal?AbortSignal.any([e.signal,p]):p,$=await(e.fetch??fetch)(i,{method:"POST",body:u,signal:w,headers:{Accept:"application/json"}});return{response:$,text:await $.text()}},"attempt");let o,d;try{for(let u=0;;u++){({response:o,text:d}=await s());const p=Y[u];if(p===void 0||![502,503,504].includes(o.status))break;await new Promise(w=>setTimeout(w,p))}}catch(u){throw e.signal?.aborted?e.signal.reason:new h("server_error",`the directory token endpoint could not be reached: ${u instanceof Error?u.message:String(u)}`,{origin:"directory",cause:new A("directory_token",i,u)})}let a=null;try{a=d?JSON.parse(d):null}catch{a=null}const l=a&&typeof a=="object"&&!Array.isArray(a)?a:{};if(!o.ok){const{error:u,grantRefusal:p}=H(o.status,l,r,t);throw u.code==="machine_revoked"&&S({level:"error",event:"daemon.grants.machine_revoked",fields:{audience:r.audience}}),S({level:"info",event:"daemon.providers.mint_refused",fields:{audience:r.audience,grantee:r.grantee,code:u.code,directory_error:typeof l.error=="string"?l.error:null,status:o.status}}),p&&await V(r,z(l),e),u}const m=l.access_token;if(typeof m!="string"||!m)throw new h("server_error","the directory token response has no access_token",{origin:"directory"});const f=K(m),x=Math.floor((e.now?.()??Date.now())/1e3),y=l.expires_in,_=typeof y=="number"&&Number.isFinite(y)&&y>0?x+Math.floor(y):null,v=typeof f.exp=="number"?f.exp:null,b=_!==null&&v!==null?Math.min(_,v):_??v;if(b===null)throw new h("server_error","the directory token response names no expiry",{origin:"directory"});const R=typeof f.jti=="string"&&f.jti?f.jti:M("sha256").update(m).digest("hex"),T=typeof f.payer=="string"&&f.payer?f.payer:r.sub;return{access_token:m,jti:R,exp:b,payer:T}}c(W,"mintOutboundToken");const k=new Map;async function ce(r,e={}){const t=F(r),i=e.now??Date.now,n=G(t,i());if(n)return{...n,key:t};let s=k.get(t);if(!s){const d=U(t);s=W(r,{...e,signal:void 0}).then(a=>(C(t,a,d),a)).finally(()=>{k.delete(t)}),s.catch(()=>{}),k.set(t,s)}return{...e.signal?await X(s,e.signal):await s,key:t}}c(ce,"outboundToken");function X(r,e){return e.aborted?Promise.reject(e.reason):new Promise((t,i)=>{const n=c(()=>i(e.reason),"onAbort");e.addEventListener("abort",n,{once:!0}),r.then(t,i).finally(()=>e.removeEventListener("abort",n))})}c(X,"abortable");export{Y as MINT_RETRY_DELAYS_MS,J as MINT_TIMEOUT_MS,le as discardFile,W as mintOutboundToken,ce as outboundToken};
|
|
@@ -77,18 +77,24 @@ export declare class DuplicateSubscriptionError extends Error {
|
|
|
77
77
|
constructor(message: string);
|
|
78
78
|
}
|
|
79
79
|
export declare function readSubscriptionPool(provider: ManagedProviderId): SubscriptionCredential[];
|
|
80
|
+
/** The pool with each account's row id: the stable handle a caller outside the process removes an account by. */
|
|
81
|
+
export declare function readSubscriptionPoolRows(provider: ManagedProviderId): Array<{
|
|
82
|
+
id: string;
|
|
83
|
+
entry: SubscriptionCredential;
|
|
84
|
+
}>;
|
|
80
85
|
export declare function writeSubscriptionPool(provider: ManagedProviderId, next: SubscriptionCredential[]): Promise<SubscriptionCredential[]>;
|
|
81
86
|
export declare function addSubscription(provider: ManagedProviderId, entry: SubscriptionCredential): Promise<SubscriptionCredential[]>;
|
|
82
87
|
export declare function upsertSubscription(provider: ManagedProviderId, entry: SubscriptionCredential): Promise<SubscriptionCredential[]>;
|
|
83
88
|
export declare function upsertSubscriptionWithUniqueAccount(provider: ManagedProviderId, entry: SubscriptionCredential): Promise<SubscriptionCredential[]>;
|
|
84
89
|
export declare function renameSubscription(provider: ManagedProviderId, ref: string, nextLabel: string): Promise<SubscriptionCredential[]>;
|
|
85
90
|
export declare function removeManagedAccount(provider: ManagedProviderId, ref: string): Promise<SubscriptionCredential[]>;
|
|
91
|
+
/** Remove the account whose row id is `id` (see `readSubscriptionPoolRows`); throws when no account has it. */
|
|
92
|
+
export declare function removeManagedAccountById(provider: ManagedProviderId, id: string): Promise<SubscriptionCredential[]>;
|
|
86
93
|
export declare function removeSubscription(provider: ManagedProviderId, ref: string): Promise<SubscriptionCredential[]>;
|
|
87
94
|
export declare function removeAllManagedAccounts(provider: ManagedProviderId): Promise<number>;
|
|
88
95
|
export declare function removeManagedProvider(provider: ManagedProviderId, label?: string): Promise<SubscriptionCredential[]>;
|
|
89
96
|
export declare function promoteSubscription(provider: ManagedProviderId, ref: string): Promise<SubscriptionCredential[]>;
|
|
90
97
|
export declare function markSubscriptionAttempt(provider: ManagedProviderId, ref: string, attemptAt?: number): Promise<SubscriptionCredential[]>;
|
|
91
|
-
export declare function markSubscriptionAuthenticationFailed(provider: ManagedProviderId, ref: string, attempted: Pick<SubscriptionCredential, 'refresh' | 'access'>, retryAfterMs: number, attemptAt?: number): Promise<SubscriptionCredential[]>;
|
|
92
98
|
/** Record a 429 on an account. `scope` defaults to the whole account; a model scope cools only that family on this account, and a 429 older than the newest one already recorded for the same scope is ignored. */
|
|
93
99
|
export declare function markSubscriptionRateLimited(provider: ManagedProviderId, ref: string, retryAfterMs: number, attemptAt?: number, rateLimitedAt?: number, scope?: RateLimitScope): Promise<SubscriptionCredential[]>;
|
|
94
100
|
/** A call on `modelId` succeeded: clear the account-wide cooldown and auth failure, and that model family's cooldown, unless a newer 429 for the same scope landed after `attemptAt` began. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}
|
|
3
|
-
`,{mode:384}),n}
|
|
4
|
-
`))}})}o(L,"locked");function k(t,n,e){g(t,"INSERT INTO managed_accounts(provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure) VALUES(?,?,?,?,?,?,?,?,?)").run(n,e.id,e.label,e.labelKey,e.position,e.lastAttemptAt,e.lastRateLimitedAt,e.rateLimitedUntil,e.authFailure??null)}o(k,"insert");function x(t,n){g(t,"UPDATE managed_accounts SET label=?,label_key=?,position=?,last_attempt_at=?,last_rate_limited_at=?,rate_limited_until=?,auth_failure=? WHERE provider_id=? AND id=?").run(n.label,n.labelKey,n.position,n.lastAttemptAt,n.lastRateLimitedAt,n.rateLimitedUntil,n.authFailure??null,n.providerId,n.id)}o(x,"update");function it(t,n,e,a,i,r){g(t,"INSERT INTO managed_account_model_cooldowns(provider_id,account_id,family,rate_limited_until,last_rate_limited_at) VALUES(?,?,?,?,?) ON CONFLICT(provider_id,account_id,family) DO UPDATE SET rate_limited_until=excluded.rate_limited_until,last_rate_limited_at=excluded.last_rate_limited_at").run(n,e,a,i,r)}o(it,"upsertModelCooldown");function C(t,n,e){e===void 0?g(t,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=?").run(n):g(t,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=? AND account_id=?").run(n,e)}o(C,"deleteModelCooldowns");function E(t,n){return B(t,n).map(({entry:e})=>e)}o(E,"publicPool");function $(t,n){try{n()}catch(e){throw new Error(`managed ${b(t)} credential file was published, but its SQL membership update failed; SQL membership remains authoritative: ${e instanceof Error?e.message:String(e)}`,{cause:e})}}o($,"writeMembershipAfterCredentialPublish");async function Zt(t,n){return L(t,async(e,a,i)=>{const r=n.map(u=>N(t,u)),s=r.map((u,l)=>({entry:u,id:a[l]?.row.id??U()})),c={};for(const{entry:u,id:l}of s)c[l]=I(u);return i({version:1,accounts:c}),$(t,()=>A(h(),u=>{const l=T(u,t);g(u,"DELETE FROM managed_accounts WHERE provider_id=?").run(t),g(u,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=?").run(t),s.forEach(({entry:m,id:d},y)=>{k(u,t,{providerId:t,id:d,label:m.label,labelKey:f(m.label),position:y,lastAttemptAt:m.lastAttemptAt,lastRateLimitedAt:m.lastRateLimitedAt,rateLimitedUntil:m.rateLimitedUntil,...m.authFailure?{authFailure:m.authFailure}:{}});for(const[F,M]of Object.entries(m.modelCooldowns??{}))it(u,t,d,F,M,l.get(d)?.find(p=>p.family===F)?.lastRateLimitedAt??m.lastRateLimitedAt)})})),r})}o(Zt,"writeSubscriptionPool");function O(t,n){const e=n.trim().toLowerCase(),a=Number.parseInt(e,10);return Number.isFinite(a)&&String(a)===e&&a>=1&&a<=t.length?a-1:t.findIndex(({entry:i})=>f(i.label)===e)}o(O,"resolve");async function It(t,n){const e=N(t,n);return L(t,async(a,i,r)=>{if(i.some(({entry:l})=>f(l.label)===f(e.label)))throw new R(`${b(t)} subscription already exists: ${e.label}`);const s=_(e),c=i.find(({entry:l})=>_(l)===s);if(s&&c)throw new R(`This ${b(t)} account is already added as \u201C${c.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const u=U();return a.accounts[u]=I(e),r(a),$(t,()=>A(h(),l=>k(l,t,{providerId:t,id:u,label:e.label,labelKey:f(e.label),position:i.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil,...e.authFailure?{authFailure:e.authFailure}:{}}))),E(t,a)})}o(It,"addSubscription");async function vt(t,n){const e=N(t,n);return D(t).find(i=>f(i.label)===f(e.label))?$t(t,e):It(t,e)}o(vt,"upsertSubscription");async function $t(t,n){const e=N(t,n);return L(t,async(a,i,r)=>{const s=i.findIndex(({entry:d})=>f(d.label)===f(e.label)),c=i[s],u=i.find(({entry:d},y)=>y!==s&&_(d)===_(e));if(_(e)&&u)throw new R(`This ${b(t)} account is already added as \u201C${u.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);if(!c){const d=U();return a.accounts[d]=I(e),r(a),$(t,()=>A(h(),y=>k(y,t,{providerId:t,id:d,label:e.label,labelKey:f(e.label),position:i.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil}))),E(t,a)}const l=c.row,m=e.lastRateLimitedAt>=l.lastRateLimitedAt;return a.accounts[l.id]=I(e),r(a),$(t,()=>A(h(),d=>{x(d,{...l,label:e.label,labelKey:f(e.label),lastAttemptAt:Math.max(l.lastAttemptAt,e.lastAttemptAt),lastRateLimitedAt:m?e.lastRateLimitedAt:l.lastRateLimitedAt,rateLimitedUntil:m?e.rateLimitedUntil:l.rateLimitedUntil}),et(c.entry,e)&&C(d,t,l.id)})),E(t,a)})}o($t,"upsertSubscriptionWithUniqueAccount");async function te(t,n,e){return L(t,async(a,i)=>{const r=O(i,n);if(r<0)throw new Error(`Unknown ${b(t)} subscription: ${n}`);const s=e.trim();if(!s)throw new Error(`${b(t)} subscription label cannot be empty`);if(i.some((c,u)=>u!==r&&f(c.entry.label)===f(s)))throw new R(`${b(t)} subscription already exists: ${s}`);return A(h(),c=>x(c,{...i[r].row,label:s,labelKey:f(s)})),E(t,a)})}o(te,"renameSubscription");async function q(t,n){return L(t,async(e,a,i)=>{const r=O(a,n);if(r<0)throw new Error(`Unknown ${b(t)} subscription: ${n}`);const s=a[r].row;return A(h(),c=>{g(c,"DELETE FROM managed_accounts WHERE provider_id=? AND id=?").run(t,s.id),C(c,t,s.id)}),delete e.accounts[s.id],i(e),E(t,e)})}o(q,"remove");async function ee(t,n){return q(t,n)}o(ee,"removeManagedAccount");async function ne(t,n){return q(t,n)}o(ne,"removeSubscription");async function Ft(t){return L(t,async(n,e,a)=>{A(h(),i=>{g(i,"DELETE FROM managed_accounts WHERE provider_id=?").run(t),C(i,t)});for(const{row:i}of e)delete n.accounts[i.id];return a(n),e.length})}o(Ft,"removeAllManagedAccounts");async function ae(t,n){return n===void 0?(await Ft(t),[]):q(t,n)}o(ae,"removeManagedProvider");async function ie(t,n){return L(t,async(e,a)=>{const i=O(a,n);if(i<0)throw new Error(`Unknown ${b(t)} subscription: ${n}`);return A(h(),r=>{[a[i],...a.filter((c,u)=>u!==i)].forEach((c,u)=>x(r,{...c.row,position:u}))}),E(t,e)})}o(ie,"promoteSubscription");async function rt(t,n,e){return A(h(),a=>{const i=P(a,t,n);if(!i)throw new Error(`Unknown ${b(t)} subscription: ${n}`);const r=e(i);r&&x(a,r)}),D(t)}o(rt,"mutateHealth");async function re(t,n,e=Date.now()){return rt(t,n,a=>({...a,lastAttemptAt:Math.max(a.lastAttemptAt,e)}))}o(re,"markSubscriptionAttempt");async function oe(t,n,e,a,i=Date.now()){return L(t,async(r,s)=>{const c=s.find(({entry:u})=>f(u.label)===f(n));if(!c)throw new Error(`Unknown ${b(t)} subscription: ${n}`);return c.entry.refresh!==e.refresh||c.entry.access!==e.access||A(h(),u=>x(u,{...c.row,authFailure:"invalid_grant",lastAttemptAt:Math.max(c.row.lastAttemptAt,i),rateLimitedUntil:Math.max(c.row.rateLimitedUntil,i+Math.max(0,a))})),E(t,r)})}o(oe,"markSubscriptionAuthenticationFailed");async function se(t,n,e,a=Date.now(),i=Date.now(),r={kind:"account"}){return e=Math.min(Math.max(0,e),H),r.kind==="account"?rt(t,n,s=>i<s.lastRateLimitedAt?null:{...s,lastAttemptAt:Math.max(s.lastAttemptAt,a),lastRateLimitedAt:i,rateLimitedUntil:i+e}):(A(h(),s=>{const c=P(s,t,n);if(!c)throw new Error(`Unknown ${b(t)} subscription: ${n}`);x(s,{...c,lastAttemptAt:Math.max(c.lastAttemptAt,a)});const u=T(s,t).get(c.id)?.find(l=>l.family===r.family);u&&i<u.lastRateLimitedAt||it(s,t,c.id,r.family,i+e,i)}),D(t))}o(se,"markSubscriptionRateLimited");async function ce(t,n,e=Date.now(),a){const i=j(a);return A(h(),r=>{const s=P(r,t,n);if(!s)throw new Error(`Unknown ${b(t)} subscription: ${n}`);if(e>=s.lastRateLimitedAt&&x(r,{...s,authFailure:void 0,lastAttemptAt:Math.max(s.lastAttemptAt,e),rateLimitedUntil:0}),i===void 0)return;const c=T(r,t).get(s.id)?.find(u=>u.family===i);c&&e>=c.lastRateLimitedAt&&g(r,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=? AND account_id=? AND family=?").run(t,s.id,i)}),D(t)}o(ce,"markSubscriptionSuccess");async function le(t,n,e){return L(t,async(a,i,r)=>{const s=O(i,n);if(s<0)throw new Error(`${b(t)} subscription "${n}" no longer exists`);const c=i[s];if(c.entry.expires>Date.now())return c.entry;const u=await e(c.entry.refresh,c.entry.clientId),l=nt(at(t,c.entry.label,u),u.clientId??c.entry.clientId);return a.accounts[c.row.id]={...a.accounts[c.row.id],...l},r(a),{...c.entry,...l}})}o(le,"refreshSubscriptionCredential");function St(t){const n=t,e=[n?.errorMessage,n?.message,t instanceof Error?t.message:""].filter(i=>typeof i=="string").join(" ");return(typeof n?.status=="number"?n.status:Number(e.match(/\bstatus[=:]\s*(\d+)/i)?.[1]))===400&&/invalid_grant|refresh token .*invalid|refresh token .*not found/i.test(e)}o(St,"isInvalidRefreshTokenError");async function ue(t,n,e){return L(t,async(a,i,r)=>{const s=new Set;let c;for(;s.size<i.length;){const u=n(i.filter(({entry:m})=>!s.has(m.label)).map(({entry:m})=>m));if(!u)break;const l=i.find(({entry:m})=>m.label===u.label);s.add(l.entry.label);try{const m=await e(l.entry);if(m){const d=nt(at(t,l.entry.label,m),m.clientId??l.entry.clientId);return a.accounts[l.row.id]={...a.accounts[l.row.id],...d},r(a),{...l.entry,...d}}return l.entry}catch(m){if(!St(m))throw m;c=m;const d=Date.now();A(h(),y=>x(y,{...l.row,authFailure:"invalid_grant",lastAttemptAt:Math.max(l.row.lastAttemptAt,d),rateLimitedUntil:Math.max(l.row.rateLimitedUntil,d+yt)}))}}if(c)throw c})}o(ue,"modifyElectedSubscription");async function Ut(t,n){const e=n.accountId?.trim(),a=n.email?.trim();if(t!==W)return{...e?{accountId:e}:{},...a?{email:a}:{}};try{const i=await fetch("https://api.anthropic.com/api/oauth/profile",{headers:{Authorization:`Bearer ${n.access}`,Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!i.ok)return e?{accountId:e}:{};const r=await i.json(),s=typeof r.account?.uuid=="string"&&r.account.uuid.trim()?r.account.uuid.trim():void 0,c=typeof r.account?.email=="string"&&r.account.email.trim()?r.account.email.trim():void 0;return{...s?{accountId:s}:{},...c?{email:c}:{}}}catch{return e?{accountId:e}:{}}}o(Ut,"enrichManagedLoginIdentity");async function de(t,n,e){const a=await Ut(t,e);return L(t,async(i,r,s)=>{const c=e.autoLabel?void 0:n?.trim(),u=_({refresh:e.refresh,accountId:a.accountId}),l=(c?r.find(({entry:p})=>f(p.label)===f(c)):void 0)??r.find(({entry:p})=>_(p)===u);let m=l?.entry.label??c;if(!m&&e.autoLabel){const p=a.email||n?.trim()||`Account ${r.length+1}`;m=p;for(let S=2;r.some(({entry:w})=>f(w.label)===f(m));S++)m=`${p} (${S})`}if(!m)throw new Error(`${b(t)} subscription label cannot be empty`);const d=N(t,{label:m,refresh:e.refresh,access:e.access,expires:e.expires,...a.accountId?{accountId:a.accountId}:{},...e.clientId?{clientId:e.clientId}:{},rateLimitedUntil:0,lastAttemptAt:Date.now(),lastRateLimitedAt:e.lastRateLimitedAt});if(!l){if(r.some(({entry:w})=>f(w.label)===f(d.label)))throw new R(`${b(t)} subscription already exists: ${d.label}`);const p=r.find(({entry:w})=>_(w)===_(d));if(_(d)&&p)throw new R(`This ${b(t)} account is already added as \u201C${p.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const S=U();return i.accounts[S]=I(d),s(i),$(t,()=>A(h(),w=>k(w,t,{providerId:t,id:S,label:d.label,labelKey:f(d.label),position:r.length,lastAttemptAt:d.lastAttemptAt,lastRateLimitedAt:d.lastRateLimitedAt,rateLimitedUntil:d.rateLimitedUntil}))),E(t,i)}const y=l.row,F=r.find(({entry:p})=>p.label!==l.entry.label&&_(p)===_(d));if(_(d)&&F)throw new R(`This ${b(t)} account is already added as \u201C${F.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);i.accounts[y.id]=I(d),s(i);const M=d.lastRateLimitedAt>=y.lastRateLimitedAt;return $(t,()=>A(h(),p=>{x(p,{...y,authFailure:void 0,lastAttemptAt:Math.max(y.lastAttemptAt,d.lastAttemptAt),lastRateLimitedAt:M?d.lastRateLimitedAt:y.lastRateLimitedAt,rateLimitedUntil:M?0:y.rateLimitedUntil}),et(l.entry,d)&&C(p,t,y.id)})),E(t,i)})}o(de,"commitManagedLogin");function Nt(t,n=Date.now()){if(!t?.trim())return;const e=Number(t.trim());if(Number.isFinite(e))return Math.max(0,Math.round(e*1e3));const a=Date.parse(t);return Number.isFinite(a)?Math.max(0,a-n):void 0}o(Nt,"parseRetryAfterHeader");function Dt(t){if(!t)return;const n=o(a=>t instanceof Headers?t.get(a)??void 0:Object.entries(t).find(([i])=>i.toLowerCase()===a)?.[1],"get"),e=n("retry-after-ms");return e!==void 0&&e.trim()!==""&&Number.isFinite(Number(e))?Math.max(0,Number(e)):Nt(n("retry-after"))}o(Dt,"retryAfterMsFromHeaders");function me(t){if(!t||typeof t!="object"||!("headers"in t))return;const n=t.headers;if(!(!(n instanceof Headers)&&(!n||typeof n!="object"||Array.isArray(n))))return Dt(n)}o(me,"retryAfterMsFromError");function Mt(){const t=dt();if(t)return t.rotation;const n=g(ht(bt(h())),"SELECT default_fallback_strength,revert_when_available FROM rotation_settings WHERE singleton=1").get();return{defaultFallbackStrength:Y(n?.default_fallback_strength)??Q,revertWhenAvailable:n?.revert_when_available===void 0||n.revert_when_available===null?!0:!!n.revert_when_available}}o(Mt,"readRotationConfig");function fe(t){const n=Mt(),e={defaultFallbackStrength:Y(t.defaultFallbackStrength)??n.defaultFallbackStrength??Q,revertWhenAvailable:t.revertWhenAvailable??n.revertWhenAvailable??!0};return A(h(),a=>g(a,"INSERT INTO rotation_settings(singleton,default_fallback_strength,revert_when_available) VALUES(1,?,?) ON CONFLICT(singleton) DO UPDATE SET default_fallback_strength=excluded.default_fallback_strength,revert_when_available=excluded.revert_when_available").run(e.defaultFallbackStrength,e.revertWhenAvailable?1:0)),e}o(fe,"writeRotationConfig");function he(t,n=Date.now(),e){const a=D(t),i=a.filter(s=>s.authFailure===void 0&&Et(s,e)<=n),r=b(t);return i.length===0?`${r} pool exhausted`:`${r} ${i[0]?.label??t} (${i.length}/${a.length} ready)`}o(he,"formatStatusLine");export{W as ANTHROPIC_PROVIDER_ID,R as DuplicateSubscriptionError,yt as INVALID_REFRESH_TOKEN_BACKOFF_MS,zt as MANAGED_PROVIDER_IDS,H as MAX_RATE_LIMIT_COOLDOWN_MS,J as OPENAI_CHATGPT_PROVIDER_ID,G as OPENAI_CODEX_PROVIDER_ID,xt as activeCooldowns,It as addSubscription,de as commitManagedLogin,Et as coolingUntilFor,Ut as enrichManagedLoginIdentity,Gt as formatCooldowns,he as formatStatusLine,b as getProviderLabel,St as isInvalidRefreshTokenError,Qt as isManagedProvider,re as markSubscriptionAttempt,oe as markSubscriptionAuthenticationFailed,se as markSubscriptionRateLimited,ce as markSubscriptionSuccess,ue as modifyElectedSubscription,Xt as openAIDeviceId,Nt as parseRetryAfterHeader,ie as promoteSubscription,Mt as readRotationConfig,D as readSubscriptionPool,le as refreshSubscriptionCredential,Ft as removeAllManagedAccounts,ee as removeManagedAccount,ae as removeManagedProvider,ne as removeSubscription,te as renameSubscription,Yt as resolveProviderCandidates,me as retryAfterMsFromError,Dt as retryAfterMsFromHeaders,Jt as subscriptionNeedsSuccessWrite,vt as upsertSubscription,$t as upsertSubscriptionWithUniqueAccount,fe as writeRotationConfig,Zt as writeSubscriptionPool};
|
|
1
|
+
var lt=Object.defineProperty;var r=(t,n)=>lt(t,"name",{value:n,configurable:!0});import{prepared as g}from"./sqlite-statements.js";import{createHash as ut,randomUUID as N}from"node:crypto";import{mkdirSync as dt,readFileSync as mt,writeFileSync as ft}from"node:fs";import{join as G}from"node:path";import{brokerConfigSnapshot as ht}from"./config.js";import{LockedJsonFile as bt}from"./locked-file.js";import{secretsDir as pt}from"./layout.js";import{openScopedStore as At}from"./scoped-state/db.js";import{providerStore as yt}from"./scoped-state/paths.js";import{providerModelCooldowns as P,providerModelCooldownsFor as _t,providerRowByReference as j,providerRows as gt,providerWrite as A}from"./scoped-state/providers.js";import{modelCooldownFamily as W}from"./rate-limit-scope.js";const H="anthropic",z="openai-codex",J="openai",K=600*1e3,Et=720*60*60*1e3,Jt=[H,z,J],Q="strong",X={anthropic:{label:"Claude",credentials:"anthropic-credentials.json"},"openai-codex":{label:"OpenAI Codex",credentials:"openai-codex-credentials.json"},openai:{label:"ChatGPT",credentials:"openai-credentials.json"}};function I(t){return{refresh:t.refresh,access:t.access,expires:t.expires,...t.accountId?{accountId:t.accountId}:{},...t.clientId?{clientId:t.clientId}:{}}}r(I,"stored");function V(t){return typeof t=="string"&&t.trim()?t.trim():void 0}r(V,"optionalString");function f(){return pt()}r(f,"agentDir");function B(t){return G(f(),X[t].credentials)}r(B,"credentialFile");function Y(t){return t==="ultra"||t==="strong"||t==="medium"||t==="light"?t:void 0}r(Y,"normalizeStrength");function h(t){return t.toLowerCase()}r(h,"key");function Lt(t){const n=typeof t.label=="string"?t.label.trim():"",e=typeof t.refresh=="string"?t.refresh.trim():"",a=typeof t.access=="string"?t.access.trim():"",i=Number(t.expires);if(!n||!e||!a||!Number.isFinite(i))return;const o=V(t.accountId),s=V(t.clientId);return{label:n,refresh:e,access:a,expires:i,rateLimitedUntil:Number.isFinite(Number(t.rateLimitedUntil))?Number(t.rateLimitedUntil):0,lastAttemptAt:Number.isFinite(Number(t.lastAttemptAt))?Number(t.lastAttemptAt):0,lastRateLimitedAt:Number.isFinite(Number(t.lastRateLimitedAt))?Number(t.lastRateLimitedAt):0,...t.authFailure==="invalid_grant"?{authFailure:"invalid_grant"}:{},...o?{accountId:o}:{},...s?{clientId:s}:{},...Z(t.modelCooldowns)?{modelCooldowns:Z(t.modelCooldowns)}:{}}}r(Lt,"credential");function Z(t){if(!t||typeof t!="object"||Array.isArray(t))return;const n=Object.entries(t).filter(([e,a])=>e.trim()!==""&&Number.isFinite(Number(a))).map(([e,a])=>[e,Number(a)]);return n.length?Object.fromEntries(n):void 0}r(Z,"modelCooldowns");function U(t,n){if(!n.label?.trim())throw new Error(`${b(t)} subscription label cannot be empty`);const e=Lt(n);if(!e)throw new Error(`Refusing to persist incomplete ${b(t)} subscription "${n.label.trim()}"; pool rows require both OAuth tokens.`);return e}r(U,"assertCredential");function v(t,n){if(!n||n.trim()==="")return{version:1,accounts:{}};let e;try{e=JSON.parse(n)}catch(a){throw new Error(`managed credential file ${t} is corrupt: ${a instanceof Error?a.message:String(a)}`)}if(typeof e!="object"||e===null||Array.isArray(e)||e.version!==1||typeof e.accounts!="object"||e.accounts===null||Array.isArray(e.accounts))throw new Error(`managed credential file ${t} is corrupt`);return e}r(v,"parseCredentials");function xt(t,n,e=[]){const a=n.accounts[t.id];if(!a||typeof a.refresh!="string"||typeof a.access!="string"||!Number.isFinite(a.expires))throw new Error(`managed ${t.providerId} account ${t.id} has no credential record`);return{label:t.label,refresh:a.refresh,access:a.access,expires:a.expires,rateLimitedUntil:t.authFailure||!t.lastRateLimitedAt?t.rateLimitedUntil:Math.min(t.rateLimitedUntil,t.lastRateLimitedAt+K),lastAttemptAt:t.lastAttemptAt,lastRateLimitedAt:t.lastRateLimitedAt,...t.authFailure?{authFailure:t.authFailure}:{},...a.accountId?{accountId:a.accountId}:{},...a.clientId?{clientId:a.clientId}:{},...e.length?{modelCooldowns:Object.fromEntries(e.map(i=>[i.family,i.lastRateLimitedAt?Math.min(i.rateLimitedUntil,i.lastRateLimitedAt+K):i.rateLimitedUntil]))}:{}}}r(xt,"toPublic");function k(t,n){const e=_t(f(),t);return gt(f(),t).map(a=>({row:a,entry:xt(a,n,e.get(a.id))}))}r(k,"rowsWithCredentials");function tt(t){return new bt(B(t),!0,`{"version":1,"accounts":{}}
|
|
2
|
+
`)}r(tt,"managedCredentialFile");function et(t){return v(B(t),tt(t).readUnlocked())}r(et,"readFile");function nt(t,n){const e=t.accountId?.trim(),a=n.accountId?.trim();return!!(e&&a&&e!==a)}r(nt,"differentAccount");function _(t){return t.accountId?.trim()||(t.refresh.trim()?`refresh:${ut("sha256").update(t.refresh.trim()).digest("hex")}`:void 0)}r(_,"identity");function at(t,n){const e=V(n);return e?{...t,clientId:e}:t}r(at,"withClientId");function it(t,n,e){const a=e.refresh?.trim()??"",i=e.access?.trim()??"",o=Number(e.expires);if(!a||!i||!Number.isFinite(o))throw new Error(`${b(t)} subscription "${n}" returned an incomplete credential; the stored credential was left unchanged.`);return{refresh:a,access:i,expires:o}}r(it,"assertTokens");function Rt(t,n){const e=W(n);return Math.max(t.rateLimitedUntil||0,e===void 0?0:t.modelCooldowns?.[e]??0)}r(Rt,"coolingUntilFor");function wt(t,n=Date.now()){return[...(t.rateLimitedUntil||0)>n?[{scope:"account",until:t.rateLimitedUntil}]:[],...Object.entries(t.modelCooldowns??{}).filter(([,e])=>e>n).map(([e,a])=>({scope:e,until:a}))]}r(wt,"activeCooldowns");function Qt(t,n=Date.now()){const e=wt(t,n).map(({scope:a,until:i})=>`${a==="account"?"":`${a} `}cooldown ${It(i-n)}`);return e.length?e.join(", "):void 0}r(Qt,"formatCooldowns");function It(t){const n=Math.max(1,Math.ceil(t/6e4));if(n<60)return`${n}m`;const e=Math.floor(n/60);return e<24?`${e}h${n%60?` ${n%60}m`:""}`:`${Math.floor(e/24)}d${e%24?` ${e%24}h`:""}`}r(It,"formatWait");function Ft(t,n){const e=W(n);return e!==void 0&&t.modelCooldowns?.[e]!==void 0}r(Ft,"hasModelCooldownRow");function Xt(t,n){return!!(t.rateLimitedUntil||t.authFailure)||Ft(t,n)}r(Xt,"subscriptionNeedsSuccessWrite");function Yt(t){return t===H||t===z||t===J}r(Yt,"isManagedProvider");function Zt(){const t=G(f(),"openai-device-id");try{const e=mt(t,"utf8").trim();if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return e.toLowerCase()}catch{}const n=N();return dt(f(),{recursive:!0,mode:448}),ft(t,`${n}
|
|
3
|
+
`,{mode:384}),n}r(Zt,"openAIDeviceId");function b(t){return X[t].label}r(b,"getProviderLabel");function vt(t,n=Date.now(),e={}){const a=[],i=[];for(let o=0;o<t.length;o++){const s=t[o],c=s.registered?s.automaticFallback===!1?"non-managed-fallback":s.authenticated?(s.coolingUntil??0)>n?"cooling":void 0:"unauthenticated":"unregistered";if(!c)a.push(s);else if(i.push({candidate:s,reason:c}),e.pinnedToFirst&&o===0&&c!=="cooling")break}return{candidates:a,rejected:i}}r(vt,"resolveProviderCandidates");class w extends Error{static{r(this,"DuplicateSubscriptionError")}constructor(n){super(n),this.name="DuplicateSubscriptionError"}}function D(t){return k(t,et(t)).map(({entry:n})=>n)}r(D,"readSubscriptionPool");function te(t){return k(t,et(t)).map(({row:n,entry:e})=>({id:n.id,entry:e}))}r(te,"readSubscriptionPoolRows");async function E(t,n){const e=B(t);return tt(t).withLockAsync(async(a,i,o)=>{const s=v(e,a);return{result:await n(s,k(t,s),c=>o(`${JSON.stringify(c)}
|
|
4
|
+
`))}})}r(E,"locked");function O(t,n,e){g(t,"INSERT INTO managed_accounts(provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure) VALUES(?,?,?,?,?,?,?,?,?)").run(n,e.id,e.label,e.labelKey,e.position,e.lastAttemptAt,e.lastRateLimitedAt,e.rateLimitedUntil,e.authFailure??null)}r(O,"insert");function L(t,n){g(t,"UPDATE managed_accounts SET label=?,label_key=?,position=?,last_attempt_at=?,last_rate_limited_at=?,rate_limited_until=?,auth_failure=? WHERE provider_id=? AND id=?").run(n.label,n.labelKey,n.position,n.lastAttemptAt,n.lastRateLimitedAt,n.rateLimitedUntil,n.authFailure??null,n.providerId,n.id)}r(L,"update");function rt(t,n,e,a,i,o){g(t,"INSERT INTO managed_account_model_cooldowns(provider_id,account_id,family,rate_limited_until,last_rate_limited_at) VALUES(?,?,?,?,?) ON CONFLICT(provider_id,account_id,family) DO UPDATE SET rate_limited_until=excluded.rate_limited_until,last_rate_limited_at=excluded.last_rate_limited_at").run(n,e,a,i,o)}r(rt,"upsertModelCooldown");function T(t,n,e){e===void 0?g(t,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=?").run(n):g(t,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=? AND account_id=?").run(n,e)}r(T,"deleteModelCooldowns");function x(t,n){return k(t,n).map(({entry:e})=>e)}r(x,"publicPool");function F(t,n){try{n()}catch(e){throw new Error(`managed ${b(t)} credential file was published, but its SQL membership update failed; SQL membership remains authoritative: ${e instanceof Error?e.message:String(e)}`,{cause:e})}}r(F,"writeMembershipAfterCredentialPublish");async function ee(t,n){return E(t,async(e,a,i)=>{const o=n.map(l=>U(t,l)),s=o.map((l,u)=>({entry:l,id:a[u]?.row.id??N()})),c={};for(const{entry:l,id:u}of s)c[u]=I(l);return i({version:1,accounts:c}),F(t,()=>A(f(),l=>{const u=P(l,t);g(l,"DELETE FROM managed_accounts WHERE provider_id=?").run(t),g(l,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=?").run(t),s.forEach(({entry:d,id:m},y)=>{O(l,t,{providerId:t,id:m,label:d.label,labelKey:h(d.label),position:y,lastAttemptAt:d.lastAttemptAt,lastRateLimitedAt:d.lastRateLimitedAt,rateLimitedUntil:d.rateLimitedUntil,...d.authFailure?{authFailure:d.authFailure}:{}});for(const[$,C]of Object.entries(d.modelCooldowns??{}))rt(l,t,m,$,C,u.get(m)?.find(p=>p.family===$)?.lastRateLimitedAt??d.lastRateLimitedAt)})})),o})}r(ee,"writeSubscriptionPool");function M(t,n){const e=n.trim().toLowerCase(),a=Number.parseInt(e,10);return Number.isFinite(a)&&String(a)===e&&a>=1&&a<=t.length?a-1:t.findIndex(({entry:i})=>h(i.label)===e)}r(M,"resolve");async function $t(t,n){const e=U(t,n);return E(t,async(a,i,o)=>{if(i.some(({entry:u})=>h(u.label)===h(e.label)))throw new w(`${b(t)} subscription already exists: ${e.label}`);const s=_(e),c=i.find(({entry:u})=>_(u)===s);if(s&&c)throw new w(`This ${b(t)} account is already added as \u201C${c.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const l=N();return a.accounts[l]=I(e),o(a),F(t,()=>A(f(),u=>O(u,t,{providerId:t,id:l,label:e.label,labelKey:h(e.label),position:i.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil,...e.authFailure?{authFailure:e.authFailure}:{}}))),x(t,a)})}r($t,"addSubscription");async function ne(t,n){const e=U(t,n);return D(t).find(i=>h(i.label)===h(e.label))?St(t,e):$t(t,e)}r(ne,"upsertSubscription");async function St(t,n){const e=U(t,n);return E(t,async(a,i,o)=>{const s=i.findIndex(({entry:m})=>h(m.label)===h(e.label)),c=i[s],l=i.find(({entry:m},y)=>y!==s&&_(m)===_(e));if(_(e)&&l)throw new w(`This ${b(t)} account is already added as \u201C${l.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);if(!c){const m=N();return a.accounts[m]=I(e),o(a),F(t,()=>A(f(),y=>O(y,t,{providerId:t,id:m,label:e.label,labelKey:h(e.label),position:i.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil}))),x(t,a)}const u=c.row,d=e.lastRateLimitedAt>=u.lastRateLimitedAt;return a.accounts[u.id]=I(e),o(a),F(t,()=>A(f(),m=>{L(m,{...u,authFailure:void 0,label:e.label,labelKey:h(e.label),lastAttemptAt:Math.max(u.lastAttemptAt,e.lastAttemptAt),lastRateLimitedAt:d?e.lastRateLimitedAt:u.lastRateLimitedAt,rateLimitedUntil:d?e.rateLimitedUntil:u.rateLimitedUntil}),nt(c.entry,e)&&T(m,t,u.id)})),x(t,a)})}r(St,"upsertSubscriptionWithUniqueAccount");async function ae(t,n,e){return E(t,async(a,i)=>{const o=M(i,n);if(o<0)throw new Error(`Unknown ${b(t)} subscription: ${n}`);const s=e.trim();if(!s)throw new Error(`${b(t)} subscription label cannot be empty`);if(i.some((c,l)=>l!==o&&h(c.entry.label)===h(s)))throw new w(`${b(t)} subscription already exists: ${s}`);return A(f(),c=>L(c,{...i[o].row,label:s,labelKey:h(s)})),x(t,a)})}r(ae,"renameSubscription");async function q(t,n,e){return E(t,async(a,i,o)=>{const s=n(i);if(s<0)throw new Error(`Unknown ${b(t)} subscription: ${e}`);const c=i[s].row;return A(f(),l=>{g(l,"DELETE FROM managed_accounts WHERE provider_id=? AND id=?").run(t,c.id),T(l,t,c.id)}),delete a.accounts[c.id],o(a),x(t,a)})}r(q,"remove");async function Nt(t,n){return q(t,e=>M(e,n),n)}r(Nt,"removeManagedAccount");async function ie(t,n){return q(t,e=>e.findIndex(({row:a})=>a.id===n),n)}r(ie,"removeManagedAccountById");async function re(t,n){return q(t,e=>M(e,n),n)}r(re,"removeSubscription");async function Ut(t){return E(t,async(n,e,a)=>{A(f(),i=>{g(i,"DELETE FROM managed_accounts WHERE provider_id=?").run(t),T(i,t)});for(const{row:i}of e)delete n.accounts[i.id];return a(n),e.length})}r(Ut,"removeAllManagedAccounts");async function oe(t,n){return n===void 0?(await Ut(t),[]):Nt(t,n)}r(oe,"removeManagedProvider");async function se(t,n){return E(t,async(e,a)=>{const i=M(a,n);if(i<0)throw new Error(`Unknown ${b(t)} subscription: ${n}`);return A(f(),o=>{[a[i],...a.filter((c,l)=>l!==i)].forEach((c,l)=>L(o,{...c.row,position:l}))}),x(t,e)})}r(se,"promoteSubscription");async function ot(t,n,e){return A(f(),a=>{const i=j(a,t,n);if(!i)throw new Error(`Unknown ${b(t)} subscription: ${n}`);const o=e(i);o&&L(a,o)}),D(t)}r(ot,"mutateHealth");async function ce(t,n,e=Date.now()){return ot(t,n,a=>({...a,lastAttemptAt:Math.max(a.lastAttemptAt,e)}))}r(ce,"markSubscriptionAttempt");async function le(t,n,e,a=Date.now(),i=Date.now(),o={kind:"account"}){return e=Math.min(Math.max(0,e),K),o.kind==="account"?ot(t,n,s=>i<s.lastRateLimitedAt?null:{...s,lastAttemptAt:Math.max(s.lastAttemptAt,a),lastRateLimitedAt:i,rateLimitedUntil:i+e}):(A(f(),s=>{const c=j(s,t,n);if(!c)throw new Error(`Unknown ${b(t)} subscription: ${n}`);L(s,{...c,lastAttemptAt:Math.max(c.lastAttemptAt,a)});const l=P(s,t).get(c.id)?.find(u=>u.family===o.family);l&&i<l.lastRateLimitedAt||rt(s,t,c.id,o.family,i+e,i)}),D(t))}r(le,"markSubscriptionRateLimited");async function ue(t,n,e=Date.now(),a){const i=W(a);return A(f(),o=>{const s=j(o,t,n);if(!s)throw new Error(`Unknown ${b(t)} subscription: ${n}`);if(e>=s.lastRateLimitedAt&&L(o,{...s,authFailure:void 0,lastAttemptAt:Math.max(s.lastAttemptAt,e),rateLimitedUntil:0}),i===void 0)return;const c=P(o,t).get(s.id)?.find(l=>l.family===i);c&&e>=c.lastRateLimitedAt&&g(o,"DELETE FROM managed_account_model_cooldowns WHERE provider_id=? AND account_id=? AND family=?").run(t,s.id,i)}),D(t)}r(ue,"markSubscriptionSuccess");function st(t){const n=Date.now();A(f(),e=>L(e,{...t,authFailure:"invalid_grant",lastAttemptAt:Math.max(t.lastAttemptAt,n),rateLimitedUntil:Math.max(t.rateLimitedUntil,n+Et)}))}r(st,"recordInvalidGrant");async function de(t,n,e){return E(t,async(a,i,o)=>{const s=M(i,n);if(s<0)throw new Error(`${b(t)} subscription "${n}" no longer exists`);const c=i[s];if(c.entry.expires>Date.now())return c.entry;let l;try{l=await e(c.entry.refresh,c.entry.clientId)}catch(d){throw ct(d)&&st(c.row),d}const u=at(it(t,c.entry.label,l),l.clientId??c.entry.clientId);return a.accounts[c.row.id]={...a.accounts[c.row.id],...u},o(a),{...c.entry,...u}})}r(de,"refreshSubscriptionCredential");function ct(t){const n=t,e=[n?.errorMessage,n?.message,t instanceof Error?t.message:""].filter(i=>typeof i=="string").join(" ");return(typeof n?.status=="number"?n.status:Number(e.match(/\bstatus[=:]\s*(\d+)/i)?.[1]))===400&&/invalid_grant|refresh token .*invalid|refresh token .*not found/i.test(e)}r(ct,"isInvalidRefreshTokenError");async function me(t,n,e){return E(t,async(a,i,o)=>{const s=new Set;let c;for(;s.size<i.length;){const l=n(i.filter(({entry:d})=>!s.has(d.label)).map(({entry:d})=>d));if(!l)break;const u=i.find(({entry:d})=>d.label===l.label);s.add(u.entry.label);try{const d=await e(u.entry);if(d){const m=at(it(t,u.entry.label,d),d.clientId??u.entry.clientId);return a.accounts[u.row.id]={...a.accounts[u.row.id],...m},o(a),{...u.entry,...m}}return u.entry}catch(d){if(!ct(d))throw d;c=d,st(u.row)}}if(c)throw c})}r(me,"modifyElectedSubscription");async function Dt(t,n){const e=n.accountId?.trim(),a=n.email?.trim();if(t!==H)return{...e?{accountId:e}:{},...a?{email:a}:{}};try{const i=await fetch("https://api.anthropic.com/api/oauth/profile",{headers:{Authorization:`Bearer ${n.access}`,Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!i.ok)return e?{accountId:e}:{};const o=await i.json(),s=typeof o.account?.uuid=="string"&&o.account.uuid.trim()?o.account.uuid.trim():void 0,c=typeof o.account?.email=="string"&&o.account.email.trim()?o.account.email.trim():void 0;return{...s?{accountId:s}:{},...c?{email:c}:{}}}catch{return e?{accountId:e}:{}}}r(Dt,"enrichManagedLoginIdentity");async function fe(t,n,e){const a=await Dt(t,e);return E(t,async(i,o,s)=>{const c=e.autoLabel?void 0:n?.trim(),l=_({refresh:e.refresh,accountId:a.accountId}),u=(c?o.find(({entry:p})=>h(p.label)===h(c)):void 0)??o.find(({entry:p})=>_(p)===l);let d=u?.entry.label??c;if(!d&&e.autoLabel){const p=a.email||n?.trim()||`Account ${o.length+1}`;d=p;for(let S=2;o.some(({entry:R})=>h(R.label)===h(d));S++)d=`${p} (${S})`}if(!d)throw new Error(`${b(t)} subscription label cannot be empty`);const m=U(t,{label:d,refresh:e.refresh,access:e.access,expires:e.expires,...a.accountId?{accountId:a.accountId}:{},...e.clientId?{clientId:e.clientId}:{},rateLimitedUntil:0,lastAttemptAt:Date.now(),lastRateLimitedAt:e.lastRateLimitedAt});if(!u){if(o.some(({entry:R})=>h(R.label)===h(m.label)))throw new w(`${b(t)} subscription already exists: ${m.label}`);const p=o.find(({entry:R})=>_(R)===_(m));if(_(m)&&p)throw new w(`This ${b(t)} account is already added as \u201C${p.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const S=N();return i.accounts[S]=I(m),s(i),F(t,()=>A(f(),R=>O(R,t,{providerId:t,id:S,label:m.label,labelKey:h(m.label),position:o.length,lastAttemptAt:m.lastAttemptAt,lastRateLimitedAt:m.lastRateLimitedAt,rateLimitedUntil:m.rateLimitedUntil}))),x(t,i)}const y=u.row,$=o.find(({entry:p})=>p.label!==u.entry.label&&_(p)===_(m));if(_(m)&&$)throw new w(`This ${b(t)} account is already added as \u201C${$.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);i.accounts[y.id]=I(m),s(i);const C=m.lastRateLimitedAt>=y.lastRateLimitedAt;return F(t,()=>A(f(),p=>{L(p,{...y,authFailure:void 0,lastAttemptAt:Math.max(y.lastAttemptAt,m.lastAttemptAt),lastRateLimitedAt:C?m.lastRateLimitedAt:y.lastRateLimitedAt,rateLimitedUntil:C?0:y.rateLimitedUntil}),nt(u.entry,m)&&T(p,t,y.id)})),x(t,i)})}r(fe,"commitManagedLogin");function Mt(t,n=Date.now()){if(!t?.trim())return;const e=Number(t.trim());if(Number.isFinite(e))return Math.max(0,Math.round(e*1e3));const a=Date.parse(t);return Number.isFinite(a)?Math.max(0,a-n):void 0}r(Mt,"parseRetryAfterHeader");function Ct(t){if(!t)return;const n=r(a=>t instanceof Headers?t.get(a)??void 0:Object.entries(t).find(([i])=>i.toLowerCase()===a)?.[1],"get"),e=n("retry-after-ms");return e!==void 0&&e.trim()!==""&&Number.isFinite(Number(e))?Math.max(0,Number(e)):Mt(n("retry-after"))}r(Ct,"retryAfterMsFromHeaders");function he(t){if(!t||typeof t!="object"||!("headers"in t))return;const n=t.headers;if(!(!(n instanceof Headers)&&(!n||typeof n!="object"||Array.isArray(n))))return Ct(n)}r(he,"retryAfterMsFromError");function kt(){const t=ht();if(t)return t.rotation;const n=g(At(yt(f())),"SELECT default_fallback_strength,revert_when_available FROM rotation_settings WHERE singleton=1").get();return{defaultFallbackStrength:Y(n?.default_fallback_strength)??Q,revertWhenAvailable:n?.revert_when_available===void 0||n.revert_when_available===null?!0:!!n.revert_when_available}}r(kt,"readRotationConfig");function be(t){const n=kt(),e={defaultFallbackStrength:Y(t.defaultFallbackStrength)??n.defaultFallbackStrength??Q,revertWhenAvailable:t.revertWhenAvailable??n.revertWhenAvailable??!0};return A(f(),a=>g(a,"INSERT INTO rotation_settings(singleton,default_fallback_strength,revert_when_available) VALUES(1,?,?) ON CONFLICT(singleton) DO UPDATE SET default_fallback_strength=excluded.default_fallback_strength,revert_when_available=excluded.revert_when_available").run(e.defaultFallbackStrength,e.revertWhenAvailable?1:0)),e}r(be,"writeRotationConfig");function pe(t,n=Date.now(),e){const a=D(t),i=a.filter(s=>s.authFailure===void 0&&Rt(s,e)<=n),o=b(t);return i.length===0?`${o} pool exhausted`:`${o} ${i[0]?.label??t} (${i.length}/${a.length} ready)`}r(pe,"formatStatusLine");export{H as ANTHROPIC_PROVIDER_ID,w as DuplicateSubscriptionError,Et as INVALID_REFRESH_TOKEN_BACKOFF_MS,Jt as MANAGED_PROVIDER_IDS,K as MAX_RATE_LIMIT_COOLDOWN_MS,J as OPENAI_CHATGPT_PROVIDER_ID,z as OPENAI_CODEX_PROVIDER_ID,wt as activeCooldowns,$t as addSubscription,fe as commitManagedLogin,Rt as coolingUntilFor,Dt as enrichManagedLoginIdentity,Qt as formatCooldowns,pe as formatStatusLine,b as getProviderLabel,ct as isInvalidRefreshTokenError,Yt as isManagedProvider,ce as markSubscriptionAttempt,le as markSubscriptionRateLimited,ue as markSubscriptionSuccess,me as modifyElectedSubscription,Zt as openAIDeviceId,Mt as parseRetryAfterHeader,se as promoteSubscription,kt as readRotationConfig,D as readSubscriptionPool,te as readSubscriptionPoolRows,de as refreshSubscriptionCredential,Ut as removeAllManagedAccounts,Nt as removeManagedAccount,ie as removeManagedAccountById,oe as removeManagedProvider,re as removeSubscription,ae as renameSubscription,vt as resolveProviderCandidates,he as retryAfterMsFromError,Ct as retryAfterMsFromHeaders,Xt as subscriptionNeedsSuccessWrite,ne as upsertSubscription,St as upsertSubscriptionWithUniqueAccount,be as writeRotationConfig,ee as writeSubscriptionPool};
|