commit-analyzer 1.0.3 → 1.1.1
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/.claude/settings.local.json +11 -1
- package/README.md +33 -2
- package/commits.csv +2 -0
- package/eslint.config.mts +45 -0
- package/package.json +18 -9
- package/src/1.domain/analysis.ts +93 -0
- package/src/1.domain/analyzed-commit.ts +97 -0
- package/src/1.domain/application-error.ts +32 -0
- package/src/1.domain/category.ts +52 -0
- package/src/1.domain/commit-analysis-service.ts +92 -0
- package/src/1.domain/commit-hash.ts +40 -0
- package/src/1.domain/commit.ts +99 -0
- package/src/1.domain/date-formatting-service.ts +81 -0
- package/src/1.domain/date-range.ts +76 -0
- package/src/1.domain/report-generation-service.ts +292 -0
- package/src/2.application/analyze-commits.usecase.ts +307 -0
- package/src/2.application/generate-report.usecase.ts +209 -0
- package/src/2.application/llm-service.ts +54 -0
- package/src/2.application/resume-analysis.usecase.ts +123 -0
- package/src/3.presentation/analysis-repository.interface.ts +27 -0
- package/src/3.presentation/analyze-command.ts +128 -0
- package/src/3.presentation/cli-application.ts +255 -0
- package/src/3.presentation/command-handler.interface.ts +4 -0
- package/src/3.presentation/commit-analysis-controller.ts +101 -0
- package/src/3.presentation/commit-repository.interface.ts +47 -0
- package/src/3.presentation/console-formatter.ts +129 -0
- package/src/3.presentation/progress-repository.interface.ts +49 -0
- package/src/3.presentation/report-command.ts +50 -0
- package/src/3.presentation/resume-command.ts +59 -0
- package/src/3.presentation/storage-repository.interface.ts +33 -0
- package/src/3.presentation/storage-service.interface.ts +32 -0
- package/src/3.presentation/version-control-service.interface.ts +46 -0
- package/src/4.infrastructure/cache-service.ts +271 -0
- package/src/4.infrastructure/cached-analysis-repository.ts +46 -0
- package/src/4.infrastructure/claude-llm-adapter.ts +124 -0
- package/src/4.infrastructure/csv-service.ts +252 -0
- package/src/4.infrastructure/file-storage-repository.ts +108 -0
- package/src/4.infrastructure/file-system-storage-adapter.ts +87 -0
- package/src/4.infrastructure/gemini-llm-adapter.ts +46 -0
- package/src/4.infrastructure/git-adapter.ts +143 -0
- package/src/4.infrastructure/git-commit-repository.ts +85 -0
- package/src/4.infrastructure/json-progress-tracker.ts +182 -0
- package/src/4.infrastructure/llm-adapter-factory.ts +26 -0
- package/src/4.infrastructure/llm-adapter.ts +455 -0
- package/src/4.infrastructure/llm-analysis-repository.ts +38 -0
- package/src/4.infrastructure/openai-llm-adapter.ts +57 -0
- package/src/di.ts +109 -0
- package/src/main.ts +63 -0
- package/src/utils/app-paths.ts +36 -0
- package/src/utils/concurrency.ts +81 -0
- package/src/utils.ts +77 -0
- package/tsconfig.json +7 -1
- package/src/cli.ts +0 -170
- package/src/csv-reader.ts +0 -180
- package/src/csv.ts +0 -40
- package/src/errors.ts +0 -49
- package/src/git.ts +0 -112
- package/src/index.ts +0 -395
- package/src/llm.ts +0 -411
- package/src/progress.ts +0 -84
- package/src/report-generator.ts +0 -286
- package/src/types.ts +0 -24
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { execSync } from "child_process"
|
|
2
|
+
|
|
3
|
+
import { IVersionControlService } from "@presentation/version-control-service.interface"
|
|
4
|
+
|
|
5
|
+
import { getErrorMessage } from "../utils"
|
|
6
|
+
|
|
7
|
+
export class GitAdapter implements IVersionControlService {
|
|
8
|
+
private static readonly LARGE_DIFF_BUFFER = 50 * 1024 * 1024 // 50MB buffer for large diffs
|
|
9
|
+
private static readonly EXEC_OPTIONS = {
|
|
10
|
+
encoding: "utf8" as const,
|
|
11
|
+
stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"],
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getCommitInfo(hash: string): Promise<{
|
|
15
|
+
hash: string
|
|
16
|
+
message: string
|
|
17
|
+
date: Date
|
|
18
|
+
diff: string
|
|
19
|
+
}> {
|
|
20
|
+
try {
|
|
21
|
+
const showOutput = execSync(
|
|
22
|
+
`git show --format="%H|%s|%ci" --no-patch "${hash}"`,
|
|
23
|
+
GitAdapter.EXEC_OPTIONS,
|
|
24
|
+
).trim()
|
|
25
|
+
|
|
26
|
+
const [fullHash, message, dateStr] = showOutput.split("|")
|
|
27
|
+
const date = new Date(dateStr)
|
|
28
|
+
|
|
29
|
+
const diff = execSync(`git show "${hash}"`, {
|
|
30
|
+
...GitAdapter.EXEC_OPTIONS,
|
|
31
|
+
maxBuffer: GitAdapter.LARGE_DIFF_BUFFER,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
hash: fullHash,
|
|
36
|
+
message,
|
|
37
|
+
date,
|
|
38
|
+
diff,
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Failed to get commit info for ${hash}: ${getErrorMessage(error)}`,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async validateCommitHash(hash: string): Promise<boolean> {
|
|
48
|
+
try {
|
|
49
|
+
execSync(`git rev-parse --verify "${hash}"`, GitAdapter.EXEC_OPTIONS)
|
|
50
|
+
return true
|
|
51
|
+
} catch {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async isValidRepository(): Promise<boolean> {
|
|
57
|
+
try {
|
|
58
|
+
execSync("git rev-parse --git-dir", GitAdapter.EXEC_OPTIONS)
|
|
59
|
+
return true
|
|
60
|
+
} catch {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getCurrentUserEmail(): Promise<string> {
|
|
66
|
+
try {
|
|
67
|
+
return execSync("git config user.email", GitAdapter.EXEC_OPTIONS).trim()
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Failed to get current user email: ${getErrorMessage(error)}`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getCurrentUserName(): Promise<string> {
|
|
76
|
+
try {
|
|
77
|
+
return execSync("git config user.name", GitAdapter.EXEC_OPTIONS).trim()
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Failed to get current user name: ${getErrorMessage(error)}`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getRepositoryName(): Promise<string> {
|
|
86
|
+
try {
|
|
87
|
+
// Try to get the repository name from remote origin URL
|
|
88
|
+
const remoteUrl = execSync("git config --get remote.origin.url", GitAdapter.EXEC_OPTIONS).trim()
|
|
89
|
+
|
|
90
|
+
// Extract repository name from various URL formats
|
|
91
|
+
// git@github.com:user/repo.git -> repo
|
|
92
|
+
// https://github.com/user/repo.git -> repo
|
|
93
|
+
// https://github.com/user/repo -> repo
|
|
94
|
+
const match = remoteUrl.match(/\/([^\/]+?)(?:\.git)?$/)
|
|
95
|
+
if (match && match[1]) {
|
|
96
|
+
return match[1]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Fallback: get the directory name
|
|
100
|
+
const dirName = execSync("basename $(git rev-parse --show-toplevel)", GitAdapter.EXEC_OPTIONS).trim()
|
|
101
|
+
return dirName
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Final fallback: use current directory name
|
|
104
|
+
try {
|
|
105
|
+
return execSync("basename $(pwd)", GitAdapter.EXEC_OPTIONS).trim()
|
|
106
|
+
} catch {
|
|
107
|
+
return "Unknown Project"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getUserAuthoredCommits(params: {
|
|
113
|
+
authorEmail: string
|
|
114
|
+
limit?: number
|
|
115
|
+
since?: string
|
|
116
|
+
until?: string
|
|
117
|
+
}): Promise<string[]> {
|
|
118
|
+
const { authorEmail, limit, since, until } = params
|
|
119
|
+
try {
|
|
120
|
+
const limitFlag = limit ? `--max-count=${limit}` : ""
|
|
121
|
+
const sinceFlag = since ? `--since="${since}"` : ""
|
|
122
|
+
const untilFlag = until ? `--until="${until}"` : ""
|
|
123
|
+
|
|
124
|
+
const output = execSync(
|
|
125
|
+
`git log --author="${authorEmail}" --format="%H" --no-merges ${limitFlag} ${sinceFlag} ${untilFlag}`,
|
|
126
|
+
GitAdapter.EXEC_OPTIONS,
|
|
127
|
+
).trim()
|
|
128
|
+
|
|
129
|
+
return this.parseCommitHashes(output)
|
|
130
|
+
} catch (error) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Failed to get user authored commits: ${getErrorMessage(error)}`,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private parseCommitHashes(output: string): string[] {
|
|
138
|
+
if (!output) {
|
|
139
|
+
return []
|
|
140
|
+
}
|
|
141
|
+
return output.split("\n").filter((hash) => hash.length > 0)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Commit } from "@domain/commit"
|
|
2
|
+
import { CommitHash } from "@domain/commit-hash"
|
|
3
|
+
|
|
4
|
+
import { ICommitRepository } from "@presentation/commit-repository.interface"
|
|
5
|
+
import { IVersionControlService } from "@presentation/version-control-service.interface"
|
|
6
|
+
|
|
7
|
+
export class GitCommitRepository implements ICommitRepository {
|
|
8
|
+
constructor(private readonly versionControlService: IVersionControlService) {}
|
|
9
|
+
|
|
10
|
+
async getByHash(hash: CommitHash): Promise<Commit> {
|
|
11
|
+
const commitInfo = await this.versionControlService.getCommitInfo(
|
|
12
|
+
hash.getValue(),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
return new Commit({
|
|
16
|
+
hash,
|
|
17
|
+
message: commitInfo.message,
|
|
18
|
+
date: commitInfo.date,
|
|
19
|
+
diff: commitInfo.diff,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getByAuthor(params: {
|
|
24
|
+
authorEmail: string
|
|
25
|
+
limit?: number
|
|
26
|
+
since?: string
|
|
27
|
+
until?: string
|
|
28
|
+
}): Promise<Commit[]> {
|
|
29
|
+
const { authorEmail, limit, since, until } = params
|
|
30
|
+
const commitHashes =
|
|
31
|
+
await this.versionControlService.getUserAuthoredCommits({
|
|
32
|
+
authorEmail,
|
|
33
|
+
limit,
|
|
34
|
+
since,
|
|
35
|
+
until,
|
|
36
|
+
})
|
|
37
|
+
const commits: Commit[] = []
|
|
38
|
+
|
|
39
|
+
for (const hashString of commitHashes) {
|
|
40
|
+
try {
|
|
41
|
+
const hash = CommitHash.create(hashString)
|
|
42
|
+
const commit = await this.getByHash(hash)
|
|
43
|
+
commits.push(commit)
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.warn(`Warning: Failed to load commit ${hashString}:`, error)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return commits
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async getByHashes(hashes: CommitHash[]): Promise<Commit[]> {
|
|
53
|
+
const commits: Commit[] = []
|
|
54
|
+
|
|
55
|
+
for (const hash of hashes) {
|
|
56
|
+
try {
|
|
57
|
+
const commit = await this.getByHash(hash)
|
|
58
|
+
commits.push(commit)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn(
|
|
61
|
+
`Warning: Failed to load commit ${hash.getShortHash()}:`,
|
|
62
|
+
error,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return commits
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async exists(hash: CommitHash): Promise<boolean> {
|
|
71
|
+
return this.versionControlService.validateCommitHash(hash.getValue())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getCurrentUserEmail(): Promise<string> {
|
|
75
|
+
return this.versionControlService.getCurrentUserEmail()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getCurrentUserName(): Promise<string> {
|
|
79
|
+
return this.versionControlService.getCurrentUserName()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async isValidRepository(): Promise<boolean> {
|
|
83
|
+
return this.versionControlService.isValidRepository()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Analysis } from "@domain/analysis"
|
|
2
|
+
import { AnalyzedCommit } from "@domain/analyzed-commit"
|
|
3
|
+
import { Category } from "@domain/category"
|
|
4
|
+
import { Commit } from "@domain/commit"
|
|
5
|
+
import { CommitHash } from "@domain/commit-hash"
|
|
6
|
+
|
|
7
|
+
import { ConsoleFormatter } from "@presentation/console-formatter"
|
|
8
|
+
import {
|
|
9
|
+
IProgressRepository,
|
|
10
|
+
ProgressState,
|
|
11
|
+
} from "@presentation/progress-repository.interface"
|
|
12
|
+
import { IStorageService } from "@presentation/storage-service.interface"
|
|
13
|
+
|
|
14
|
+
import { calculatePercentage } from "../utils"
|
|
15
|
+
import { AppPaths } from "../utils/app-paths"
|
|
16
|
+
|
|
17
|
+
export class JSONProgressTracker implements IProgressRepository {
|
|
18
|
+
private static readonly JSON_INDENT = 2
|
|
19
|
+
|
|
20
|
+
constructor(private readonly storageService: IStorageService) {}
|
|
21
|
+
|
|
22
|
+
private getProgressFilePath(): string {
|
|
23
|
+
return AppPaths.getProgressFilePath()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async saveProgress(state: ProgressState): Promise<void> {
|
|
27
|
+
const serializedState = this.serializeProgressState(state)
|
|
28
|
+
const content = JSON.stringify(
|
|
29
|
+
serializedState,
|
|
30
|
+
null,
|
|
31
|
+
JSONProgressTracker.JSON_INDENT,
|
|
32
|
+
)
|
|
33
|
+
await this.storageService.writeFile(
|
|
34
|
+
this.getProgressFilePath(),
|
|
35
|
+
content,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async loadProgress(): Promise<ProgressState | null> {
|
|
40
|
+
if (!(await this.hasProgress())) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const content = await this.storageService.readFile(
|
|
46
|
+
this.getProgressFilePath(),
|
|
47
|
+
)
|
|
48
|
+
const serializedState = JSON.parse(content)
|
|
49
|
+
return this.deserializeProgressState(serializedState)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
ConsoleFormatter.logError(`Failed to load progress file: ${error}`)
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async hasProgress(): Promise<boolean> {
|
|
57
|
+
return this.storageService.fileExists(this.getProgressFilePath())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async clearProgress(): Promise<void> {
|
|
61
|
+
await this.storageService.deleteFile(this.getProgressFilePath())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getRemainingCommits(state: ProgressState): CommitHash[] {
|
|
65
|
+
const processedHashes = new Set(
|
|
66
|
+
state.processedCommits.map((hash) => hash.getValue()),
|
|
67
|
+
)
|
|
68
|
+
return state.totalCommits.filter(
|
|
69
|
+
(hash) => !processedHashes.has(hash.getValue()),
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
formatProgressSummary(state: ProgressState): string {
|
|
74
|
+
const processed = state.processedCommits.length
|
|
75
|
+
const total = state.totalCommits.length
|
|
76
|
+
const remaining = total - processed
|
|
77
|
+
const percentComplete = calculatePercentage(processed, total)
|
|
78
|
+
|
|
79
|
+
return `
|
|
80
|
+
Previous session:
|
|
81
|
+
- Started: ${state.startTime.toLocaleString()}
|
|
82
|
+
- Progress: ${processed}/${total} commits (${percentComplete}%)
|
|
83
|
+
- Remaining: ${remaining} commits
|
|
84
|
+
- Output file: ${state.outputFile}
|
|
85
|
+
`.trim()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private serializeProgressState(
|
|
89
|
+
state: ProgressState,
|
|
90
|
+
): Record<string, unknown> {
|
|
91
|
+
return {
|
|
92
|
+
totalCommits: state.totalCommits.map((hash) => hash.getValue()),
|
|
93
|
+
processedCommits: state.processedCommits.map((hash) => hash.getValue()),
|
|
94
|
+
analyzedCommits: state.analyzedCommits.map((commit) => ({
|
|
95
|
+
hash: commit.getHash().getValue(),
|
|
96
|
+
message: commit.getMessage(),
|
|
97
|
+
date: commit.getDate().toISOString(),
|
|
98
|
+
year: commit.getYear(),
|
|
99
|
+
category: commit.getAnalysis().getCategory().getValue(),
|
|
100
|
+
summary: commit.getAnalysis().getSummary(),
|
|
101
|
+
description: commit.getAnalysis().getDescription(),
|
|
102
|
+
})),
|
|
103
|
+
lastProcessedIndex: state.lastProcessedIndex,
|
|
104
|
+
startTime: state.startTime.toISOString(),
|
|
105
|
+
outputFile: state.outputFile,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private deserializeProgressState(
|
|
110
|
+
data: Record<string, unknown>,
|
|
111
|
+
): ProgressState {
|
|
112
|
+
this.validateProgressData(data)
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
totalCommits: (data.totalCommits as string[]).map((hash: string) =>
|
|
116
|
+
CommitHash.create(hash),
|
|
117
|
+
),
|
|
118
|
+
processedCommits: (data.processedCommits as string[]).map(
|
|
119
|
+
(hash: string) => CommitHash.create(hash),
|
|
120
|
+
),
|
|
121
|
+
analyzedCommits: this.deserializeAnalyzedCommits(
|
|
122
|
+
data.analyzedCommits as Record<string, unknown>[],
|
|
123
|
+
),
|
|
124
|
+
lastProcessedIndex: data.lastProcessedIndex as number,
|
|
125
|
+
startTime: new Date(data.startTime as string),
|
|
126
|
+
outputFile: data.outputFile as string,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private validateProgressData(data: Record<string, unknown>): void {
|
|
131
|
+
if (!Array.isArray(data.totalCommits)) {
|
|
132
|
+
throw new Error("Invalid progress data: totalCommits must be an array")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!Array.isArray(data.processedCommits)) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"Invalid progress data: processedCommits must be an array",
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (typeof data.lastProcessedIndex !== "number") {
|
|
142
|
+
throw new Error(
|
|
143
|
+
"Invalid progress data: lastProcessedIndex must be a number",
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (typeof data.startTime !== "string") {
|
|
148
|
+
throw new Error("Invalid progress data: startTime must be a string")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof data.outputFile !== "string") {
|
|
152
|
+
throw new Error("Invalid progress data: outputFile must be a string")
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private deserializeAnalyzedCommits(
|
|
157
|
+
data: Record<string, unknown>[],
|
|
158
|
+
): AnalyzedCommit[] {
|
|
159
|
+
if (!Array.isArray(data)) {
|
|
160
|
+
return []
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return data.map((item: Record<string, unknown>) => {
|
|
164
|
+
const hash = CommitHash.create(item.hash as string)
|
|
165
|
+
const commit = new Commit({
|
|
166
|
+
hash,
|
|
167
|
+
message: item.message as string,
|
|
168
|
+
date: new Date(item.date as string),
|
|
169
|
+
diff: "", // We don't store diff in progress, so use empty string
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const category = Category.create(item.category as string)
|
|
173
|
+
const analysis = new Analysis({
|
|
174
|
+
category,
|
|
175
|
+
summary: item.summary as string,
|
|
176
|
+
description: item.description as string,
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return new AnalyzedCommit(commit, analysis)
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ClaudeLLMAdapter } from "./claude-llm-adapter"
|
|
2
|
+
import { GeminiLLMAdapter } from "./gemini-llm-adapter"
|
|
3
|
+
import { LLMAdapter } from "./llm-adapter"
|
|
4
|
+
import { OpenAILLMAdapter } from "./openai-llm-adapter"
|
|
5
|
+
|
|
6
|
+
export class LLMAdapterFactory {
|
|
7
|
+
static create(llm?: string): LLMAdapter {
|
|
8
|
+
const normalizedLLM = llm?.toLowerCase()
|
|
9
|
+
|
|
10
|
+
switch (normalizedLLM) {
|
|
11
|
+
case "gemini":
|
|
12
|
+
return new GeminiLLMAdapter()
|
|
13
|
+
case "openai":
|
|
14
|
+
case "gpt":
|
|
15
|
+
return new OpenAILLMAdapter()
|
|
16
|
+
case "claude":
|
|
17
|
+
default:
|
|
18
|
+
return new ClaudeLLMAdapter()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static getSupportedLLMs(): string[] {
|
|
23
|
+
return ["claude", "gemini", "openai"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|