aicommit2
Version:
A Reactive CLI that generates commit messages for Git and Jujutsu with various AI
2 lines (1 loc) • 8.32 kB
JavaScript
import C from"chalk";import _ from"openai";import{concatMap as M,from as w,map as P,catchError as $}from"rxjs";import{fromPromise as v}from"rxjs/internal/observable/innerFrom";import{A as j,l as x,a as A,b as k,c as b,d as S,e as I}from"./ai.service-d8e94c3a.mjs";import{i as K}from"./openai-5eabc0ae.mjs";import{g as H,H as U,b as q}from"./cli-8ee62906.mjs";import"fs";import"path";import"@pacote/xxhash";import"winston";import"http";import"https";import"net";import"tls";import"url";import"assert";import"tty";import"util";import"os";import"events";import"cleye";import"module";import"crypto";import"node:buffer";import"node:path";import"node:child_process";import"node:process";import"child_process";import"node:url";import"node:os";import"node:fs";import"buffer";import"stream";import"node:util";import"inquirer";import"fs/promises";import"readline";import"figlet";import"gradient-string";import"ora";import"inquirer-reactive-list-prompt";import"winston-daily-rotate-file";import"axios";import"node:fs/promises";import"chokidar";import"rxjs/operators";const O=class l extends j{constructor(r){super(r),this.params=r,this.getOpenRouterBaseUrl=()=>(this.params.config.url||"https://openrouter.ai").replace(/\/$/,""),this.getOpenRouterCatalogUrl=()=>`${this.getOpenRouterBaseUrl()}/api/v1`,this.getOpenRouterHeaders=()=>({"HTTP-Referer":"https://github.com/tak-bro/aicommit2","X-OpenRouter-Title":"aicommit2","X-OpenRouter-Categories":"cli-agent"}),this.getOpenRouterAuthHeaders=()=>({Authorization:`Bearer ${this.params.config.key}`,"Content-Type":"application/json",...this.getOpenRouterHeaders()}),this.hasRequestObject=t=>typeof t=="object"&&t!==null&&!Array.isArray(t)&&Object.keys(t).length>0,this.getRequestedModel=()=>Array.isArray(this.params.config.model)?this.params.config.model[0]||"":typeof this.params.config.model=="string"?this.params.config.model:"",this.getCatalogCacheKey=()=>`${this.getOpenRouterCatalogUrl()}|${this.params.config.key||""}`,this.getModelCacheKey=()=>`${this.getCatalogCacheKey()}|${this.getRequestedModel()}`,this.matchOpenRouterModel=(t,o)=>{const s=t.trim();return o.find(i=>[i.id,i.canonical_slug,i.name].filter(c=>!!c).some(c=>c===s))},this.getRequestPayloadExtras=async()=>{const t=this.params.config,o={},s=this.hasRequestObject(t.responseFormat)?t.responseFormat:{type:"json_object"};s&&await this.isResponseFormatSupported()&&(o.response_format=s),this.hasRequestObject(t.provider)&&(o.provider=t.provider);const i=await this.getReasoningPayload();return i&&(o.reasoning=i),o},this.extractOpenRouterText=t=>{if(!t||typeof t!="object")return"";const o=t;return o.content||o.reasoning_content||o.reasoning||""},this.buildChatCompletionPayload=async(t,o,s)=>{const i=this.params.config.maxTokens,m=this.params.config.temperature,c=K(this.params.config.model),n=this.getRequestedModel();return{messages:[{role:"system",content:t},{role:"user",content:o}],model:n,stream:s,...await this.getRequestPayloadExtras()||{},...c?{max_completion_tokens:i,temperature:1}:{max_tokens:i,top_p:this.params.config.topP,temperature:m}}},this.generateStreamingCommitMessage$=()=>{const{generate:t,type:o}=this.params.config;return this.createStreamingCommitMessages$(s=>{this.streamChunks(s).catch(i=>s.error(i))},o,t)},this.streamChunks=async t=>{const o=this.params.stagedDiff.diff,{logging:s,timeout:i}=this.params.config,m=H(this.buildPromptOptions()),c=`Here is the diff: ${o}`,n="OpenRouter",d=`${this.params.config.url||"https://openrouter.ai"}${this.params.config.path||"/api/v1/chat/completions"}`,g={Authorization:`Bearer ${this.params.config.key}`,"Content-Type":"application/json",...this.getOpenRouterHeaders()};x(o,"commit",n,this.params.config.model,d,g,s),A(o,"commit",n,m,c,s);const u=await this.buildChatCompletionPayload(m,c,!0);k(o,"commit",n,u,s);const f=Date.now();let p="";try{const R=await this.openAI.chat.completions.create(u,{timeout:i});for await(const T of R){const y=this.extractOpenRouterText(T.choices?.[0]?.delta);y&&(p+=y,t.next(y))}const E=Date.now()-f;b(o,"commit",n,{streamed:!0,totalLength:p.length},s),S(o,"commit",n,E,p,s),t.complete()}catch(h){I(o,"commit",n,h,s),t.error(h)}},this.colors={primary:"#f97316",secondary:"#fff"},this.serviceName=C.bgHex(this.colors.primary).hex(this.colors.secondary).bold(`[OpenRouter${this.formatModelSuffix()}]`),this.errorPrefix=C.red.bold(`[OpenRouter${this.formatModelSuffix()}]`);const e=this.params.config.url||"https://openrouter.ai",a=(this.params.config.path||"/api/v1/chat/completions").replace(/\/chat\/completions\/?$/,"");this.openAI=new _({apiKey:this.params.config.key,baseURL:`${e}${a}`,defaultHeaders:{...this.getOpenRouterHeaders()}})}async fetchOpenRouterCatalog(){const r=this.getCatalogCacheKey(),e=l.catalogCache.get(r);if(e)return e;const a=["/models/user","/models"];let t;for(const o of a)try{const i=(await new U({method:"GET",baseURL:`${this.getOpenRouterCatalogUrl()}${o}`,timeout:this.params.config.timeout}).setHeaders(this.getOpenRouterAuthHeaders()).execute()).data?.data??[];return l.catalogCache.set(r,i),i}catch(s){if(t=s,!(s instanceof Error?s.message:String(s)).includes("404"))throw s}throw t instanceof Error?t:new Error(String(t))}async getOpenRouterModel(){const r=this.getRequestedModel();if(!r||r==="openrouter/auto")return null;const e=this.getModelCacheKey(),a=l.modelCache.get(e);if(a!==void 0)return a;try{const t=await this.fetchOpenRouterCatalog(),o=this.matchOpenRouterModel(r,t)||null;return l.modelCache.set(e,o),o}catch{return null}}async supportsOpenRouterParameters(r){const e=await this.getOpenRouterModel();return e?r.some(a=>e.supported_parameters?.includes(a)??!1):!1}async isResponseFormatSupported(){return this.supportsOpenRouterParameters(["response_format"])}async isReasoningSupported(){return this.supportsOpenRouterParameters(["reasoning","include_reasoning"])}async getReasoningPayload(){if(!await this.isReasoningSupported())return;const r=this.params.config,e=this.hasRequestObject(r.reasoning)?{...r.reasoning}:void 0,a=e?{...e}:{};return"exclude"in a||(a.exclude=!0),a}getServiceSpecificErrorMessage(r){const e=r.message||"";return e.includes("API key")||e.includes("api_key")?"Invalid API key. Check your OpenRouter API key in configuration":e.includes("402")||e.includes("Payment Required")?"OpenRouter credits are exhausted or billing is required. Check your account balance.":e.includes("rate_limit")||e.includes("Rate limit")?"Rate limit exceeded. Wait a moment and try again, or check your OpenRouter limits":e.includes("model")||e.includes("Model")?"Model not found or not accessible. Check if the OpenRouter model name is correct":e.includes("403")||e.includes("Forbidden")?"Access denied. Your API key may not have permission for this OpenRouter model":e.includes("404")||e.includes("Not Found")?"Model or endpoint not found. Check your OpenRouter configuration":e.includes("500")||e.includes("Internal Server Error")?"OpenRouter server error. Try again later":e.includes("overloaded")||e.includes("capacity")?"OpenRouter is overloaded. Try again in a few minutes":null}generateCommitMessage$(){return this.params.config.stream||!1?this.generateStreamingCommitMessage$():v(this.generateMessage("commit")).pipe(M(e=>w(e)),P(this.formatAsChoice),$(this.handleError$))}generateCodeReview$(){return v(this.generateMessage("review")).pipe(M(r=>w(r)),P(this.formatCodeReviewAsChoice),$(this.handleError$))}async generateMessage(r){const e=this.params.stagedDiff.diff,{logging:a,generate:t,type:o,timeout:s}=this.params.config,i=this.buildPromptOptions(),m=r==="review"?q(i):H(i),c=`Here is the diff: ${e}`,n="OpenRouter",d=`${this.params.config.url||"https://openrouter.ai"}${this.params.config.path||"/api/v1/chat/completions"}`,g={Authorization:`Bearer ${this.params.config.key}`,"Content-Type":"application/json",...this.getOpenRouterHeaders()};x(e,r,n,this.params.config.model,d,g,a),A(e,r,n,m,c,a);const u=await this.buildChatCompletionPayload(m,c,!1);k(e,r,n,u,a);const f=Date.now();try{const p=await this.openAI.chat.completions.create(u,{timeout:s}),h=this.extractOpenRouterText(p.choices?.[0]?.message),R=Date.now()-f;return b(e,r,n,p,a),S(e,r,n,R,h,a),r==="review"?this.parseCodeReview(h):this.parseMessage(h,o,t)}catch(p){throw I(e,r,n,p,a),p}}};O.catalogCache=new Map,O.modelCache=new Map;let F=O;export{F as OpenRouterService};