clarity-ai 1.0.0
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/README.md +108 -0
- package/bin/clarity.js +2 -0
- package/package.json +51 -0
- package/scripts/postinstall.js +53 -0
- package/src/agents/BaseAgent.js +54 -0
- package/src/agents/CodeAgent.js +57 -0
- package/src/agents/FileAgent.js +39 -0
- package/src/agents/MonitorAgent.js +54 -0
- package/src/agents/ShellAgent.js +31 -0
- package/src/agents/WebAgent.js +39 -0
- package/src/agents/manager.js +116 -0
- package/src/commands/index.js +725 -0
- package/src/config/keys.js +104 -0
- package/src/config/paths.js +22 -0
- package/src/config/settings.js +28 -0
- package/src/index.js +86 -0
- package/src/memory/context.js +38 -0
- package/src/memory/store.js +54 -0
- package/src/providers/claude.js +61 -0
- package/src/providers/deepseek.js +53 -0
- package/src/providers/gemini.js +48 -0
- package/src/providers/groq.js +52 -0
- package/src/providers/index.js +39 -0
- package/src/providers/openai.js +52 -0
- package/src/providers/openrouter.js +52 -0
- package/src/tools/bash.js +25 -0
- package/src/tools/code.js +52 -0
- package/src/tools/files.js +62 -0
- package/src/tools/git.js +67 -0
- package/src/tools/index.js +40 -0
- package/src/tools/pkg.js +46 -0
- package/src/tools/search.js +29 -0
- package/src/tools/system.js +29 -0
- package/src/tools/web.js +24 -0
- package/src/ui/banner.js +15 -0
- package/src/ui/blocks.js +55 -0
- package/src/ui/chatbox.js +126 -0
- package/src/ui/colors.js +22 -0
- package/src/ui/prompt.js +49 -0
- package/src/ui/spinner.js +43 -0
- package/src/utils/logger.js +40 -0
- package/src/utils/markdown.js +25 -0
- package/src/utils/termux.js +38 -0
- package/src/utils/version-check.js +66 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import paths from './paths.js';
|
|
3
|
+
|
|
4
|
+
const PROVIDER_REGEX = {
|
|
5
|
+
groq: /^gsk_[A-Za-z0-9]{30,}$/,
|
|
6
|
+
openrouter: /^sk-or-[A-Za-z0-9]{20,}$/,
|
|
7
|
+
anthropic: /^sk-ant-[A-Za-z0-9]{20,}$/,
|
|
8
|
+
openai: /^sk-[A-Za-z0-9]{20,}$/,
|
|
9
|
+
gemini: /^AIzaSy[A-Za-z0-9_-]{20,}$/,
|
|
10
|
+
deepseek: /^[A-Za-z0-9]{20,}$/,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const PROVIDER_NAMES = {
|
|
14
|
+
groq: 'Groq',
|
|
15
|
+
openrouter: 'OpenRouter',
|
|
16
|
+
anthropic: 'Anthropic',
|
|
17
|
+
openai: 'OpenAI',
|
|
18
|
+
gemini: 'Google Gemini',
|
|
19
|
+
deepseek: 'DeepSeek',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const keys = new Conf({
|
|
23
|
+
projectName: 'clarity',
|
|
24
|
+
configName: 'keys',
|
|
25
|
+
cwd: paths.config,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function obfuscate(key) {
|
|
29
|
+
return Buffer.from(key).toString('base64').split('').reverse().join('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function deobfuscate(encoded) {
|
|
33
|
+
return Buffer.from(encoded.split('').reverse().join(''), 'base64').toString();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setKey(provider, key) {
|
|
37
|
+
const name = provider.toLowerCase();
|
|
38
|
+
if (!PROVIDER_REGEX[name]) {
|
|
39
|
+
throw new Error(`Unknown provider: ${provider}. Supported: ${Object.keys(PROVIDER_REGEX).join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
if (!PROVIDER_REGEX[name].test(key)) {
|
|
42
|
+
throw new Error(`Invalid key format for ${name}. Expected format: ${PROVIDER_REGEX[name]}`);
|
|
43
|
+
}
|
|
44
|
+
keys.set(name, obfuscate(key));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getKey(provider) {
|
|
48
|
+
const encoded = keys.get(provider.toLowerCase());
|
|
49
|
+
if (!encoded) return null;
|
|
50
|
+
return deobfuscate(encoded);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function listKeys() {
|
|
54
|
+
const all = {};
|
|
55
|
+
for (const [provider, _] of Object.entries(PROVIDER_REGEX)) {
|
|
56
|
+
const key = keys.get(provider);
|
|
57
|
+
if (key) {
|
|
58
|
+
all[provider] = {
|
|
59
|
+
configured: true,
|
|
60
|
+
keyPreview: deobfuscate(key).slice(0, 8) + '...' + deobfuscate(key).slice(-4),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return all;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function removeKey(provider) {
|
|
68
|
+
keys.delete(provider.toLowerCase());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasAnyKeys() {
|
|
72
|
+
return Object.keys(PROVIDER_REGEX).some(p => keys.get(p));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function testKey(provider) {
|
|
76
|
+
const apiKey = getKey(provider);
|
|
77
|
+
if (!apiKey) return { success: false, error: 'No key configured' };
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const testEndpoints = {
|
|
81
|
+
groq: { url: 'https://api.groq.com/openai/v1/models', headers: { 'Authorization': `Bearer ${apiKey}` } },
|
|
82
|
+
gemini: { url: `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}` },
|
|
83
|
+
openrouter: { url: 'https://openrouter.ai/api/v1/models', headers: { 'Authorization': `Bearer ${apiKey}` } },
|
|
84
|
+
openai: { url: 'https://api.openai.com/v1/models', headers: { 'Authorization': `Bearer ${apiKey}` } },
|
|
85
|
+
anthropic: { url: 'https://api.anthropic.com/v1/messages', headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }, method: 'POST', body: { model: 'claude-3-haiku-20240307', max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] } },
|
|
86
|
+
deepseek: { url: 'https://api.deepseek.com/v1/models', headers: { 'Authorization': `Bearer ${apiKey}` } },
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const endpoint = testEndpoints[provider];
|
|
90
|
+
if (!endpoint) return { success: false, error: 'Unknown provider' };
|
|
91
|
+
|
|
92
|
+
const res = await fetch(endpoint.url, {
|
|
93
|
+
method: endpoint.method || 'GET',
|
|
94
|
+
headers: { ...endpoint.headers, 'Content-Type': 'application/json' },
|
|
95
|
+
body: endpoint.body ? JSON.stringify(endpoint.body) : undefined,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return { success: res.ok, status: res.status, error: res.ok ? null : `HTTP ${res.status}` };
|
|
99
|
+
} catch (err) {
|
|
100
|
+
return { success: false, error: err.message };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { setKey, getKey, listKeys, removeKey, hasAnyKeys, testKey, PROVIDER_NAMES, PROVIDER_REGEX };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isTermux } from '../utils/termux.js';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
const HOME = homedir();
|
|
6
|
+
const XDG_CONFIG = process.env.XDG_CONFIG_HOME || resolve(HOME, '.config');
|
|
7
|
+
const XDG_DATA = process.env.XDG_DATA_HOME || resolve(HOME, '.local', 'share');
|
|
8
|
+
const XDG_CACHE = process.env.XDG_CACHE_HOME || resolve(HOME, '.cache');
|
|
9
|
+
|
|
10
|
+
const paths = {
|
|
11
|
+
config: resolve(XDG_CONFIG, 'clarity'),
|
|
12
|
+
data: resolve(XDG_DATA, 'clarity'),
|
|
13
|
+
cache: resolve(XDG_CACHE, 'clarity'),
|
|
14
|
+
logs: resolve(XDG_CACHE, 'clarity', 'logs'),
|
|
15
|
+
history: resolve(XDG_CONFIG, 'clarity', 'history'),
|
|
16
|
+
keys: resolve(XDG_CONFIG, 'clarity', 'keys.json'),
|
|
17
|
+
settings: resolve(XDG_CONFIG, 'clarity', 'settings.json'),
|
|
18
|
+
agents: resolve(XDG_CONFIG, 'clarity', 'agents.json'),
|
|
19
|
+
memory: resolve(XDG_DATA, 'clarity', 'memory.json'),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default paths;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import paths from './paths.js';
|
|
3
|
+
|
|
4
|
+
const schema = {
|
|
5
|
+
theme: { type: 'string', default: 'dark' },
|
|
6
|
+
defaultModel: { type: 'string', default: 'groq/llama3-70b-8192' },
|
|
7
|
+
stream: { type: 'boolean', default: true },
|
|
8
|
+
showTokens: { type: 'boolean', default: true },
|
|
9
|
+
saveHistory: { type: 'boolean', default: true },
|
|
10
|
+
initDir: { type: 'boolean', default: false },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const settings = new Conf({
|
|
14
|
+
projectName: 'clarity',
|
|
15
|
+
schema,
|
|
16
|
+
configName: 'settings',
|
|
17
|
+
cwd: paths.config,
|
|
18
|
+
defaults: {
|
|
19
|
+
theme: 'dark',
|
|
20
|
+
defaultModel: 'groq/llama3-70b-8192',
|
|
21
|
+
stream: true,
|
|
22
|
+
showTokens: true,
|
|
23
|
+
saveHistory: true,
|
|
24
|
+
initDir: false,
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default settings;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { checkNodeVersion, checkDependencies, checkUpdates } from './utils/version-check.js';
|
|
3
|
+
import { isTermux } from './utils/termux.js';
|
|
4
|
+
import paths from './config/paths.js';
|
|
5
|
+
import { hasAnyKeys } from './config/keys.js';
|
|
6
|
+
import settings from './config/settings.js';
|
|
7
|
+
import renderBanner from './ui/banner.js';
|
|
8
|
+
import blocks from './ui/blocks.js';
|
|
9
|
+
import { startChat } from './ui/chatbox.js';
|
|
10
|
+
import agentManager from './agents/manager.js';
|
|
11
|
+
import memory from './memory/store.js';
|
|
12
|
+
import { mkdirSync, existsSync } from 'fs';
|
|
13
|
+
import { dirname } from 'path';
|
|
14
|
+
import readline from 'readline';
|
|
15
|
+
|
|
16
|
+
checkNodeVersion();
|
|
17
|
+
|
|
18
|
+
for (const dir of [paths.config, paths.data, paths.cache, paths.logs]) {
|
|
19
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
checkDependencies(process.cwd());
|
|
23
|
+
checkUpdates();
|
|
24
|
+
|
|
25
|
+
process.on('uncaughtException', (err) => {
|
|
26
|
+
blocks.error('Unexpected Error', err.message);
|
|
27
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
process.on('SIGINT', () => {
|
|
32
|
+
console.log();
|
|
33
|
+
blocks.info('Goodbye', 'Thanks for using CLARITY!');
|
|
34
|
+
process.exit(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
process.on('exit', () => {
|
|
38
|
+
agentManager.save();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
async function main() {
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
|
|
44
|
+
if (args.length === 0 && !hasAnyKeys()) {
|
|
45
|
+
renderBanner();
|
|
46
|
+
blocks.warn('No API Keys',
|
|
47
|
+
`No API keys found. Run 'clarity init' first.\n\nQuick start (free options):\n1. Get Groq key: https://console.groq.com\n2. Get Gemini key: https://aistudio.google.com\n\nThen: clarity /keys set groq YOUR_KEY`);
|
|
48
|
+
|
|
49
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
50
|
+
rl.question('Would you like to run the setup wizard now? (Y/n) ', async (answer) => {
|
|
51
|
+
rl.close();
|
|
52
|
+
if (answer.toLowerCase() !== 'n') {
|
|
53
|
+
const { default: commandRegistry } = await import('./commands/index.js');
|
|
54
|
+
await commandRegistry.init();
|
|
55
|
+
} else {
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (args.length > 0) {
|
|
63
|
+
const input = args.join(' ');
|
|
64
|
+
if (input.startsWith('/')) {
|
|
65
|
+
const { default: commandRegistry } = await import('./commands/index.js');
|
|
66
|
+
const result = await commandRegistry.execute(input, {});
|
|
67
|
+
if (result?.exit) process.exit(0);
|
|
68
|
+
if (!result?.stay) process.exit(0);
|
|
69
|
+
} else {
|
|
70
|
+
const { default: commandRegistry } = await import('./commands/index.js');
|
|
71
|
+
const { default: chatbox } = await import('./ui/chatbox.js');
|
|
72
|
+
startChat();
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await agentManager.restore();
|
|
78
|
+
const { default: commandRegistry } = await import('./commands/index.js');
|
|
79
|
+
const { default: chatbox } = await import('./ui/chatbox.js');
|
|
80
|
+
startChat();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch(err => {
|
|
84
|
+
blocks.error('Startup Error', err.message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import memory from './store.js';
|
|
2
|
+
|
|
3
|
+
const contextManager = {
|
|
4
|
+
buildPrompt({ platform, arch, nodeVersion, cwd, isTermux, toolList, userMemory }) {
|
|
5
|
+
const parts = [
|
|
6
|
+
`You are CLARITY, an AI agent running in the user's terminal (possibly Termux on Android).`,
|
|
7
|
+
``,
|
|
8
|
+
`You have access to tools: bash execution, file operations, web search, code execution, git, and package management.`,
|
|
9
|
+
``,
|
|
10
|
+
`When the user asks you to do something that requires a tool, use it. Always show what you're doing.`,
|
|
11
|
+
``,
|
|
12
|
+
`Keep responses concise and terminal-friendly. Use markdown for code blocks. Avoid very long prose.`,
|
|
13
|
+
``,
|
|
14
|
+
`You are running on: ${platform} ${arch}, Node ${nodeVersion}.`,
|
|
15
|
+
`Current directory: ${cwd}`,
|
|
16
|
+
`Termux: ${isTermux}`,
|
|
17
|
+
`Available tools: ${toolList}`,
|
|
18
|
+
`User memory: ${userMemory}`,
|
|
19
|
+
];
|
|
20
|
+
return parts.join('\n');
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
getSystemPrompt() {
|
|
24
|
+
const mem = memory.show();
|
|
25
|
+
const notes = mem.filter(m => m.role === 'system').map(m => m.content.replace('[Memory: ', '').replace(']', ''));
|
|
26
|
+
return {
|
|
27
|
+
platform: process.platform,
|
|
28
|
+
arch: process.arch,
|
|
29
|
+
nodeVersion: process.versions.node,
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
isTermux: !!process.env.PREFIX?.includes('com.termux'),
|
|
32
|
+
toolList: 'bash, files, web, search, code, git, pkg, system',
|
|
33
|
+
userMemory: notes.join('; ') || 'none',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default contextManager;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import paths from '../config/paths.js';
|
|
3
|
+
|
|
4
|
+
const MAX_CONTEXT = 10;
|
|
5
|
+
|
|
6
|
+
const memoryStore = new Conf({
|
|
7
|
+
projectName: 'clarity',
|
|
8
|
+
configName: 'memory',
|
|
9
|
+
cwd: paths.data,
|
|
10
|
+
defaults: { conversations: [], context: [] }
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const memory = {
|
|
14
|
+
add(conversation) {
|
|
15
|
+
const ctx = memoryStore.get('context') || [];
|
|
16
|
+
const lastMsg = conversation[conversation.length - 1];
|
|
17
|
+
if (lastMsg) ctx.push(lastMsg);
|
|
18
|
+
if (ctx.length > MAX_CONTEXT * 2) ctx.splice(0, 2);
|
|
19
|
+
memoryStore.set('context', ctx);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
getContext() {
|
|
23
|
+
return memoryStore.get('context') || [];
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
clear() {
|
|
27
|
+
memoryStore.set('context', []);
|
|
28
|
+
memoryStore.set('conversations', []);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
show() {
|
|
32
|
+
return this.getContext();
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
addNote(note) {
|
|
36
|
+
const ctx = memoryStore.get('context') || [];
|
|
37
|
+
ctx.push({ role: 'system', content: `[Memory: ${note}]` });
|
|
38
|
+
if (ctx.length > MAX_CONTEXT * 2) ctx.splice(0, 2);
|
|
39
|
+
memoryStore.set('context', ctx);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
saveConversation(messages) {
|
|
43
|
+
const convs = memoryStore.get('conversations') || [];
|
|
44
|
+
convs.push({ id: Date.now(), messages: messages.slice(-20), timestamp: new Date().toISOString() });
|
|
45
|
+
if (convs.length > 50) convs.shift();
|
|
46
|
+
memoryStore.set('conversations', convs);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
getConversations() {
|
|
50
|
+
return memoryStore.get('conversations') || [];
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default memory;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const PROVIDER = {
|
|
2
|
+
name: 'anthropic',
|
|
3
|
+
free: false,
|
|
4
|
+
streaming: true,
|
|
5
|
+
models: ['claude-3-5-haiku-20241022', 'claude-3-5-sonnet-20241022'],
|
|
6
|
+
baseURL: 'https://api.anthropic.com/v1',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
async function* sendMessage(apiKey, messages, model = 'claude-3-5-haiku-20241022', stream = true) {
|
|
10
|
+
const url = `${PROVIDER.baseURL}/messages`;
|
|
11
|
+
|
|
12
|
+
const systemMsg = messages.filter(m => m.role === 'system');
|
|
13
|
+
const chatMessages = messages.filter(m => m.role !== 'system');
|
|
14
|
+
|
|
15
|
+
const body = {
|
|
16
|
+
model,
|
|
17
|
+
max_tokens: 4096,
|
|
18
|
+
messages: chatMessages.map(m => ({ role: m.role, content: m.content })),
|
|
19
|
+
stream,
|
|
20
|
+
};
|
|
21
|
+
if (systemMsg.length > 0) body.system = systemMsg.map(m => m.content).join('\n');
|
|
22
|
+
|
|
23
|
+
const res = await fetch(url, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify(body),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!res.ok) throw new Error(`Anthropic API error: ${res.status} ${res.statusText}`);
|
|
30
|
+
|
|
31
|
+
if (!stream) {
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
yield data.content[0].text;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const reader = res.body.getReader();
|
|
38
|
+
const decoder = new TextDecoder();
|
|
39
|
+
let buffer = '';
|
|
40
|
+
|
|
41
|
+
while (true) {
|
|
42
|
+
const { done, value } = await reader.read();
|
|
43
|
+
if (done) break;
|
|
44
|
+
buffer += decoder.decode(value, { stream: true });
|
|
45
|
+
const lines = buffer.split('\n');
|
|
46
|
+
buffer = lines.pop() || '';
|
|
47
|
+
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (line.startsWith('data: ')) {
|
|
50
|
+
try {
|
|
51
|
+
const json = JSON.parse(line.slice(6));
|
|
52
|
+
if (json.type === 'content_block_delta' && json.delta?.text) {
|
|
53
|
+
yield json.delta.text;
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { PROVIDER, sendMessage };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const PROVIDER = {
|
|
2
|
+
name: 'deepseek',
|
|
3
|
+
free: false,
|
|
4
|
+
cheap: true,
|
|
5
|
+
streaming: true,
|
|
6
|
+
models: ['deepseek-chat', 'deepseek-coder'],
|
|
7
|
+
baseURL: 'https://api.deepseek.com/v1',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
async function* sendMessage(apiKey, messages, model = 'deepseek-chat', stream = true) {
|
|
11
|
+
const url = `${PROVIDER.baseURL}/chat/completions`;
|
|
12
|
+
const body = { model, messages, stream };
|
|
13
|
+
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
17
|
+
body: JSON.stringify(body),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (!res.ok) throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);
|
|
21
|
+
|
|
22
|
+
if (!stream) {
|
|
23
|
+
const data = await res.json();
|
|
24
|
+
yield data.choices[0].message.content;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const reader = res.body.getReader();
|
|
29
|
+
const decoder = new TextDecoder();
|
|
30
|
+
let buffer = '';
|
|
31
|
+
|
|
32
|
+
while (true) {
|
|
33
|
+
const { done, value } = await reader.read();
|
|
34
|
+
if (done) break;
|
|
35
|
+
buffer += decoder.decode(value, { stream: true });
|
|
36
|
+
const lines = buffer.split('\n');
|
|
37
|
+
buffer = lines.pop() || '';
|
|
38
|
+
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (line.startsWith('data: ')) {
|
|
41
|
+
const data = line.slice(6).trim();
|
|
42
|
+
if (data === '[DONE]') return;
|
|
43
|
+
try {
|
|
44
|
+
const json = JSON.parse(data);
|
|
45
|
+
const delta = json.choices?.[0]?.delta?.content;
|
|
46
|
+
if (delta) yield delta;
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { PROVIDER, sendMessage };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const PROVIDER = {
|
|
2
|
+
name: 'gemini',
|
|
3
|
+
free: true,
|
|
4
|
+
streaming: true,
|
|
5
|
+
models: ['gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-2.0-flash-exp'],
|
|
6
|
+
baseURL: 'https://generativelanguage.googleapis.com/v1beta',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
async function* sendMessage(apiKey, messages, model = 'gemini-1.5-flash', stream = true) {
|
|
10
|
+
const url = `${PROVIDER.baseURL}/models/${model}:streamGenerateContent?key=${apiKey}&alt=sse`;
|
|
11
|
+
|
|
12
|
+
const contents = messages.map(m => ({
|
|
13
|
+
role: m.role === 'assistant' ? 'model' : m.role,
|
|
14
|
+
parts: [{ text: m.content }]
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const res = await fetch(url, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: { 'Content-Type': 'application/json' },
|
|
20
|
+
body: JSON.stringify({ contents }),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!res.ok) throw new Error(`Gemini API error: ${res.status} ${res.statusText}`);
|
|
24
|
+
|
|
25
|
+
const reader = res.body.getReader();
|
|
26
|
+
const decoder = new TextDecoder();
|
|
27
|
+
let buffer = '';
|
|
28
|
+
|
|
29
|
+
while (true) {
|
|
30
|
+
const { done, value } = await reader.read();
|
|
31
|
+
if (done) break;
|
|
32
|
+
buffer += decoder.decode(value, { stream: true });
|
|
33
|
+
const lines = buffer.split('\n');
|
|
34
|
+
buffer = lines.pop() || '';
|
|
35
|
+
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
if (line.startsWith('data: ')) {
|
|
38
|
+
try {
|
|
39
|
+
const json = JSON.parse(line.slice(6));
|
|
40
|
+
const text = json.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
41
|
+
if (text) yield text;
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { PROVIDER, sendMessage };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const PROVIDER = {
|
|
2
|
+
name: 'groq',
|
|
3
|
+
free: true,
|
|
4
|
+
streaming: true,
|
|
5
|
+
models: ['llama3-70b-8192', 'llama3-8b-8192', 'mixtral-8x7b-32768', 'gemma2-9b-it'],
|
|
6
|
+
baseURL: 'https://api.groq.com/openai/v1',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
async function* sendMessage(apiKey, messages, model = 'llama3-70b-8192', stream = true) {
|
|
10
|
+
const url = `${PROVIDER.baseURL}/chat/completions`;
|
|
11
|
+
const body = { model, messages, stream };
|
|
12
|
+
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify(body),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!res.ok) throw new Error(`Groq API error: ${res.status} ${res.statusText}`);
|
|
20
|
+
|
|
21
|
+
if (!stream) {
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
yield data.choices[0].message.content;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const reader = res.body.getReader();
|
|
28
|
+
const decoder = new TextDecoder();
|
|
29
|
+
let buffer = '';
|
|
30
|
+
|
|
31
|
+
while (true) {
|
|
32
|
+
const { done, value } = await reader.read();
|
|
33
|
+
if (done) break;
|
|
34
|
+
buffer += decoder.decode(value, { stream: true });
|
|
35
|
+
const lines = buffer.split('\n');
|
|
36
|
+
buffer = lines.pop() || '';
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
if (line.startsWith('data: ')) {
|
|
40
|
+
const data = line.slice(6).trim();
|
|
41
|
+
if (data === '[DONE]') return;
|
|
42
|
+
try {
|
|
43
|
+
const json = JSON.parse(data);
|
|
44
|
+
const delta = json.choices?.[0]?.delta?.content;
|
|
45
|
+
if (delta) yield delta;
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { PROVIDER, sendMessage };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as groq from './groq.js';
|
|
2
|
+
import * as gemini from './gemini.js';
|
|
3
|
+
import * as deepseek from './deepseek.js';
|
|
4
|
+
import * as openrouter from './openrouter.js';
|
|
5
|
+
import * as openai from './openai.js';
|
|
6
|
+
import * as claude from './claude.js';
|
|
7
|
+
|
|
8
|
+
const providers = { groq, gemini, deepseek, openrouter, openai, anthropic: claude };
|
|
9
|
+
|
|
10
|
+
const PRIORITY = ['groq', 'gemini', 'deepseek', 'openrouter', 'openai', 'anthropic'];
|
|
11
|
+
|
|
12
|
+
const capabilities = {
|
|
13
|
+
groq: { free: true, streaming: true, models: ['llama3-70b-8192', 'llama3-8b-8192', 'mixtral-8x7b-32768', 'gemma2-9b-it'], baseURL: 'https://api.groq.com/openai/v1' },
|
|
14
|
+
gemini: { free: true, streaming: true, models: ['gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-2.0-flash-exp'], baseURL: 'https://generativelanguage.googleapis.com/v1beta' },
|
|
15
|
+
deepseek: { free: false, cheap: true, streaming: true, models: ['deepseek-chat', 'deepseek-coder'], baseURL: 'https://api.deepseek.com/v1' },
|
|
16
|
+
openrouter: { free: true, streaming: true, models: ['meta-llama/llama-3.1-8b-instruct:free', 'google/gemma-2-9b-it:free', 'mistralai/mistral-7b-instruct:free'], baseURL: 'https://openrouter.ai/api/v1' },
|
|
17
|
+
anthropic: { free: false, streaming: true, models: ['claude-3-5-haiku-20241022', 'claude-3-5-sonnet-20241022'], baseURL: 'https://api.anthropic.com/v1' },
|
|
18
|
+
openai: { free: false, streaming: true, models: ['gpt-4o-mini', 'gpt-4o', 'gpt-3.5-turbo'], baseURL: 'https://api.openai.com/v1' },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
async function sendMessage(apiKey, messages, model, stream = true) {
|
|
22
|
+
const [providerName] = model.split('/');
|
|
23
|
+
const provider = providers[providerName];
|
|
24
|
+
if (!provider) throw new Error(`Unknown provider: ${providerName}`);
|
|
25
|
+
|
|
26
|
+
const modelName = model.includes('/') ? model.slice(model.indexOf('/') + 1) : model;
|
|
27
|
+
return provider.sendMessage(apiKey, messages, modelName, stream);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function listModels(providerName) {
|
|
31
|
+
const cap = capabilities[providerName];
|
|
32
|
+
return cap ? cap.models : [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getPriorityProviders(configuredProviders) {
|
|
36
|
+
return PRIORITY.filter(p => configuredProviders.includes(p));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { sendMessage, listModels, getPriorityProviders, capabilities, providers };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const PROVIDER = {
|
|
2
|
+
name: 'openai',
|
|
3
|
+
free: false,
|
|
4
|
+
streaming: true,
|
|
5
|
+
models: ['gpt-4o-mini', 'gpt-4o', 'gpt-3.5-turbo'],
|
|
6
|
+
baseURL: 'https://api.openai.com/v1',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
async function* sendMessage(apiKey, messages, model = 'gpt-4o-mini', stream = true) {
|
|
10
|
+
const url = `${PROVIDER.baseURL}/chat/completions`;
|
|
11
|
+
const body = { model, messages, stream };
|
|
12
|
+
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify(body),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!res.ok) throw new Error(`OpenAI API error: ${res.status} ${res.statusText}`);
|
|
20
|
+
|
|
21
|
+
if (!stream) {
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
yield data.choices[0].message.content;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const reader = res.body.getReader();
|
|
28
|
+
const decoder = new TextDecoder();
|
|
29
|
+
let buffer = '';
|
|
30
|
+
|
|
31
|
+
while (true) {
|
|
32
|
+
const { done, value } = await reader.read();
|
|
33
|
+
if (done) break;
|
|
34
|
+
buffer += decoder.decode(value, { stream: true });
|
|
35
|
+
const lines = buffer.split('\n');
|
|
36
|
+
buffer = lines.pop() || '';
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
if (line.startsWith('data: ')) {
|
|
40
|
+
const data = line.slice(6).trim();
|
|
41
|
+
if (data === '[DONE]') return;
|
|
42
|
+
try {
|
|
43
|
+
const json = JSON.parse(data);
|
|
44
|
+
const delta = json.choices?.[0]?.delta?.content;
|
|
45
|
+
if (delta) yield delta;
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { PROVIDER, sendMessage };
|