@riflo/ryte 1.1.3 → 1.2.2
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/package.json +1 -1
- package/src/ai.js +62 -27
- package/src/config.js +45 -0
- package/src/index.js +112 -42
- package/src/prompt.js +22 -14
- package/src/provider.js +31 -0
package/package.json
CHANGED
package/src/ai.js
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { getConfig } from "./config.js";
|
|
2
|
+
import { getProviderConfig } from "./provider.js";
|
|
3
3
|
|
|
4
|
-
export async function generateAIResponse(messages) {
|
|
5
|
-
const
|
|
6
|
-
const openAiKey = process.env.OPENAI_API_KEY;
|
|
4
|
+
export async function generateAIResponse(messages, overrideConfig = null) {
|
|
5
|
+
const config = overrideConfig || getConfig();
|
|
7
6
|
|
|
8
|
-
if (!
|
|
9
|
-
|
|
10
|
-
process.exit(1);
|
|
7
|
+
if (!config || !config.apiKey) {
|
|
8
|
+
throw new Error("API Key not found. Please run 'ryte config' or follow the setup flow.");
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
11
|
+
const providerName = config.provider || "openai";
|
|
12
|
+
const pConfig = getProviderConfig(providerName, config.baseUrl);
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
: "https://api.openai.com/v1/chat/completions";
|
|
20
|
-
|
|
21
|
-
// llama-3.1-8b-instant: 14,400 TPM (6x higher than llama-3.3-70b-versatile)
|
|
22
|
-
// Still very capable for commit messages and PR summaries
|
|
23
|
-
const model = isGroq
|
|
24
|
-
? "llama-3.1-8b-instant"
|
|
25
|
-
: "gpt-4o-mini";
|
|
14
|
+
const apiUrl = pConfig.url;
|
|
15
|
+
const apiKey = config.apiKey;
|
|
16
|
+
const model = config.model || pConfig.model;
|
|
26
17
|
|
|
27
18
|
const MAX_RETRIES = 3;
|
|
28
19
|
|
|
@@ -42,7 +33,6 @@ export async function generateAIResponse(messages) {
|
|
|
42
33
|
});
|
|
43
34
|
|
|
44
35
|
if (response.status === 429) {
|
|
45
|
-
// Rate limited — parse retry-after header or use exponential backoff
|
|
46
36
|
const retryAfter = parseInt(response.headers.get("retry-after") || "15", 10);
|
|
47
37
|
const waitSeconds = retryAfter + 1;
|
|
48
38
|
|
|
@@ -50,22 +40,67 @@ export async function generateAIResponse(messages) {
|
|
|
50
40
|
process.stdout.write(`\r\x1b[33m⚠ Rate limit hit. Waiting ${i}s before retry (${attempt}/${MAX_RETRIES})...\x1b[0m`);
|
|
51
41
|
await new Promise(r => setTimeout(r, 1000));
|
|
52
42
|
}
|
|
53
|
-
process.stdout.write("\r" + " ".repeat(80) + "\r");
|
|
43
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r");
|
|
54
44
|
continue;
|
|
55
45
|
}
|
|
56
46
|
|
|
57
47
|
if (!response.ok) {
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
let errorData;
|
|
49
|
+
try {
|
|
50
|
+
errorData = await response.json();
|
|
51
|
+
} catch (e) {
|
|
52
|
+
throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(errorData.error?.message || `API request failed with status ${response.status}`);
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
const data = await response.json();
|
|
63
|
-
|
|
58
|
+
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
|
59
|
+
throw new Error("Invalid response format from AI provider.");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const content = data.choices[0].message.content;
|
|
63
|
+
if (!content) {
|
|
64
|
+
throw new Error("AI provider returned an empty response.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const trimmed = content.trim();
|
|
68
|
+
|
|
69
|
+
// Deterministic Internal Contract Prep
|
|
70
|
+
// Currently returns { text: string, structured: Object }
|
|
71
|
+
return parseAICommitMessage(trimmed);
|
|
64
72
|
} catch (e) {
|
|
73
|
+
// Check for network errors (Offline)
|
|
74
|
+
if (e.code === 'ENOTFOUND' || e.code === 'EAI_AGAIN') {
|
|
75
|
+
throw new Error("Network unreachable. Please check your internet connection.");
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
if (attempt === MAX_RETRIES) {
|
|
66
|
-
|
|
67
|
-
process.exit(1);
|
|
79
|
+
throw e;
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parses a conventional commit message into a structured object.
|
|
87
|
+
* This is the 'Deterministic Contract' for the engine.
|
|
88
|
+
*/
|
|
89
|
+
function parseAICommitMessage(text) {
|
|
90
|
+
const lines = text.split("\n");
|
|
91
|
+
const header = lines[0].trim();
|
|
92
|
+
|
|
93
|
+
// Simple regex for conventional commit: type(scope): subject
|
|
94
|
+
const match = header.match(/^(\w+)(?:\(([^)]+)\))?:\s*(.+)$/);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
text: text, // The full raw text for legacy compatibility
|
|
98
|
+
structured: {
|
|
99
|
+
header: header,
|
|
100
|
+
type: match ? match[1] : "other",
|
|
101
|
+
scope: match ? match[2] : null,
|
|
102
|
+
subject: match ? match[3] : header,
|
|
103
|
+
body: lines.slice(1).filter(l => l.trim().length > 0).join("\n").trim() || null
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".ryte");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
provider: "openai",
|
|
10
|
+
apiKey: "",
|
|
11
|
+
model: "gpt-4o-mini",
|
|
12
|
+
baseUrl: "" // Optional for local providers like OpenClaw/Ollama
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function getConfig() {
|
|
16
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const data = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
21
|
+
const parsed = JSON.parse(data);
|
|
22
|
+
// Ensure some basic structure integrity
|
|
23
|
+
if (typeof parsed !== "object" || parsed === null) throw new Error("Invalid config format");
|
|
24
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.warn(`\n\x1b[33m⚠ Warning: Configuration file is corrupted. Re-initializing...\x1b[0m`);
|
|
27
|
+
// If corrupted, return null so setupFlow triggers or we can fallback
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function setConfig(updates) {
|
|
33
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
34
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
const current = getConfig() || DEFAULT_CONFIG;
|
|
37
|
+
const updated = { ...current, ...updates };
|
|
38
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), { mode: 0o600 });
|
|
39
|
+
return updated;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function hasValidConfig() {
|
|
43
|
+
const config = getConfig();
|
|
44
|
+
return !!(config && config.apiKey);
|
|
45
|
+
}
|
package/src/index.js
CHANGED
|
@@ -6,12 +6,16 @@ import path from "path";
|
|
|
6
6
|
import { getStagedDiff, getCurrentBranch, getBranchCommits, applyCommit } from "./git.js";
|
|
7
7
|
import { generateAIResponse } from "./ai.js";
|
|
8
8
|
import { COMMIT_SYSTEM_PROMPT, PR_SYSTEM_PROMPT } from "./prompt.js";
|
|
9
|
+
import { getConfig, setConfig, hasValidConfig } from "./config.js";
|
|
10
|
+
import { PROVIDERS } from "./provider.js";
|
|
9
11
|
|
|
10
12
|
const rl = readline.createInterface({
|
|
11
13
|
input: process.stdin,
|
|
12
14
|
output: process.stdout
|
|
13
15
|
});
|
|
14
16
|
|
|
17
|
+
const VERSION = "1.2.2";
|
|
18
|
+
|
|
15
19
|
async function question(query) {
|
|
16
20
|
return new Promise(resolve => rl.question(query, resolve));
|
|
17
21
|
}
|
|
@@ -40,6 +44,43 @@ function editInteractively(initialText) {
|
|
|
40
44
|
});
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
async function setupFlow() {
|
|
48
|
+
console.log("\n\x1b[36mWelcome to RYTE. Let's set up your Git Intelligence Layer.\x1b[0m");
|
|
49
|
+
console.log("------------------------------------------------------------");
|
|
50
|
+
|
|
51
|
+
console.log("\nSelect your LLM Provider:");
|
|
52
|
+
const providerList = Object.keys(PROVIDERS);
|
|
53
|
+
providerList.forEach((p, i) => console.log(`${i + 1}) ${p.charAt(0).toUpperCase() + p.slice(1)}`));
|
|
54
|
+
|
|
55
|
+
const choice = await question(`\nChoose [1-${providerList.length}]: `);
|
|
56
|
+
const providerKey = providerList[parseInt(choice) - 1] || "openai";
|
|
57
|
+
|
|
58
|
+
const apiKey = await question(`Paste your ${providerKey.toUpperCase()} API Key: `);
|
|
59
|
+
if (!apiKey) {
|
|
60
|
+
console.error("Error: API Key is required.");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setConfig({
|
|
65
|
+
provider: providerKey,
|
|
66
|
+
apiKey: apiKey,
|
|
67
|
+
model: PROVIDERS[providerKey].defaultModel
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log("\n\x1b[32m✔ Configuration saved to ~/.ryte/config.json\x1b[0m");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function handleConfig() {
|
|
74
|
+
const config = getConfig() || {};
|
|
75
|
+
console.log("\n\x1b[36mCurrent Configuration:\x1b[0m");
|
|
76
|
+
console.log(JSON.stringify(config, null, 2));
|
|
77
|
+
|
|
78
|
+
const choice = await question("\nWould you like to reset configuration? [y/N]: ");
|
|
79
|
+
if (choice.toLowerCase() === "y") {
|
|
80
|
+
await setupFlow();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
43
84
|
async function interactiveLoop(initialResult, type) {
|
|
44
85
|
let currentResult = initialResult;
|
|
45
86
|
|
|
@@ -72,25 +113,34 @@ async function interactiveLoop(initialResult, type) {
|
|
|
72
113
|
async function handleCommit() {
|
|
73
114
|
const diff = getStagedDiff();
|
|
74
115
|
if (!diff) {
|
|
75
|
-
console.log("No staged changes found
|
|
116
|
+
console.log("\n\x1b[33m⚠ No staged changes found.\x1b[0m");
|
|
117
|
+
console.log("Use \x1b[32m`git add` \x1b[0m to stage files before committing.");
|
|
76
118
|
process.exit(0);
|
|
77
119
|
}
|
|
78
120
|
|
|
79
121
|
const branch = getCurrentBranch();
|
|
80
122
|
|
|
81
123
|
while (true) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
124
|
+
try {
|
|
125
|
+
console.log("\nAnalyzing staged diff...");
|
|
126
|
+
const result = await generateAIResponse([
|
|
127
|
+
{ role: "system", content: COMMIT_SYSTEM_PROMPT },
|
|
128
|
+
{ role: "user", content: `Branch: ${branch}\n\nDiff:\n${diff}` }
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
const finalAction = await interactiveLoop(result.text, "Commit Message");
|
|
132
|
+
|
|
133
|
+
if (finalAction !== "REGENERATE") {
|
|
134
|
+
applyCommit(finalAction);
|
|
135
|
+
console.log("\n\x1b[32m✔ Commit applied successfully!\x1b[0m");
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.error(`\n\x1b[31m✖ AI Generation failed:\x1b[0m ${e.message}`);
|
|
140
|
+
const retry = await question("\nWould you like to try again? [y/N]: ");
|
|
141
|
+
if (retry.toLowerCase() !== "y") {
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
94
144
|
}
|
|
95
145
|
}
|
|
96
146
|
}
|
|
@@ -98,25 +148,34 @@ async function handleCommit() {
|
|
|
98
148
|
async function handlePR() {
|
|
99
149
|
const commits = getBranchCommits();
|
|
100
150
|
if (!commits) {
|
|
101
|
-
console.log("No recent commits found
|
|
151
|
+
console.log("\n\x1b[33m⚠ No recent commits found.\x1b[0m");
|
|
152
|
+
console.log("Ensure you have committed changes that are distinct from your main branch.");
|
|
102
153
|
process.exit(0);
|
|
103
154
|
}
|
|
104
155
|
const branch = getCurrentBranch();
|
|
105
156
|
|
|
106
157
|
while (true) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
try {
|
|
159
|
+
console.log("\nAnalyzing recent commits...");
|
|
160
|
+
const result = await generateAIResponse([
|
|
161
|
+
{ role: "system", content: PR_SYSTEM_PROMPT },
|
|
162
|
+
{ role: "user", content: `Branch: ${branch}\n\nCommits:\n${commits}` }
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
const finalAction = await interactiveLoop(result.text, "PR Markdown Description");
|
|
166
|
+
|
|
167
|
+
if (finalAction !== "REGENERATE") {
|
|
168
|
+
console.log("\n\x1b[32mFinal PR Content:\x1b[0m\n");
|
|
169
|
+
console.log(finalAction);
|
|
170
|
+
console.log("\n(You can copy-paste the above into your pull request)");
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.error(`\n\x1b[31m✖ AI Generation failed:\x1b[0m ${e.message}`);
|
|
175
|
+
const retry = await question("\nWould you like to try again? [y/N]: ");
|
|
176
|
+
if (retry.toLowerCase() !== "y") {
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
120
179
|
}
|
|
121
180
|
}
|
|
122
181
|
}
|
|
@@ -125,12 +184,19 @@ async function main() {
|
|
|
125
184
|
const args = process.argv.slice(2);
|
|
126
185
|
const cmd = args[0]?.toLowerCase();
|
|
127
186
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
187
|
+
try {
|
|
188
|
+
if (!hasValidConfig()) {
|
|
189
|
+
await setupFlow();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (cmd === "c" || cmd === "commit") {
|
|
193
|
+
await handleCommit();
|
|
194
|
+
} else if (cmd === "pr") {
|
|
195
|
+
await handlePR();
|
|
196
|
+
} else if (cmd === "config") {
|
|
197
|
+
await handleConfig();
|
|
198
|
+
} else {
|
|
199
|
+
console.log(`
|
|
134
200
|
\x1b[1;38;5;39m██████╗ \x1b[1;38;5;63m██╗ ██╗\x1b[1;38;5;129m████████╗\x1b[1;38;5;161m███████╗\x1b[0m
|
|
135
201
|
\x1b[1;38;5;39m██╔══██╗\x1b[1;38;5;63m╚██╗ ██╔╝\x1b[1;38;5;129m╚══██╔══╝\x1b[1;38;5;161m██╔════╝\x1b[0m
|
|
136
202
|
\x1b[1;38;5;39m██████╔╝\x1b[1;38;5;63m ╚████╔╝ \x1b[1;38;5;129m ██║ \x1b[1;38;5;161m█████╗ \x1b[0m
|
|
@@ -139,17 +205,21 @@ async function main() {
|
|
|
139
205
|
\x1b[1;38;5;39m╚═╝ ╚═╝\x1b[1;38;5;63m ╚═╝ \x1b[1;38;5;129m ╚═╝ \x1b[1;38;5;161m╚══════╝\x1b[0m
|
|
140
206
|
|
|
141
207
|
\x1b[1;38;5;46m[ THE AI-POWERED GIT INFRASTRUCTURE ]\x1b[0m
|
|
142
|
-
\x1b[
|
|
208
|
+
\x1b[90mv${VERSION} | by Riflo\x1b[0m
|
|
143
209
|
|
|
144
210
|
\x1b[33mCOMMANDS:\x1b[0m
|
|
145
|
-
\x1b[32mryte c\x1b[0m
|
|
146
|
-
\x1b[32mryte pr\x1b[0m
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
211
|
+
\x1b[32mryte c\x1b[0m Generate semantic commit from diff
|
|
212
|
+
\x1b[32mryte pr\x1b[0m Generate PR markdown from branch commits
|
|
213
|
+
\x1b[32mryte config\x1b[0m Generate or edit your local configuration
|
|
214
|
+
|
|
215
|
+
\x1b[33mONBOARDING:\x1b[0m
|
|
216
|
+
No .env required. Run \x1b[32mryte config\x1b[0m or just run \x1b[32mryte c\x1b[0m to
|
|
217
|
+
start the interactive setup.
|
|
218
|
+
`);
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.error(`\n\x1b[31m✖ Unexpected Error:\x1b[0m ${e.message}`);
|
|
222
|
+
process.exit(1);
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
rl.close();
|
package/src/prompt.js
CHANGED
|
@@ -2,25 +2,33 @@ export const COMMIT_SYSTEM_PROMPT = `
|
|
|
2
2
|
You are an expert developer assistant specialized in Git workflows and the Conventional Commits specification.
|
|
3
3
|
Your goal is to generate a concise, meaningful, and technically accurate commit message based on provided git diffs.
|
|
4
4
|
|
|
5
|
+
PRIORITY:
|
|
6
|
+
1. LOGIC OVER METADATA: If the diff contains both code changes (src/, lib/) and metadata changes (package.json version, README typos), the commit message MUST focus on the code logic.
|
|
7
|
+
2. SUBSTANCE: Avoid subjects like "bump version" or "update files" if there is meaningful logic change. Focus on the "What" and "Why" of the code evolution.
|
|
8
|
+
|
|
5
9
|
RULES:
|
|
6
|
-
1. OUTPUT ONLY THE COMMIT MESSAGE. No markdown, no
|
|
10
|
+
1. OUTPUT ONLY THE COMMIT MESSAGE. No markdown, no filler.
|
|
7
11
|
2. Follow Conventional Commits: <type>(<scope>): <subject>
|
|
8
|
-
3. Use types:
|
|
12
|
+
3. Use types:
|
|
13
|
+
- feat: new capability or significant hardening/stabilization.
|
|
14
|
+
- fix: bug fixes.
|
|
15
|
+
- refactor: code restructuring without changing behavior.
|
|
16
|
+
- docs: documentation only.
|
|
17
|
+
- chore: maintenance, version bumps (only if NO logic changed).
|
|
9
18
|
4. Subject line:
|
|
10
|
-
- Use imperative mood ("add",
|
|
11
|
-
- Max 50 characters.
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
7. Body (Optional): If the change is complex, add a brief body after 1 blank line to explain technical nuances.
|
|
17
|
-
8. Context: If a branch name or ticket is provided, incorporate it into the scope or footer if applicable.
|
|
18
|
-
9. Anti-Hallucination: Documentation files (like README.md) often contain example commit messages (e.g., "feat(auth): ..."). DO NOT assume these examples are the topic of the current change.
|
|
19
|
-
10. Logical Validation: Your suggested <scope> must be derived from actual modified logic in the diff, not from text inside code blocks, comments, or examples within a documentation file.
|
|
20
|
-
11. If only README.md or docs are changed, the type MUST be "docs" and the scope should relate to the documentation structure (e.g., "readme", "config", "intro"), NOT the example code inside it.
|
|
19
|
+
- Use imperative mood ("add", "implement", "harden").
|
|
20
|
+
- Max 50 characters. No period.
|
|
21
|
+
- Focus on the technical achievement.
|
|
22
|
+
5. Breaking Changes: Use "!" if behavior changes significantly (e.g., "feat!: ...").
|
|
23
|
+
6. Scope: Infer from affected module (e.g., "config", "ai", "core").
|
|
24
|
+
7. Body: Use bullet points for complex multi-file changes to explain "Why" and nuances.
|
|
21
25
|
|
|
22
26
|
Example:
|
|
23
|
-
feat(
|
|
27
|
+
feat(core): harden config recovery and ai response validation
|
|
28
|
+
|
|
29
|
+
- Implement auto-healing for corrupted config.json
|
|
30
|
+
- Add defensive network error handling in ai.js
|
|
31
|
+
- Standardize internal engine response contract
|
|
24
32
|
`;
|
|
25
33
|
|
|
26
34
|
export const PR_SYSTEM_PROMPT = `
|
package/src/provider.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const PROVIDERS = {
|
|
2
|
+
openai: {
|
|
3
|
+
baseUrl: "https://api.openai.com/v1/chat/completions",
|
|
4
|
+
defaultModel: "gpt-4o-mini",
|
|
5
|
+
authHeader: (key) => `Bearer ${key}`
|
|
6
|
+
},
|
|
7
|
+
groq: {
|
|
8
|
+
baseUrl: "https://api.groq.com/openai/v1/chat/completions",
|
|
9
|
+
defaultModel: "llama-3.1-8b-instant",
|
|
10
|
+
authHeader: (key) => `Bearer ${key}`
|
|
11
|
+
},
|
|
12
|
+
openrouter: {
|
|
13
|
+
baseUrl: "https://openrouter.ai/api/v1/chat/completions",
|
|
14
|
+
defaultModel: "google/gemini-pro-1.5-exp",
|
|
15
|
+
authHeader: (key) => `Bearer ${key}`
|
|
16
|
+
},
|
|
17
|
+
local: {
|
|
18
|
+
baseUrl: "http://localhost:11434/v1/chat/completions", // Default Ollama/OpenClaw local port
|
|
19
|
+
defaultModel: "llama3",
|
|
20
|
+
authHeader: (key) => `Bearer ${key}`
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function getProviderConfig(name, customBaseUrl = "") {
|
|
25
|
+
const p = PROVIDERS[name] || PROVIDERS.openai;
|
|
26
|
+
return {
|
|
27
|
+
url: customBaseUrl || p.baseUrl,
|
|
28
|
+
model: p.defaultModel,
|
|
29
|
+
auth: p.authHeader
|
|
30
|
+
};
|
|
31
|
+
}
|