grix-connector
Version:
Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.
2 lines (1 loc) • 14.9 kB
JavaScript
function R(s){const e=s.toLowerCase();return e.includes("open.bigmodel.cn")||e.includes("bigmodel.cn")||e.includes("api.z.ai")?{id:"zhipu",label:"Zhipu GLM"}:e.includes("api.kimi.com")?{id:"kimi",label:"Kimi"}:e.includes("api.minimaxi.com")?{id:"minimax_cn",label:"MiniMax"}:e.includes("api.minimax.io")?{id:"minimax_en",label:"MiniMax"}:e.includes("api.deepseek.com")?{id:"deepseek",label:"DeepSeek"}:e.includes("api.stepfun.ai")||e.includes("api.stepfun.com")?{id:"stepfun",label:"StepFun"}:e.includes("api.siliconflow.cn")?{id:"siliconflow_cn",label:"SiliconFlow"}:e.includes("api.siliconflow.com")?{id:"siliconflow_en",label:"SiliconFlow"}:e.includes("openrouter.ai")?{id:"openrouter",label:"OpenRouter"}:e.includes("api.novita.ai")?{id:"novita",label:"Novita AI"}:null}const f=1e4;function u(s,e,i){return{provider:s,providerLabel:e,planName:null,tiers:[],balance:null,success:!1,error:i}}function m(s){if(typeof s=="number")return s;if(typeof s=="string"){const e=Number(s);return Number.isFinite(e)?e:null}return null}function w(s){if(!Number.isFinite(s)||s<=0)return null;try{return new Date(s).toISOString()}catch{return null}}function T(s){if(typeof s=="string")return s;if(typeof s=="number"){const e=s<1e12?s*1e3:s;return w(e)}return null}async function S(s){const e="zhipu",i="Zhipu GLM";try{const t=await fetch("https://api.z.ai/api/monitor/usage/quota/limit",{method:"GET",headers:{Authorization:s,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const o=await t.json();if(o.success===!1)return u(e,i,`API error: ${o.msg??"Unknown error"}`);const n=o.data;if(!n)return u(e,i,"Missing data field");const r=typeof n.level=="string"?n.level:null,a=$(n);return{provider:e,providerLabel:i,planName:r,tiers:a,balance:null,success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}function $(s){const e=Array.isArray(s.limits)?s.limits:[],i=[];for(const o of e){if(String(o.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const r=m(o.percentage)??0,a=typeof o.nextResetTime=="number"?o.nextResetTime:Number.MAX_SAFE_INTEGER,l=a===Number.MAX_SAFE_INTEGER?null:w(a);i.push({percentage:r,resetMs:a,resetIso:l})}i.sort((o,n)=>o.resetMs-n.resetMs);const t=[];if(i.length>0){const o=i[0];t.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(o.percentage*100)/100,resetsAt:o.resetIso})}return t}async function P(s){const e="kimi",i="Kimi";try{const t=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const o=await t.json(),n=[],r=Array.isArray(o.limits)?o.limits:[];for(const l of r){const d=l.detail;if(!d)continue;const c=m(d.limit)??1,p=m(d.remaining)??0,b=T(d.resetTime),h=Math.max(0,c-p);n.push({name:"five_hour",label:"5h limit",usedPercent:c>0?Math.round(h/c*1e4)/100:0,resetsAt:b})}const a=o.usage;if(a){const l=m(a.limit)??1,d=m(a.remaining)??0,c=T(a.resetTime),p=Math.max(0,l-d);n.push({name:"weekly_limit",label:"Weekly limit",usedPercent:l>0?Math.round(p/l*1e4)/100:0,resetsAt:c})}return{provider:e,providerLabel:i,planName:null,tiers:n,balance:null,success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function v(s,e){const i=e?"minimax_cn":"minimax_en",t="MiniMax",o=e?"api.minimaxi.com":"api.minimax.io";try{const n=await fetch(`https://${o}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(f)});if(n.status===401||n.status===403)return u(i,t,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const p=await n.text().catch(()=>"");return u(i,t,`API error (HTTP ${n.status}): ${p.slice(0,200)}`)}const r=await n.json(),a=r.base_resp;if(a&&typeof a.status_code=="number"&&a.status_code!==0)return u(i,t,`API error (code ${a.status_code}): ${a.status_msg??"Unknown"}`);const l=[],c=(Array.isArray(r.model_remains)?r.model_remains:[])[0];if(c){const p=m(c.current_interval_total_count)??0,b=m(c.current_interval_usage_count)??0,h=typeof c.end_time=="number"?c.end_time:null;p>0&&l.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(b/p*1e4)/100,resetsAt:h!==null?w(h):null});const g=m(c.current_weekly_total_count)??0,j=m(c.current_weekly_usage_count)??0,k=typeof c.weekly_end_time=="number"?c.weekly_end_time:null;g>0&&l.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(j/g*1e4)/100,resetsAt:k!==null?w(k):null})}return{provider:i,providerLabel:t,planName:null,tiers:l,balance:null,success:!0,error:null}}catch(n){return u(i,t,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function N(s){const e="deepseek",i="DeepSeek";try{const t=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const c=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${c.slice(0,200)}`)}const o=await t.json(),n=o.is_available===!0,a=(Array.isArray(o.balance_infos)?o.balance_infos:[])[0];if(!a)return u(e,i,"No balance info returned");const l=String(a.currency??"CNY"),d=m(a.total_balance);return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:d??0,total:null,used:null,unit:l},success:!0,error:n?null:"Insufficient balance"}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function E(s){const e="stepfun",i="StepFun";try{const t=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const r=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${r.slice(0,200)}`)}const o=await t.json(),n=m(o.balance)??0;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:n,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function _(s,e){const i=e?"siliconflow_cn":"siliconflow_en",t=e?"SiliconFlow":"SiliconFlow (EN)",o=e?"api.siliconflow.cn":"api.siliconflow.com",n=e?"CNY":"USD";try{const r=await fetch(`https://${o}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(r.status===401||r.status===403)return u(i,t,`Authentication failed (HTTP ${r.status})`);if(!r.ok){const c=await r.text().catch(()=>"");return u(i,t,`API error (HTTP ${r.status}): ${c.slice(0,200)}`)}const l=(await r.json()).data;if(!l)return u(i,t,"Missing data field");const d=m(l.totalBalance)??0;return{provider:i,providerLabel:t,planName:null,tiers:[],balance:{remaining:d,total:null,used:null,unit:n},success:!0,error:null}}catch(r){return u(i,t,`Network error: ${r instanceof Error?r.message:String(r)}`)}}async function x(s){const e="openrouter",i="OpenRouter";try{const t=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const d=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${d.slice(0,200)}`)}const o=await t.json(),n=o.data??o,r=m(n.total_credits)??0,a=m(n.total_usage)??0,l=r-a;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:l,total:r,used:a,unit:"USD"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function M(s){const e="novita",i="Novita AI";try{const t=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const r=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${r.slice(0,200)}`)}const o=await t.json(),n=(m(o.availableBalance)??0)/1e4;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:n,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}const y=new Map,G=300*1e3;async function z(s,e){const i=`${e.slice(0,8)}@${s}`,t=y.get(i);if(t&&Date.now()-t.timestamp<=G){const r=await L(t.providerId,s,e);if(r)return r;y.delete(i)}else t&&y.delete(i);const o=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],n=await Promise.allSettled(o.map(r=>L(r,s,e)));for(let r=0;r<n.length;r++){const a=n[r];if(a.status==="fulfilled"&&a.value?.success)return y.set(i,{providerId:o[r],timestamp:Date.now()}),a.value}return null}const A=new Map,B=300*1e3;async function H(s){const e=s.slice(0,8),i=A.get(e);if(i&&Date.now()-i.timestamp<=B){const n=[i.providerId],r=await Promise.allSettled(n.map(a=>I(a,s)));for(const a of r)if(a.status==="fulfilled"&&a.value?.success)return a.value;A.delete(e)}else i&&A.delete(e);const t=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],o=await Promise.allSettled(t.map(n=>I(n,s)));for(let n=0;n<o.length;n++){const r=o[n];if(r.status==="fulfilled"&&r.value?.success)return A.set(e,{providerId:t[n],timestamp:Date.now()}),r.value}return null}async function I(s,e){try{switch(s){case"zhipu":return S(e);case"kimi":return P(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return N(e);case"stepfun":return E(e);case"siliconflow_cn":return _(e,!0);case"siliconflow_en":return _(e,!1);case"openrouter":return x(e);case"novita":return M(e)}}catch{return null}}async function L(s,e,i){const t=e.replace(/\/+$/,""),o={Authorization:"Bearer ${apiKey}",Accept:"application/json"};try{switch(s){case"zhipu":{const n=await fetch("${origin}/api/monitor/usage/quota/limit",{method:"GET",headers:{...o,Authorization:i,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json();if(r.success===!1)return null;const a=r.data;if(!a)return null;const l=typeof a.level=="string"?a.level:null,d=$(a);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:l,tiers:d,balance:null,success:!0,error:null}}case"deepseek":{const n=await fetch("${origin}/user/balance",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.is_available===!0,d=(Array.isArray(r.balance_infos)?r.balance_infos:[])[0];if(!d)return null;const c=String(d.currency??"CNY"),p=m(d.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:p??0,total:null,used:null,unit:c},success:!0,error:a?null:"Insufficient balance"}}case"kimi":{const n=await fetch("${origin}/coding/v1/usages",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=[],l=Array.isArray(r.limits)?r.limits:[];for(const d of l){const c=d.detail;if(!c)continue;const p=m(c.limit)??1,b=m(c.remaining)??0,h=T(c.resetTime),g=Math.max(0,p-b);a.push({name:"five_hour",label:"5h limit",usedPercent:p>0?Math.round(g/p*1e4)/100:0,resetsAt:h})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:a,balance:null,success:!0,error:null}}case"openrouter":{const n=await fetch("${origin}/api/v1/credits",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.data??r,l=m(a.total_credits)??0,d=m(a.total_usage)??0,c=l-d;return{provider:"openrouter",providerLabel:"OpenRouter",planName:null,tiers:[],balance:{remaining:c,total:l,used:d,unit:"USD"},success:!0,error:null}}case"stepfun":{const n=await fetch("${origin}/v1/accounts",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=m(r.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":{const n=await fetch("${origin}/v1/api/openplatform/coding_plan/remains",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.base_resp;if(a&&typeof a.status_code=="number"&&a.status_code!==0)return null;const d=(Array.isArray(r.model_remains)?r.model_remains:[])[0];if(!d)return null;const c=[],p=m(d.current_interval_total_count)??0,b=m(d.current_interval_usage_count)??0;return p>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(b/p*1e4)/100,resetsAt:null}),{provider:"minimax_cn",providerLabel:"MiniMax",planName:null,tiers:c,balance:null,success:!0,error:null}}case"siliconflow_cn":{const n=await fetch("${origin}/v1/user/info",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const a=(await n.json()).data;if(!a)return null;const l=m(a.totalBalance)??0;return{provider:"siliconflow_cn",providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const n=await fetch("${origin}/v3/user/balance",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=(m(r.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function U(s,e){if(!e.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const i=R(s);if(i)switch(i.id){case"zhipu":return S(e);case"kimi":return P(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return N(e);case"stepfun":return E(e);case"siliconflow_cn":return _(e,!0);case"siliconflow_en":return _(e,!1);case"openrouter":return x(e);case"novita":return M(e)}const t=await z(s,e);if(t)return t;const o=await H(e);return o||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${s}`}}export{R as detectProvider,U as queryProviderQuota};