aicommit2 2.5.11 → 2.5.13

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.
Files changed (37) hide show
  1. package/README.md +36 -0
  2. package/dist/ai.service-f59388bb.mjs +17 -0
  3. package/dist/anthropic.service-fb44d54e.mjs +1 -0
  4. package/dist/bedrock.service-40cbd1e5.mjs +1 -0
  5. package/dist/cli-d193cf90.mjs +273 -0
  6. package/dist/cli.mjs +1 -1
  7. package/dist/codestral.service-f6ae65a5.mjs +1 -0
  8. package/dist/cohere.service-af885cf7.mjs +1 -0
  9. package/dist/copilot-sdk.service-f1cc1873.mjs +5 -0
  10. package/dist/deep-seek.service-de8b4395.mjs +1 -0
  11. package/dist/gemini.service-c9b02d26.mjs +1 -0
  12. package/dist/{github-models.service-7c104b0a.mjs → github-models.service-becd2e94.mjs} +3 -3
  13. package/dist/groq.service-180e7478.mjs +1 -0
  14. package/dist/hugging-face.service-2c8f4576.mjs +2 -0
  15. package/dist/mistral.service-7a553113.mjs +1 -0
  16. package/dist/ollama.service-31b2908b.mjs +1 -0
  17. package/dist/{openai-compatible.service-987d206d.mjs → openai-compatible.service-2bea7313.mjs} +1 -1
  18. package/dist/{openai-22ac0386.mjs → openai-dc366083.mjs} +1 -1
  19. package/dist/{openai.service-fb8b0aec.mjs → openai.service-ca071f6c.mjs} +1 -1
  20. package/dist/openrouter.service-2e239655.mjs +1 -0
  21. package/dist/perplexity.service-fe8bfcd5.mjs +1 -0
  22. package/package.json +4 -2
  23. package/dist/ai.service-1ba3c081.mjs +0 -17
  24. package/dist/anthropic.service-7842f514.mjs +0 -1
  25. package/dist/bedrock.service-65bb3c73.mjs +0 -1
  26. package/dist/cli-e0859d73.mjs +0 -273
  27. package/dist/codestral.service-cd9177f4.mjs +0 -1
  28. package/dist/cohere.service-03179c4c.mjs +0 -1
  29. package/dist/copilot-sdk.service-5c240291.mjs +0 -5
  30. package/dist/deep-seek.service-0c494b66.mjs +0 -1
  31. package/dist/gemini.service-ef22fdae.mjs +0 -1
  32. package/dist/groq.service-ecd1e518.mjs +0 -1
  33. package/dist/hugging-face.service-e6a0100a.mjs +0 -2
  34. package/dist/mistral.service-902039e1.mjs +0 -1
  35. package/dist/ollama.service-d60eeaa2.mjs +0 -1
  36. package/dist/openrouter.service-16a86909.mjs +0 -1
  37. package/dist/perplexity.service-6a36f05d.mjs +0 -1
package/README.md CHANGED
@@ -36,6 +36,7 @@ ______________________________________________________________________
36
36
  - [Git Hooks](#git-hooks)
37
37
  - [Configuration](#configuration)
38
38
  - [General Settings](#general-settings)
39
+ - [Diff Compression](#diff-compression)
39
40
  - [Logging](#logging)
40
41
  - [Custom Prompt Template](#custom-prompt-template)
41
42
  - [Code Review](#code-review)
@@ -82,6 +83,7 @@ _aicommit2_ automatically generates commit messages using AI. It supports [Git](
82
83
  - **[Code Review](#code-review)**: AI-powered structured code review with severity levels before committing
83
84
  - **[Git Hook Integration](#git-hooks)**: Can be used as a prepare-commit-msg hook
84
85
  - **[Custom Prompt](#custom-prompt-template)**: Supports user-defined system prompt templates
86
+ - **[Diff Compression](#diff-compression)**: Reduces token usage by 30-60% with smart diff compression
85
87
 
86
88
  ## Supported Providers
87
89
 
@@ -928,7 +930,12 @@ For detailed information about all available settings, see the [General Settings
928
930
  | `useStats` | Enable usage statistics tracking | true |
929
931
  | `statsDays` | Days to retain statistics data (auto-cleanup) | 30 |
930
932
  | `systemPromptPath` | Path to custom system prompt file | - |
933
+ | `modelNameDisplay` | Model name display in CLI labels (`none` / `short` / `full`) | short |
931
934
  | `stream` | **Experimental.** Enable streaming for real-time commit message generation | false |
935
+ | `diffCompression` | Diff compression mode (`none` / `compact`) | none |
936
+ | `maxHunkLines` | Max lines per hunk in compressed diff (0 = unlimited) | 0 |
937
+ | `maxDiffLines` | Max total lines in compressed diff (0 = unlimited) | 0 |
938
+ | `diffContext` | Number of context lines in git diff (0-10) | 3 |
932
939
 
933
940
  ```bash
934
941
  # Example: Set settings for a specific model
@@ -956,6 +963,33 @@ aicommit2 config set ANTHROPIC.includeBody=true
956
963
  | **Ollama** | ✓ | ✓ | | ✓ | ✓ |
957
964
  | **OpenAI API-Compatible** | ✓ | ✓ | ✓ | ✓ | ✓ |
958
965
 
966
+ ## Diff Compression
967
+
968
+ aicommit2 can compress git diffs before sending to AI providers, reducing token usage by 30-60%. Inspired by [RTK](https://github.com/rtk-ai/rtk)'s token optimization techniques.
969
+
970
+ When enabled (`compact` mode), the compressor:
971
+ - Strips diff metadata headers (`diff --git`, `index`, `---/+++`)
972
+ - Minimizes context lines (keeps only lines adjacent to changes, replaces distant context with `...`)
973
+ - Caps large hunks and total diff size to protect model context windows
974
+
975
+ ```bash
976
+ # Enable diff compression globally
977
+ aicommit2 config set diffCompression=compact
978
+
979
+ # Or per model — useful for models with smaller context windows
980
+ aicommit2 config set OLLAMA.diffCompression=compact
981
+ aicommit2 config set OLLAMA.maxHunkLines=100
982
+ aicommit2 config set OLLAMA.maxDiffLines=500
983
+
984
+ # Tune compression settings
985
+ aicommit2 config set maxHunkLines=200 # max lines per hunk (0=unlimited)
986
+ aicommit2 config set maxDiffLines=1000 # max total diff lines (0=unlimited)
987
+ aicommit2 config set diffContext=1 # reduce git context lines (default: 3)
988
+
989
+ # Disable compression (default)
990
+ aicommit2 config set diffCompression=none
991
+ ```
992
+
959
993
  ## Logging
960
994
 
961
995
  The application utilizes two distinct logging systems to provide comprehensive insights into its operations:
@@ -1234,6 +1268,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
1234
1268
  <td align="center"><a href="https://github.com/denniswebb"><img src="https://avatars.githubusercontent.com/denniswebb" width="100px;" alt=""/><br /><sub><b>@denniswebb</b></sub></a><br /><a href="https://github.com/tak-bro/aicommit2/commits?author=denniswebb" title="Code">💻</a></td>
1235
1269
  <td align="center"><a href="https://github.com/peinan"><img src="https://avatars.githubusercontent.com/peinan" width="100px;" alt=""/><br /><sub><b>@peinan</b></sub></a><br /><a href="https://github.com/tak-bro/aicommit2/issues/215" title="Documentation">📖</a></td>
1236
1270
  <td align="center"><a href="https://github.com/totoroot"><img src="https://avatars.githubusercontent.com/totoroot" width="100px;" alt=""/><br /><sub><b>@totoroot</b></sub></a><br /><a href="https://github.com/tak-bro/aicommit2/commits?author=totoroot" title="Code">💻</a></td>
1271
+ <td align="center"><a href="https://github.com/lawrence3699"><img src="https://avatars.githubusercontent.com/lawrence3699" width="100px;" alt=""/><br /><sub><b>@lawrence3699</b></sub></a><br /><a href="https://github.com/tak-bro/aicommit2/commits?author=lawrence3699" title="Code">💻</a></td>
1272
+ <td align="center"><a href="https://github.com/atlet99"><img src="https://avatars.githubusercontent.com/atlet99" width="100px;" alt=""/><br /><sub><b>@atlet99</b></sub></a><br /><a href="https://github.com/tak-bro/aicommit2/commits?author=atlet99" title="Code">💻</a></td>
1237
1273
  </tr>
1238
1274
  </table>
1239
1275
  <!-- markdownlint-restore -->
@@ -0,0 +1,17 @@
1
+ import{of as L,Observable as _,Subject as J,catchError as B}from"rxjs";import"fs";import"path";import{xxh64 as M}from"@pacote/xxhash";import $ from"winston";import{A as T,l as p,D as F,g as D,s as b,h as U,d as z,E as H,e as q,f as P}from"./cli-d193cf90.mjs";const y=new Map,d=(f,r,e)=>{const o=M(0).update(r).digest("hex").substring(0,8),s=`${f}_${o}_${e}`;if(y.has(s))return y.get(s);const n=new Date,t=Z(n,f,r,e),c=`${T}/${t}`,i=$.createLogger({level:"info",format:$.format.combine($.format.timestamp({format:"YYYY-MM-DDTHH:mm:ss.SSSZ"}),$.format.printf(({timestamp:u,level:l,message:a,...m})=>m&&Object.keys(m).length>0?`[${u}] ${l}: ${a} ${JSON.stringify(m,null,2)}`:`[${u}] ${l}: ${a}`)),transports:[new $.transports.File({filename:c})]});return i.info(`=== ${f.toUpperCase()} AI SERVICE LOG ===`),i.info(`Diff Hash: ${o}`),i.info(`Request Type: ${e.toUpperCase()}`),i.info(`Start Time: ${n.toISOString()}`),i.info("=".repeat(50)),i.info(""),y.set(s,i),i},K=f=>{const r={...f},e=["authorization","x-api-key","x-goog-api-key","api-key","x-amzn-bedrock-application-key"];for(const o of e){const s=o.toLowerCase(),n=Object.keys(r).find(t=>t.toLowerCase()===s);n&&r[n]&&typeof r[n]=="string"&&(r[n].startsWith("Bearer ")?r[n]="Bearer [MASKED]":r[n]="[MASKED]")}return r},Y=(f,r,e,o,s,n,t=!0)=>{if(!t)return;const c=d(e,f,r);c.info(`Making request to ${e} API with model: ${o}`),c.info(`Request URL: ${s}`),c.info("Request headers:",K(n))},V=(f,r,e,o,s=!0)=>{if(!s)return;d(e,f,r).info("Request payload:",o)},W=(f,r,e,o,s,n=!0)=>{if(!n)return;const t=d(e,f,r);t.info("System prompt:",{prompt:o}),t.info("User prompt:",{prompt:s})},G=(f,r,e,o,s=!0)=>{if(!s)return;d(e,f,r).info("Response received:",o)},Q=(f,r,e,o,s=!0)=>{if(!s)return;d(e,f,r).error("API request failed:",o)},R=(f,r,e,o,s,n=!0)=>{if(!n)return;const t=d(e,f,r);o?t.info(`Request completed successfully in ${o}ms`):t.info("Request completed successfully"),s&&t.info("Final processed response:",{response:s}),t.info(""),t.info("=".repeat(50)),t.info(`End Time: ${new Date().toISOString()}`),t.info("=== REQUEST COMPLETED ===")},X=(f,r,e,o,s,n,t,c=!0)=>{if(!c)return;const i=d(e,f,r);t?i.error(`Request failed after ${n}ms:`,{error:t}):(i.info(`Request completed in ${n}ms`),i.info("Response:",{response:s})),R(f,r,e,n,s,c)},Z=(f,r,e,o)=>{const{year:s,month:n,day:t,hours:c,minutes:i,seconds:u}=ee(f),a=M(0).update(e).digest("hex").substring(0,8),m=r.toLowerCase().replace(/[^a-z0-9]/g,"").substring(0,20);return o==="review"?`${s}-${n}-${t}_${c}-${i}-${u}_${a}_${m}_review.log`:`${s}-${n}-${t}_${c}-${i}-${u}_${a}_${m}_commit.log`},ee=f=>{const r=f.getFullYear().toString(),e=(f.getMonth()+1).toString().padStart(2,"0"),o=f.getDate().toString().padStart(2,"0"),s=f.getHours().toString().padStart(2,"0"),n=f.getMinutes().toString().padStart(2,"0"),t=f.getSeconds().toString().padStart(2,"0");return{year:r,month:e,day:o,hours:s,minutes:n,seconds:t}},w=()=>{for(const[f,r]of y.entries())try{r.close()}catch(e){console.error(`Failed to close logger ${f}:`,e)}y.clear()};process.on("exit",w),process.on("SIGINT",()=>{w(),process.exit(0)}),process.on("SIGTERM",()=>{w(),process.exit(0)});class te{constructor(){this.buffer="",this.arrayStartFound=!1,this.scanPosition=0,this.feed=r=>{this.buffer+=r;const e=[];if(!this.arrayStartFound){const o=this.buffer.indexOf("[");if(o===-1)return e;this.arrayStartFound=!0,this.scanPosition=o+1}for(;;){const o=this.buffer.indexOf("{",this.scanPosition);if(o===-1)break;const s=this.extractBalancedBraces(o);if(!s)break;this.scanPosition=o+s.length;const n=this.tryParseCommitMessage(s);n&&e.push(n)}return e},this.flush=()=>this.feed(""),this.getBuffer=()=>this.buffer,this.getUnparsedBuffer=()=>this.buffer.slice(this.scanPosition),this.extractBalancedBraces=r=>{let e=0,o=!1,s=!1;for(let n=r;n<this.buffer.length;n++){const t=this.buffer[n];if(s){s=!1;continue}if(t==="\\"&&o){s=!0;continue}if(t==='"'){o=!o;continue}if(!o&&(t==="{"&&e++,t==="}"&&e--,e===0))return this.buffer.slice(r,n+1)}return null},this.tryParseCommitMessage=r=>{try{const e=JSON.parse(r);return typeof e.subject!="string"?null:{subject:e.subject,body:typeof e.body=="string"?e.body:void 0,footer:typeof e.footer=="string"?e.footer:void 0}}catch{return null}}}}class se{constructor(r){this.formatModelSuffix=()=>{const e=this.params.modelNameDisplay??"short",o=this.params.config.model;if(e==="none"||!o)return"";const s=Array.isArray(o)?o[0]:String(o);if(e==="full")return`/${s}`;const n=s.split("/").pop()||s;return n.length>20?`/${n.slice(0,19)}\u2026`:`/${n}`},this.handleError$=e=>{const o=this.getDetailedErrorMessage(e),s=e.status?`HTTP ${e.status}: ${o}`:o;if(this.params.config.logging){const n=this.params.stagedDiff.diff,t=this.serviceName.replace(/\[|\]/g,"").trim();X(n,"commit",t,"Error occurred","",void 0,s)}return p.error(`${this.errorPrefix} ${s}`),e.stack&&p.error(` ${e.stack}`),e.content&&p.error(` Problematic content: ${e.content}`),e.originalError&&p.error(` Original error: ${e.originalError}`),L({name:`${this.errorPrefix} ${s}`,value:s,isError:!0,disabled:!0})},this.extractJsonObjectFromResponse=e=>{const o=e.indexOf("{");return o!==-1?this.extractBalancedJson(e,o,"{","}"):null},this.buildCommitPrompt=()=>{const{systemPrompt:e,systemPromptPath:o,codeReviewPromptPath:s,locale:n,generate:t,type:c,maxLength:i}=this.params.config,u={...F,locale:n,maxLength:i,type:c,generate:t,systemPrompt:e,systemPromptPath:o,codeReviewPromptPath:s,vcs_branch:this.params.branchName||""};return D(u)},this.formatRawCommitMessage=(e,o)=>{const s=this.extractMessageAsType(e,o),n=s.subject,t=`${s.subject}${s.body?`
2
+
3
+ ${s.body}`:""}${s.footer?`
4
+
5
+ ${s.footer}`:""}`;return{title:n,value:t}},this.formatAsChoice=e=>({name:`${this.serviceName} ${e.title}`,short:e.title,value:this.params.config.includeBody?e.value:e.title,description:this.params.config.includeBody?e.value:"",isError:!1}),this.extractStreamPreview=e=>{const o=e.match(/"subject"\s*:\s*"((?:[^"\\]|\\.)*)(?:"|$)/),s=e.match(/"body"\s*:\s*"((?:[^"\\]|\\.)*)(?:"|$)/),n=o?o[1].replace(/\\"/g,'"').replace(/\\n/g,`
6
+ `):"",t=s?s[1].replace(/\\"/g,'"').replace(/\\n/g,`
7
+ `):"";return{subject:n,body:t}},this.createStreamingCommitMessages$=(e,o,s)=>{const n=`stream-${this.serviceName}-${Date.now()}`;return new _(t=>{const c=new te,i=new J;let u=0,l=!1,a=0;const m=100,A="streaming",C={name:"",value:"",streamKey:n,disabled:!0,isError:!1},x=(g=!1)=>{const h=Date.now();if(!g&&h-a<m)return;a=h;const S=c.getUnparsedBuffer();if(!S.trim())return;const{subject:v,body:j}=this.extractStreamPreview(S),N=v?`${this.serviceName} ${v}`:`${this.serviceName} Generating...`,O=j||v||"",k={name:N,short:v||"Generating...",value:`__streaming__${n}`,description:O,disabled:A,isError:!1,streamKey:n};t.next(k),l=!0},E=g=>{for(const h of g){if(u>=s)break;t.next(this.formatAsChoice(this.formatRawCommitMessage(h,o))),u++}},I=i.subscribe({next:g=>{if(u>=s)return;const h=c.feed(g);E(h),u<s&&x()},error:g=>{l&&t.next(C),t.error(g)},complete:()=>{E(c.flush()),l&&t.next(C);const g=c.getBuffer();if(u===0&&g.trim())try{const h=this.parseMessage(g,o,s);for(const S of h)t.next(this.formatAsChoice(S))}catch(h){t.error(h);return}t.complete()}});return e(i),()=>{I.unsubscribe()}}).pipe(B(this.handleError$))},this.parseCodeReview=e=>{const o=this.cleanJsonCodeBlock(e),s=this.extractJsonObjectFromResponse(o);if(!s)return p.warn(`${this.serviceName} Code review response did not contain JSON, falling back to plain text`),this.sanitizeResponse(e);const n=b(s);if(!n.ok)return p.warn(`${this.serviceName} Failed to parse code review JSON, falling back to plain text`),this.sanitizeResponse(e);const t=n.data;if(!(t&&typeof t.summary=="string"&&Array.isArray(t.items)))return p.warn(`${this.serviceName} Code review JSON missing summary or items, falling back to plain text`),this.sanitizeResponse(e);const i=t.items.filter(m=>typeof m.title=="string"&&typeof m.severity=="string"&&typeof m.description=="string"&&typeof m.category=="string"),u=i.some(m=>m.severity==="critical"),l=this.formatReviewSummaryTitle(t.summary,i),a=this.formatReviewAsMarkdown(t.summary,i,u);return this.isLoggingEnabled()&&p.info(`${this.serviceName} Parsed code review: ${i.length} items`),[{title:l,value:a}]},this.formatCodeReviewAsChoice=e=>({name:`${this.serviceName} ${e.title}`,short:e.title,value:e.value,description:e.value,isError:!1}),this.formatReviewSummaryTitle=(e,o)=>{const s=o.reduce((l,a)=>(l[a.severity]=(l[a.severity]??0)+1,l),{}),t=["critical","warning","suggestion","praise"].filter(l=>s[l]).map(l=>`${s[l]} ${l}`),c=t.length>0?` (${t.join(", ")})`:"",i=60;return`${e.length>i?`${e.slice(0,i-3)}...`:e}${c}`},this.formatReviewAsMarkdown=(e,o,s)=>{const t=[`## Summary
8
+ ${e}${s?`
9
+ <!-- HAS_CRITICAL_ISSUES -->
10
+ `:""}
11
+ `],c=["critical","warning","suggestion","praise"];for(const i of c){const u=o.filter(l=>l.severity===i);if(u.length!==0){t.push(`### [${i.toUpperCase()}] ${i.charAt(0).toUpperCase()}${i.slice(1)}
12
+ `);for(const l of u){const a=l.file?` (${l.file}${l.line?`:${l.line}`:""})`:"";t.push(`- **[${l.category}]** ${l.title}${a}`),t.push(` ${l.description}`),l.suggestion&&t.push(` > Suggestion: ${l.suggestion}`),t.push("")}}}return t.join(`
13
+ `)},this.serviceName="AI",this.errorPrefix="ERROR",this.colors={primary:""},this.params=r,this.logSessionId=r.logSessionId}getProviderName(){const r=String.fromCharCode(27),e=new RegExp(`${r}\\[[0-9;]*m`,"g");return this.serviceName.replace(e,"").replace(/\[|\]/g,"").trim()}getDetailedErrorMessage(r){const e=r.message||"",o=this.getProviderName(),s=this.params.config.model?.[0],n=this.params.config.timeout,t=this.getServiceSpecificErrorMessage(r);if(t)return t;const c=r.code||(r.status?U(r.status):z(e));return c!==H.UNKNOWN?q(c,{provider:o,model:s,timeout:n}):e||"Unknown error occurred"}getServiceSpecificErrorMessage(r){return null}cleanJsonCodeBlock(r){const e=/```(?:json|JSON)?\s*([\s\S]*?)\s*```/,o=r.match(e);return o?o[1].trim():r}extractJsonFromResponse(r){const e=[],o=r.indexOf("[");o!==-1&&e.push({startIndex:o,openChar:"[",closeChar:"]"});const s=r.indexOf("{");s!==-1&&e.push({startIndex:s,openChar:"{",closeChar:"}"}),e.sort((n,t)=>n.startIndex-t.startIndex);for(const n of e){const t=this.extractBalancedJson(r,n.startIndex,n.openChar,n.closeChar);if(t&&b(t).ok)return t}return null}extractBalancedJson(r,e,o,s){let n=0,t=!1,c=!1;for(let i=e;i<r.length;i++){const u=r[i];if(c){c=!1;continue}if(u==="\\"&&t){c=!0;continue}if(u==='"'){t=!t;continue}if(!t&&(u===o&&n++,u===s&&n--,n===0))return r.slice(e,i+1)}return null}parseMessage(r,e,o){const s=this.cleanJsonCodeBlock(r),n=this.extractJsonFromResponse(s);if(!n){const a=new Error("AI response did not contain a valid JSON object or array.");throw a.name="InvalidJsonResponse",a.content=r,a}const t=b(n);if(!t.ok){const a=new Error("Failed to parse AI response as JSON");throw a.name="JsonParseError",a.content=n,a.originalError=t.error,a}const c=t.data,i=Array.isArray(c)?c:[c];if(!i.length||!i.every(a=>typeof a.subject=="string")){const a=new Error("AI response contained malformed commit message data.");throw a.name="MalformedCommitMessage",a.content=r,a}const l=i.map(a=>this.extractMessageAsType(a,e)).map(a=>({title:`${a.subject}`,value:`${a.subject}${a.body?`
14
+
15
+ ${a.body}`:""}${a.footer?`
16
+
17
+ ${a.footer}`:""}`})).slice(0,o);if(this.isLoggingEnabled()){const a=l.map(m=>m.title).join(", ");p.info(`${this.serviceName} Parsed ${l.length} commit messages: ${a}`)}return l}extractMessageAsType(r,e){switch(e){case"conventional":const o=/(\w+)(?:\(.*?\))?:\s*(.*)/,s=r.subject.match(o),n=s?s[0]:r.subject;return{...r,subject:this.normalizeCommitMessage(n)};case"gitmoji":const t=/:\w*:\s*(.*)/,c=r.subject.match(t),i=this.params.config.disableLowerCase??!1;return{...r,subject:c&&!i?c[0].toLowerCase():r.subject};default:return r}}normalizeCommitMessage(r){const e=/^(\w+)(\(.*?\))?:\s(.*)$/,o=r.match(e);if(o){const[,s,n,t]=o,c=this.params.config.disableLowerCase??!1,i=s.toLowerCase(),u=c?t:t.charAt(0).toLowerCase()+t.slice(1);r=`${i}${n||""}: ${u}`}return r}sanitizeResponse(r){if(typeof r=="string")try{return[{title:`${P(r)}...`,value:r}]}catch{return[]}return r.map(e=>{try{return{title:`${P(e)}...`,value:e}}catch{return{title:"",value:""}}})}isLoggingEnabled(){return this.params.config.logging&&!!this.logSessionId}}export{se as A,W as a,V as b,G as c,R as d,Q as e,Y as l};
@@ -0,0 +1 @@
1
+ import j from"@anthropic-ai/sdk";import E from"chalk";import{concatMap as $,from as b,map as D,catchError as R}from"rxjs";import{fromPromise as U}from"rxjs/internal/observable/innerFrom";import{A as B,l as F,a as O,b as L,c as T,d as N,e as W}from"./ai.service-f59388bb.mjs";import{D as z,g as H,b as G,k as K}from"./cli-d193cf90.mjs";import"fs";import"path";import"@pacote/xxhash";import"winston";import"cleye";import"module";import"crypto";import"os";import"node:buffer";import"node:path";import"node:child_process";import"node:process";import"child_process";import"node:url";import"node:os";import"assert";import"events";import"node:fs";import"buffer";import"stream";import"util";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"url";import"node:fs/promises";import"chokidar";import"rxjs/operators";const J=["claude-4","claude-haiku-4","claude-sonnet-4","claude-opus-4"],X=I=>{const e=I.toLowerCase();return J.some(t=>e===t||e.startsWith(`${t}-`)||e.startsWith(`${t}.`))},Q=10*60*1e3;class V extends B{constructor(e){super(e),this.params=e,this.generateStreamingCommitMessage$=()=>{const{generate:t,type:o}=this.params.config;return this.createStreamingCommitMessages$(n=>{this.streamChunks(n).catch(c=>n.error(c))},o,t)},this.streamChunks=async t=>{const o=this.params.stagedDiff.diff,{systemPrompt:n,systemPromptPath:c,codeReviewPromptPath:s,logging:i,temperature:P,locale:l,generate:d,type:y,maxLength:M,maxTokens:C,topP:p,model:m}=this.params.config,u={...z,locale:l,maxLength:M,type:y,generate:d,systemPrompt:n,systemPromptPath:c,codeReviewPromptPath:s,vcs_branch:this.params.branchName||""},h=H(u),k=K(o,"commit"),v=`${this.params.config.url||"https://api.anthropic.com"}/v1/messages`,w={"Content-Type":"application/json","x-api-key":this.params.config.key,"anthropic-version":"2023-06-01"};F(o,"commit","Anthropic",m,v,w,i),O(o,"commit","Anthropic",h,k,i);const g=X(m),f={max_tokens:C,temperature:P,system:h,messages:[{role:"user",content:k}],model:m,stream:!0,...g?{}:{top_p:p}};L(o,"commit","Anthropic",f,i);const a=Date.now();let A="";try{const r=this.anthropic.messages.stream(f);r.on("text",_=>{A+=_,t.next(_)});const x=await r.finalMessage(),Y=Date.now()-a;T(o,"commit","Anthropic",x,i),N(o,"commit","Anthropic",Y,A,i),t.complete()}catch(r){W(o,"commit","Anthropic",r,i),t.error(r)}},this.colors={primary:"#AE5630",secondary:"#fff"},this.serviceName=E.bgHex(this.colors.primary).hex(this.colors.secondary).bold(`[Anthropic${this.formatModelSuffix()}]`),this.errorPrefix=E.red.bold(`[Anthropic${this.formatModelSuffix()}]`),this.anthropic=new j({apiKey:this.params.config.key,...this.params.config.timeout>Q&&{timeout:this.params.config.timeout}})}getServiceSpecificErrorMessage(e){const t=e.message||"";return t.includes("API key")||t.includes("api_key")?"Invalid API key. Check your Anthropic API key in configuration":t.includes("quota")||t.includes("usage")?"API quota exceeded. Check your Anthropic usage limits":t.includes("model_not_found")||t.includes("Model not found")||/model.*does not exist/i.test(t)?"Model not found or not accessible. Check if the Claude model name is correct":t.includes("403")||t.includes("Forbidden")?"Access denied. Your API key may not have permission for this Claude model":t.includes("404")||t.includes("Not Found")?"Model or endpoint not found. Check your Claude model configuration":t.includes("500")||t.includes("Internal Server Error")?"Anthropic server error. Try again later":null}generateCommitMessage$(){return this.params.config.stream||!1?this.generateStreamingCommitMessage$():U(this.generateMessage("commit")).pipe($(t=>b(t)),D(this.formatAsChoice),R(this.handleError$))}generateCodeReview$(){return U(this.generateMessage("review")).pipe($(e=>b(e)),D(this.formatCodeReviewAsChoice),R(this.handleError$))}async generateMessage(e){const t=this.params.stagedDiff.diff,{systemPrompt:o,systemPromptPath:n,codeReviewPromptPath:c,logging:s,temperature:i,locale:P,generate:l,type:d,maxLength:y,maxTokens:M,topP:C,model:p}=this.params.config,m={...z,locale:P,maxLength:y,type:d,generate:l,systemPrompt:o,systemPromptPath:n,codeReviewPromptPath:c,vcs_branch:this.params.branchName||""},u=e==="review"?G(m):H(m),h=K(t,e),S=`${this.params.config.url||"https://api.anthropic.com"}/v1/messages`,v={"Content-Type":"application/json","x-api-key":this.params.config.key,"anthropic-version":"2023-06-01"};F(t,e,"Anthropic",p,S,v,s),O(t,e,"Anthropic",u,h,s);const w=X(p),g={max_tokens:M,temperature:i,system:u,messages:[{role:"user",content:h}],model:p,...w?{}:{top_p:C}};L(t,e,"Anthropic",g,s);const f=Date.now();try{const a=await this.anthropic.messages.create(g),A=Date.now()-f;T(t,e,"Anthropic",a,s);const r=a.content.map(({text:x})=>x).join("");return N(t,e,"Anthropic",A,r,s),e==="review"?this.parseCodeReview(r):this.parseMessage(r,d,l)}catch(a){throw W(t,e,"Anthropic",a,s),a}}}export{V as AnthropicService};
@@ -0,0 +1 @@
1
+ import F from"https";import h from"chalk";import{concatMap as $,from as T,map as x,catchError as L}from"rxjs";import{fromPromise as W}from"rxjs/internal/observable/innerFrom";import{A as q,l as Y,a as H,b as z,d as V,e as _,c as K}from"./ai.service-f59388bb.mjs";import{D as J,b as j,g as Q,p as O,s as X,k as Z}from"./cli-d193cf90.mjs";import"fs";import"path";import"@pacote/xxhash";import"winston";import"cleye";import"module";import"crypto";import"os";import"node:buffer";import"node:path";import"node:child_process";import"node:process";import"child_process";import"node:url";import"node:os";import"assert";import"events";import"node:fs";import"buffer";import"stream";import"util";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"url";import"node:fs/promises";import"chokidar";import"rxjs/operators";const g="Bedrock",S={MISSING_DEPENDENCY:"MissingDependencyError",MISSING_REGION:"MissingRegionError",MISSING_MODEL_ID:"MissingModelIdError",MISSING_API_KEY:"MissingApiKeyError",MISSING_APPLICATION_KEY:"MissingApplicationKeyError",INVALID_RESPONSE:"InvalidResponseError",EMPTY_RESPONSE:"EmptyResponseError"},i=f=>typeof f=="string"&&f.length>0;let M=null,v=null;const G=f=>{const e=new Error('Amazon Bedrock support requires "@aws-sdk/client-bedrock-runtime" and "@aws-sdk/credential-providers". Install them with `pnpm add @aws-sdk/client-bedrock-runtime @aws-sdk/credential-providers`.');return e.name=S.MISSING_DEPENDENCY,e.originalError=f,e},ee=async()=>{if(M)return M;try{return M=await import("@aws-sdk/client-bedrock-runtime"),M}catch(f){throw M=null,G(f)}},U=async()=>{if(v)return v;try{return v=await import("@aws-sdk/credential-providers"),v}catch(f){throw v=null,G(f)}};class oe extends q{constructor(e){super(e),this.params=e,this.credentialCache=void 0,this.credentialCacheTimestamp=0,this.CREDENTIAL_CACHE_TTL=5*60*1e3,this.bedrockConfig=this.params.config,this.colors={primary:"#232F3E",secondary:"#FF9900"},this.serviceName=h.bgHex(this.colors.primary).hex(this.colors.secondary).bold(`[${g}${this.formatModelSuffix()}]`),this.errorPrefix=h.red.bold(`[${g}${this.formatModelSuffix()}]`),this.validateConfiguration()}validateConfiguration(){const e=this.bedrockConfig;if(!i(e.model)){const t=new Error("Model ID or inference profile ARN is required.");throw t.name=S.MISSING_MODEL_ID,t}if(!this.getRegion()){const t=new Error("AWS region is required. Configure BEDROCK.region or set AWS_REGION/AWS_DEFAULT_REGION.");throw t.name=S.MISSING_REGION,t}const r=i(e.key),o=this.canUseAwsSdk();if(!r&&!o){const t=new Error("Authentication required: Configure AWS credentials (profile, access keys, IAM role) or API key (BEDROCK.key).");throw t.name=S.MISSING_API_KEY,t}if(r&&!o&&!this.getRegion()&&!i(e.applicationBaseUrl)){const t=new Error("Bearer token authentication requires region or applicationBaseUrl to construct endpoint.");throw t.name=S.MISSING_REGION,t}}canUseAwsSdk(){const e=this.bedrockConfig,r=i(e.profile)||i(process.env.AWS_PROFILE),o=i(e.accessKeyId)&&i(e.secretAccessKey)||i(process.env.AWS_ACCESS_KEY_ID)&&i(process.env.AWS_SECRET_ACCESS_KEY);return r||o}determineAuthMethod(){const e=i(this.bedrockConfig.key);if(this.canUseAwsSdk())return"aws-sdk";if(e)return"bearer-token";throw new Error("No authentication method configured")}getServiceSpecificErrorMessage(e){const r=e?.name||e.code,o=e.message||"";switch(r){case"UnrecognizedClientException":case"InvalidSignatureException":return"Authentication with AWS failed. Check your IAM credentials or Bedrock API key settings.";case"AccessDeniedException":return"Access denied. Ensure the IAM principal or application key has permission to invoke the Bedrock resource.";case"ValidationException":return"Invalid request for the selected Bedrock model. Verify the model ID and payload.";case"ResourceNotFoundException":return"The specified Bedrock model, endpoint, or inference profile could not be found.";case"ThrottlingException":return"Request throttled by Bedrock. Reduce request rate or check service quotas."}return o.includes("Region")?"AWS region is required for Bedrock. Configure BEDROCK.region or set AWS_REGION/AWS_DEFAULT_REGION.":null}generateCommitMessage$(){return W(this.generateMessage("commit")).pipe($(e=>T(e)),x(this.formatAsChoice),L(this.handleError$))}generateCodeReview$(){return W(this.generateMessage("review")).pipe($(e=>T(e)),x(this.formatCodeReviewAsChoice),L(this.handleError$))}async generateMessage(e){const r=this.params.stagedDiff.diff,o=this.bedrockConfig,t=o.model,{logging:c,inferenceParameters:n}=o,s={...J,locale:o.locale,maxLength:o.maxLength,type:o.type,generate:o.generate,systemPrompt:o.systemPrompt,systemPromptPath:o.systemPromptPath,codeReviewPromptPath:o.codeReviewPromptPath},a=e==="review"?j(s):Q(s),m=Z(r,e),y={region:this.getRegion(),profile:o.profile,modelId:t},w=`https://bedrock-runtime.${this.getRegion()||"unknown"}.amazonaws.com/model/${encodeURIComponent(t)}/converse`;Y(r,e,g,t,w,y,c),H(r,e,g,a,m,c);const R={modelId:t,systemPrompt:a,userPrompt:m,...n&&Object.keys(n).length>0&&{inferenceConfig:n}};z(r,e,g,R,c);const u=Date.now();try{const l=this.determineAuthMethod();O()&&(console.log(h.cyan(`[Bedrock] Authentication method: ${l}`)),console.log(h.cyan(`[Bedrock] Model ID: ${t}`)),console.log(h.cyan(`[Bedrock] Region: ${this.getRegion()}`)),console.log(h.cyan(`[Bedrock] Has AWS credentials: ${this.canUseAwsSdk()}`)),console.log(h.cyan(`[Bedrock] Has API key: ${i(o.key)}`)));const E=l==="bearer-token"?await this.invokeWithBearerToken({model:t,systemPrompt:a,userPrompt:m,logging:c,requestType:e,diff:r,inferenceConfig:n}):await this.invokeWithAwsSdk({model:t,systemPrompt:a,userPrompt:m,logging:c,requestType:e,diff:r,inferenceConfig:n}),I=Date.now()-u;return V(r,e,g,I,E,c),e==="review"?this.parseCodeReview(E):this.parseMessage(E,o.type,o.generate)}catch(l){throw _(r,e,g,l,c),l instanceof Error&&(l.status=l?.status||l?.$metadata?.httpStatusCode),l}}async invokeWithAwsSdk(e){const r=this.getRegion(),{model:o,systemPrompt:t,userPrompt:c,inferenceConfig:n,logging:s,requestType:a,diff:m}=e,{BedrockRuntimeClient:y,ConverseCommand:w}=await ee(),R=this.bedrockConfig,u={region:r,requestHandler:{requestTimeout:R.timeout||12e4}},l=await this.resolveCredentials();l&&(u.credentials=l);const E=new y(u),I=new w({modelId:o,messages:[{role:"user",content:[{text:c}]}],...t?{system:[{text:t}]}:{},...n&&Object.keys(n).length>0&&{inferenceConfig:n}});O()&&console.log(h.cyan(`[Bedrock] Sending ConverseCommand with modelId: ${o}`));let C;try{C=await E.send(I),K(m,a,g,C,s)}catch(d){throw O()&&(console.error(h.red(`[Bedrock] AWS SDK Error: ${d.name}`)),console.error(h.red(`[Bedrock] Error message: ${d.message}`)),d.$metadata&&(console.error(h.red(`[Bedrock] Request ID: ${d.$metadata.requestId}`)),console.error(h.red(`[Bedrock] HTTP Status: ${d.$metadata.httpStatusCode}`))),d.$fault&&console.error(h.red(`[Bedrock] Fault: ${d.$fault}`))),d}const A=C.output?.message?.content?.[0]?.text||"";if(!A){const d=new Error("No text content found in Bedrock response.");throw d.name=S.EMPTY_RESPONSE,d.content=C,d}return A}invokeWithBearerToken(e){const{model:r,systemPrompt:o,userPrompt:t,inferenceConfig:c,logging:n,requestType:s,diff:a}=e,m=this.bedrockConfig,y=this.getRegion(),w=encodeURIComponent(r),R=m.applicationBaseUrl||`https://bedrock-runtime.${y}.amazonaws.com/model/${w}/converse`,u=new URL(R),l={modelId:r,messages:[{role:"user",content:[{text:t}]}],...c&&Object.keys(c).length>0&&{inferenceConfig:c}};o&&(l.system=[{text:o}]);const E=JSON.stringify(l),I={"Content-Type":"application/json","Content-Length":Buffer.byteLength(E).toString()};return i(m.key)&&(I.Authorization=`Bearer ${m.key}`),i(m.applicationInferenceProfileArn)&&(I["x-amzn-bedrock-inference-profile-arn"]=m.applicationInferenceProfileArn),i(m.applicationEndpointId)&&(I["x-amzn-bedrock-endpoint-id"]=m.applicationEndpointId),new Promise((C,A)=>{const d=F.request({method:"POST",protocol:u.protocol,hostname:u.hostname,port:u.port,path:u.pathname+u.search,headers:I,timeout:m.timeout},k=>{const P=[];k.on("data",N=>P.push(N)),k.on("end",()=>{const N=Buffer.concat(P).toString("utf8");if(k.statusCode&&k.statusCode>=400){const p=new Error(`Bedrock application endpoint responded with status ${k.statusCode}.`);return p.status=k.statusCode,p.content=N,_(a,s,g,p,n),A(p)}const D=X(N);if(!D.ok){const p=new Error("Failed to parse Bedrock application response as JSON. The Bedrock Converse API should always return valid JSON.");return p.name=S.INVALID_RESPONSE,p.content=N,_(a,s,g,p,n),A(p)}const B=D.data;K(a,s,g,B,n);const b=B.output?.message?.content?.[0]?.text||"";if(!b){const p=new Error("No text content found in Bedrock response.");return p.name=S.EMPTY_RESPONSE,p.content=B,_(a,s,g,p,n),A(p)}C(b)})});d.on("error",k=>{const P=k;_(a,s,g,P,n),A(P)}),d.write(E),d.end()})}getRegion(){return this.bedrockConfig.region||process.env.AWS_REGION||process.env.AWS_DEFAULT_REGION||""}async resolveCredentials(){const e=Date.now();if(this.credentialCache&&e-this.credentialCacheTimestamp<this.CREDENTIAL_CACHE_TTL)return this.credentialCache;const r=this.bedrockConfig,o=r.profile,t=r.accessKeyId,c=r.secretAccessKey,n=r.sessionToken;let s;if(i(o)){const{fromIni:a}=await U();s=a({profile:o})}else if(i(t)&&i(c))s=async()=>({accessKeyId:t,secretAccessKey:c,sessionToken:n||process.env.AWS_SESSION_TOKEN||void 0});else if(process.env.AWS_PROFILE){const{fromIni:a}=await U();s=a({profile:process.env.AWS_PROFILE})}return s&&(this.credentialCache=s,this.credentialCacheTimestamp=e),s}}export{oe as BedrockService};