orquesta-cli 0.1.16 → 0.1.17
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/dist/cli.js +85 -0
- package/dist/core/config/auto-detect.d.ts +17 -0
- package/dist/core/config/auto-detect.js +123 -0
- package/dist/core/config/providers.d.ts +29 -0
- package/dist/core/config/providers.js +277 -0
- package/dist/core/llm/llm-client.js +21 -5
- package/dist/orquesta/config-sync.js +6 -1
- package/dist/orquesta/connection.d.ts +2 -2
- package/dist/orquesta/connection.js +56 -47
- package/dist/setup/first-run-setup.js +32 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,8 @@ import { initializeOptionalTools } from './tools/registry.js';
|
|
|
13
13
|
import { sessionManager } from './core/session/session-manager.js';
|
|
14
14
|
import { connectWithToken, showConnectionStatus, disconnectFromOrquesta, switchProject } from './setup/first-run-setup.js';
|
|
15
15
|
import { syncOrquestaConfigs } from './orquesta/config-sync.js';
|
|
16
|
+
import { scanProviders, scanProvider, toEndpointConfig } from './core/config/auto-detect.js';
|
|
17
|
+
import { PROVIDERS } from './core/config/providers.js';
|
|
16
18
|
const require = createRequire(import.meta.url);
|
|
17
19
|
const packageJson = require('../package.json');
|
|
18
20
|
const program = new Command();
|
|
@@ -34,6 +36,8 @@ program
|
|
|
34
36
|
.option('--status', 'Show Orquesta connection status and exit')
|
|
35
37
|
.option('--disconnect', 'Disconnect from Orquesta and exit')
|
|
36
38
|
.option('--sync', 'Sync configurations with Orquesta and exit')
|
|
39
|
+
.option('--scan', 'Scan for available LLM providers (env vars + local ports)')
|
|
40
|
+
.option('--add-provider <providerId>', 'Add a specific provider by ID (e.g., openai, anthropic, ollama)')
|
|
37
41
|
.action(async (options) => {
|
|
38
42
|
if (options.eval) {
|
|
39
43
|
await runEvalMode();
|
|
@@ -59,6 +63,85 @@ program
|
|
|
59
63
|
process.exit(1);
|
|
60
64
|
}
|
|
61
65
|
}
|
|
66
|
+
if (options.scan) {
|
|
67
|
+
const ora = (await import('ora')).default;
|
|
68
|
+
const spinner = ora({ text: chalk.cyan('Scanning for LLM providers...'), color: 'cyan' }).start();
|
|
69
|
+
try {
|
|
70
|
+
const result = await scanProviders();
|
|
71
|
+
spinner.stop();
|
|
72
|
+
if (result.detected.length === 0) {
|
|
73
|
+
console.log(chalk.yellow('\nNo LLM providers detected.'));
|
|
74
|
+
console.log(chalk.dim('Set environment variables (e.g., OPENAI_API_KEY) or start a local provider (Ollama, LM Studio).'));
|
|
75
|
+
console.log(chalk.dim('\nSupported providers:'));
|
|
76
|
+
for (const p of PROVIDERS) {
|
|
77
|
+
const envHint = p.envVars.length > 0 ? chalk.dim(` (${p.envVars[0]})`) : p.isLocal ? chalk.dim(` (port ${p.localPort})`) : '';
|
|
78
|
+
console.log(chalk.white(` ${p.name}${envHint}`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(chalk.green(`\nDetected ${result.detected.length} provider(s):\n`));
|
|
83
|
+
for (const d of result.detected) {
|
|
84
|
+
const modelCount = d.discoveredModels.length;
|
|
85
|
+
console.log(chalk.white(` ${d.provider.name}`));
|
|
86
|
+
console.log(chalk.dim(` Source: ${d.source}`));
|
|
87
|
+
console.log(chalk.dim(` Models: ${modelCount} discovered`));
|
|
88
|
+
if (modelCount > 0) {
|
|
89
|
+
const shown = d.discoveredModels.slice(0, 5);
|
|
90
|
+
for (const m of shown) {
|
|
91
|
+
console.log(chalk.dim(` - ${m.name} (${m.id})`));
|
|
92
|
+
}
|
|
93
|
+
if (modelCount > 5)
|
|
94
|
+
console.log(chalk.dim(` ... and ${modelCount - 5} more`));
|
|
95
|
+
}
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
if (result.notFound.length > 0) {
|
|
99
|
+
console.log(chalk.dim(`Not found: ${result.notFound.join(', ')}`));
|
|
100
|
+
}
|
|
101
|
+
console.log(chalk.cyan('\nUse --add-provider <id> to add a detected provider as an endpoint.'));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
spinner.fail(chalk.red('Scan failed'));
|
|
106
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (options.addProvider) {
|
|
111
|
+
const ora = (await import('ora')).default;
|
|
112
|
+
const spinner = ora({ text: chalk.cyan(`Detecting ${options.addProvider}...`), color: 'cyan' }).start();
|
|
113
|
+
try {
|
|
114
|
+
const detected = await scanProvider(options.addProvider);
|
|
115
|
+
if (!detected) {
|
|
116
|
+
spinner.fail(chalk.red(`Provider '${options.addProvider}' not found or not available`));
|
|
117
|
+
const provider = PROVIDERS.find(p => p.id === options.addProvider);
|
|
118
|
+
if (provider && provider.envVars.length > 0) {
|
|
119
|
+
console.log(chalk.dim(`Set ${provider.envVars[0]} environment variable and try again.`));
|
|
120
|
+
}
|
|
121
|
+
else if (provider?.isLocal) {
|
|
122
|
+
console.log(chalk.dim(`Start ${provider.name} on port ${provider.localPort} and try again.`));
|
|
123
|
+
}
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const endpointConfig = toEndpointConfig(detected);
|
|
127
|
+
await configManager.addEndpoint(endpointConfig);
|
|
128
|
+
if (!configManager.getCurrentModel() && endpointConfig.models.length > 0) {
|
|
129
|
+
await configManager.setCurrentEndpoint(endpointConfig.id);
|
|
130
|
+
await configManager.setCurrentModel(endpointConfig.models[0].id);
|
|
131
|
+
}
|
|
132
|
+
spinner.succeed(chalk.green(`Added ${detected.provider.name} with ${detected.discoveredModels.length} model(s)`));
|
|
133
|
+
console.log(chalk.dim(` Endpoint: ${endpointConfig.baseUrl}`));
|
|
134
|
+
if (detected.discoveredModels.length > 0) {
|
|
135
|
+
console.log(chalk.dim(` Models: ${detected.discoveredModels.slice(0, 5).map(m => m.id).join(', ')}${detected.discoveredModels.length > 5 ? ` (+${detected.discoveredModels.length - 5} more)` : ''}`));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
spinner.fail(chalk.red('Failed to add provider'));
|
|
140
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
62
145
|
if (options.sync) {
|
|
63
146
|
if (!configManager.hasOrquestaConnection()) {
|
|
64
147
|
console.log(chalk.yellow('Not connected to Orquesta. Use --token to connect first.'));
|
|
@@ -196,6 +279,8 @@ program.on('command:*', () => {
|
|
|
196
279
|
console.log(chalk.white(' --status Show Orquesta connection status\n'));
|
|
197
280
|
console.log(chalk.white(' --sync Sync configurations with Orquesta\n'));
|
|
198
281
|
console.log(chalk.white(' --disconnect Disconnect from Orquesta\n'));
|
|
282
|
+
console.log(chalk.white(' --scan Scan for available LLM providers\n'));
|
|
283
|
+
console.log(chalk.white(' --add-provider <id> Add a provider (e.g., openai, anthropic, ollama)\n'));
|
|
199
284
|
console.log(chalk.white(' --verbose Enable verbose logging\n'));
|
|
200
285
|
console.log(chalk.white(' --debug Enable debug logging\n'));
|
|
201
286
|
console.log(chalk.white('\nUse /help in interactive mode for more help.\n'));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ProviderDefinition } from './providers.js';
|
|
2
|
+
import { EndpointConfig, ModelInfo } from '../../types/index.js';
|
|
3
|
+
export interface DetectedProvider {
|
|
4
|
+
provider: ProviderDefinition;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
source: string;
|
|
7
|
+
discoveredModels: ModelInfo[];
|
|
8
|
+
}
|
|
9
|
+
export interface ScanResult {
|
|
10
|
+
detected: DetectedProvider[];
|
|
11
|
+
notFound: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function scanProviders(): Promise<ScanResult>;
|
|
14
|
+
export declare function scanProvider(providerId: string, apiKey?: string): Promise<DetectedProvider | null>;
|
|
15
|
+
export declare function toEndpointConfig(detected: DetectedProvider): EndpointConfig;
|
|
16
|
+
export declare function fetchModels(provider: ProviderDefinition, apiKey: string): Promise<ModelInfo[]>;
|
|
17
|
+
//# sourceMappingURL=auto-detect.d.ts.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { PROVIDERS, buildAuthHeaders } from './providers.js';
|
|
3
|
+
export async function scanProviders() {
|
|
4
|
+
const detected = [];
|
|
5
|
+
const notFound = [];
|
|
6
|
+
for (const provider of PROVIDERS.filter((p) => !p.isLocal)) {
|
|
7
|
+
const apiKey = findEnvVar(provider.envVars);
|
|
8
|
+
if (apiKey) {
|
|
9
|
+
const models = await fetchModels(provider, apiKey).catch(() => []);
|
|
10
|
+
detected.push({
|
|
11
|
+
provider,
|
|
12
|
+
apiKey,
|
|
13
|
+
source: provider.envVars.find((v) => process.env[v]) || provider.envVars[0] || provider.id,
|
|
14
|
+
discoveredModels: models.length > 0 ? models : fallbackModels(provider),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
notFound.push(provider.id);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const localProbes = PROVIDERS.filter((p) => p.isLocal).map(async (provider) => {
|
|
22
|
+
const running = await probeLocal(provider);
|
|
23
|
+
if (running) {
|
|
24
|
+
const models = await fetchModels(provider, '').catch(() => []);
|
|
25
|
+
detected.push({
|
|
26
|
+
provider,
|
|
27
|
+
source: 'local-probe',
|
|
28
|
+
discoveredModels: models.length > 0 ? models : fallbackModels(provider),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
notFound.push(provider.id);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
await Promise.all(localProbes);
|
|
36
|
+
return { detected, notFound };
|
|
37
|
+
}
|
|
38
|
+
export async function scanProvider(providerId, apiKey) {
|
|
39
|
+
const provider = PROVIDERS.find((p) => p.id === providerId);
|
|
40
|
+
if (!provider)
|
|
41
|
+
return null;
|
|
42
|
+
const key = apiKey || findEnvVar(provider.envVars) || '';
|
|
43
|
+
if (provider.requiresApiKey && !key)
|
|
44
|
+
return null;
|
|
45
|
+
if (provider.isLocal) {
|
|
46
|
+
const running = await probeLocal(provider);
|
|
47
|
+
if (!running)
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const models = await fetchModels(provider, key).catch(() => []);
|
|
51
|
+
return {
|
|
52
|
+
provider,
|
|
53
|
+
apiKey: key || undefined,
|
|
54
|
+
source: apiKey ? 'manual' : provider.isLocal ? 'local-probe' : (provider.envVars.find((v) => process.env[v]) || 'manual'),
|
|
55
|
+
discoveredModels: models.length > 0 ? models : fallbackModels(provider),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function toEndpointConfig(detected) {
|
|
59
|
+
return {
|
|
60
|
+
id: `ep_${detected.provider.id}_${Date.now()}`,
|
|
61
|
+
name: detected.provider.name,
|
|
62
|
+
baseUrl: detected.provider.baseUrl,
|
|
63
|
+
apiKey: detected.apiKey,
|
|
64
|
+
provider: detected.provider.id,
|
|
65
|
+
models: detected.discoveredModels,
|
|
66
|
+
createdAt: new Date(),
|
|
67
|
+
updatedAt: new Date(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function findEnvVar(envVars) {
|
|
71
|
+
for (const name of envVars) {
|
|
72
|
+
const value = process.env[name];
|
|
73
|
+
if (value && value.trim().length > 0)
|
|
74
|
+
return value.trim();
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
async function probeLocal(provider) {
|
|
79
|
+
const url = provider.healthCheckPath || `${provider.baseUrl}/models`;
|
|
80
|
+
try {
|
|
81
|
+
const resp = await axios.get(url, { timeout: 2000 });
|
|
82
|
+
return resp.status >= 200 && resp.status < 400;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function fetchModels(provider, apiKey) {
|
|
89
|
+
if (!provider.modelsEndpoint)
|
|
90
|
+
return [];
|
|
91
|
+
const url = `${provider.baseUrl}${provider.modelsEndpoint}`;
|
|
92
|
+
const headers = buildAuthHeaders(provider, apiKey);
|
|
93
|
+
const resp = await axios.get(url, { headers, timeout: 10000 });
|
|
94
|
+
const data = resp.data;
|
|
95
|
+
const models = data?.data || data?.models || [];
|
|
96
|
+
if (!Array.isArray(models) || models.length === 0)
|
|
97
|
+
return [];
|
|
98
|
+
return models
|
|
99
|
+
.filter((m) => {
|
|
100
|
+
const id = (m.id || m.model || '').toLowerCase();
|
|
101
|
+
if (/embed|tts|whisper|dall-e|moderation|audio/.test(id))
|
|
102
|
+
return false;
|
|
103
|
+
return true;
|
|
104
|
+
})
|
|
105
|
+
.slice(0, 50)
|
|
106
|
+
.map((m) => ({
|
|
107
|
+
id: m.id || m.model || m.name,
|
|
108
|
+
name: m.name || m.display_name || m.id || m.model,
|
|
109
|
+
maxTokens: m.context_length || m.context_window || m.max_tokens || 4096,
|
|
110
|
+
enabled: true,
|
|
111
|
+
healthStatus: 'healthy',
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
function fallbackModels(provider) {
|
|
115
|
+
return provider.knownModels.map((m) => ({
|
|
116
|
+
id: m.id,
|
|
117
|
+
name: m.name,
|
|
118
|
+
maxTokens: m.maxTokens,
|
|
119
|
+
enabled: true,
|
|
120
|
+
healthStatus: 'healthy',
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=auto-detect.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type AuthMethod = 'bearer' | 'x-api-key' | 'none';
|
|
2
|
+
export type ModelCapability = 'vision' | 'tools' | 'json_mode' | 'extended_thinking' | 'streaming';
|
|
3
|
+
export interface KnownModel {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
maxTokens: number;
|
|
7
|
+
capabilities?: ModelCapability[];
|
|
8
|
+
}
|
|
9
|
+
export interface ProviderDefinition {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
authMethod: AuthMethod;
|
|
14
|
+
envVars: string[];
|
|
15
|
+
requiresApiKey: boolean;
|
|
16
|
+
isLocal: boolean;
|
|
17
|
+
localPort?: number;
|
|
18
|
+
modelsEndpoint?: string;
|
|
19
|
+
knownModels: KnownModel[];
|
|
20
|
+
openaiCompatible: boolean;
|
|
21
|
+
extraHeaders?: Record<string, string>;
|
|
22
|
+
healthCheckPath?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const PROVIDERS: ProviderDefinition[];
|
|
25
|
+
export declare function getProvider(id: string): ProviderDefinition | undefined;
|
|
26
|
+
export declare function detectProviderFromUrl(baseUrl: string): ProviderDefinition | undefined;
|
|
27
|
+
export declare function modelHasCapability(providerId: string, modelId: string, capability: ModelCapability): boolean;
|
|
28
|
+
export declare function buildAuthHeaders(provider: ProviderDefinition, apiKey: string): Record<string, string>;
|
|
29
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
export const PROVIDERS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'openai',
|
|
4
|
+
name: 'OpenAI',
|
|
5
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
6
|
+
authMethod: 'bearer',
|
|
7
|
+
envVars: ['OPENAI_API_KEY'],
|
|
8
|
+
requiresApiKey: true,
|
|
9
|
+
isLocal: false,
|
|
10
|
+
modelsEndpoint: '/models',
|
|
11
|
+
openaiCompatible: true,
|
|
12
|
+
knownModels: [
|
|
13
|
+
{ id: 'gpt-4o', name: 'GPT-4o', maxTokens: 128000, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
14
|
+
{ id: 'gpt-4o-mini', name: 'GPT-4o Mini', maxTokens: 128000, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
15
|
+
{ id: 'gpt-4-turbo', name: 'GPT-4 Turbo', maxTokens: 128000, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
16
|
+
{ id: 'o1', name: 'o1', maxTokens: 200000, capabilities: ['extended_thinking', 'streaming'] },
|
|
17
|
+
{ id: 'o1-mini', name: 'o1 Mini', maxTokens: 128000, capabilities: ['extended_thinking', 'streaming'] },
|
|
18
|
+
{ id: 'o3-mini', name: 'o3 Mini', maxTokens: 200000, capabilities: ['extended_thinking', 'tools', 'streaming'] },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'anthropic',
|
|
23
|
+
name: 'Anthropic',
|
|
24
|
+
baseUrl: 'https://api.anthropic.com/v1',
|
|
25
|
+
authMethod: 'x-api-key',
|
|
26
|
+
envVars: ['ANTHROPIC_API_KEY'],
|
|
27
|
+
requiresApiKey: true,
|
|
28
|
+
isLocal: false,
|
|
29
|
+
modelsEndpoint: '/models',
|
|
30
|
+
openaiCompatible: false,
|
|
31
|
+
extraHeaders: { 'anthropic-version': '2023-06-01' },
|
|
32
|
+
knownModels: [
|
|
33
|
+
{ id: 'claude-opus-4-6', name: 'Claude Opus 4.6', maxTokens: 200000, capabilities: ['vision', 'tools', 'extended_thinking', 'streaming'] },
|
|
34
|
+
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', maxTokens: 200000, capabilities: ['vision', 'tools', 'extended_thinking', 'streaming'] },
|
|
35
|
+
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', maxTokens: 200000, capabilities: ['vision', 'tools', 'streaming'] },
|
|
36
|
+
{ id: 'claude-sonnet-4-5-20250514', name: 'Claude Sonnet 4.5', maxTokens: 200000, capabilities: ['vision', 'tools', 'extended_thinking', 'streaming'] },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'google',
|
|
41
|
+
name: 'Google Gemini',
|
|
42
|
+
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
43
|
+
authMethod: 'bearer',
|
|
44
|
+
envVars: ['GEMINI_API_KEY', 'GOOGLE_API_KEY'],
|
|
45
|
+
requiresApiKey: true,
|
|
46
|
+
isLocal: false,
|
|
47
|
+
modelsEndpoint: '/models',
|
|
48
|
+
openaiCompatible: true,
|
|
49
|
+
knownModels: [
|
|
50
|
+
{ id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', maxTokens: 1048576, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
51
|
+
{ id: 'gemini-2.0-pro', name: 'Gemini 2.0 Pro', maxTokens: 1048576, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
52
|
+
{ id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro', maxTokens: 2097152, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
53
|
+
{ id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash', maxTokens: 1048576, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'mistral',
|
|
58
|
+
name: 'Mistral AI',
|
|
59
|
+
baseUrl: 'https://api.mistral.ai/v1',
|
|
60
|
+
authMethod: 'bearer',
|
|
61
|
+
envVars: ['MISTRAL_API_KEY'],
|
|
62
|
+
requiresApiKey: true,
|
|
63
|
+
isLocal: false,
|
|
64
|
+
modelsEndpoint: '/models',
|
|
65
|
+
openaiCompatible: true,
|
|
66
|
+
knownModels: [
|
|
67
|
+
{ id: 'mistral-large-latest', name: 'Mistral Large', maxTokens: 128000, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
68
|
+
{ id: 'mistral-small-latest', name: 'Mistral Small', maxTokens: 128000, capabilities: ['tools', 'json_mode', 'streaming'] },
|
|
69
|
+
{ id: 'codestral-latest', name: 'Codestral', maxTokens: 256000, capabilities: ['tools', 'streaming'] },
|
|
70
|
+
{ id: 'magistral-small-latest', name: 'Magistral Small', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
71
|
+
{ id: 'magistral-medium-latest', name: 'Magistral Medium', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'groq',
|
|
76
|
+
name: 'Groq',
|
|
77
|
+
baseUrl: 'https://api.groq.com/openai/v1',
|
|
78
|
+
authMethod: 'bearer',
|
|
79
|
+
envVars: ['GROQ_API_KEY'],
|
|
80
|
+
requiresApiKey: true,
|
|
81
|
+
isLocal: false,
|
|
82
|
+
modelsEndpoint: '/models',
|
|
83
|
+
openaiCompatible: true,
|
|
84
|
+
knownModels: [
|
|
85
|
+
{ id: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B', maxTokens: 128000, capabilities: ['tools', 'json_mode', 'streaming'] },
|
|
86
|
+
{ id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B', maxTokens: 128000, capabilities: ['tools', 'json_mode', 'streaming'] },
|
|
87
|
+
{ id: 'mixtral-8x7b-32768', name: 'Mixtral 8x7B', maxTokens: 32768, capabilities: ['tools', 'streaming'] },
|
|
88
|
+
{ id: 'gemma2-9b-it', name: 'Gemma 2 9B', maxTokens: 8192, capabilities: ['streaming'] },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'together',
|
|
93
|
+
name: 'Together AI',
|
|
94
|
+
baseUrl: 'https://api.together.xyz/v1',
|
|
95
|
+
authMethod: 'bearer',
|
|
96
|
+
envVars: ['TOGETHER_API_KEY'],
|
|
97
|
+
requiresApiKey: true,
|
|
98
|
+
isLocal: false,
|
|
99
|
+
modelsEndpoint: '/models',
|
|
100
|
+
openaiCompatible: true,
|
|
101
|
+
knownModels: [
|
|
102
|
+
{ id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', name: 'Llama 3.3 70B Turbo', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
103
|
+
{ id: 'Qwen/Qwen2.5-72B-Instruct-Turbo', name: 'Qwen 2.5 72B Turbo', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
104
|
+
{ id: 'deepseek-ai/DeepSeek-V3', name: 'DeepSeek V3', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
105
|
+
{ id: 'mistralai/Mixtral-8x22B-Instruct-v0.1', name: 'Mixtral 8x22B', maxTokens: 65536, capabilities: ['tools', 'streaming'] },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'fireworks',
|
|
110
|
+
name: 'Fireworks AI',
|
|
111
|
+
baseUrl: 'https://api.fireworks.ai/inference/v1',
|
|
112
|
+
authMethod: 'bearer',
|
|
113
|
+
envVars: ['FIREWORKS_API_KEY'],
|
|
114
|
+
requiresApiKey: true,
|
|
115
|
+
isLocal: false,
|
|
116
|
+
openaiCompatible: true,
|
|
117
|
+
knownModels: [
|
|
118
|
+
{ id: 'accounts/fireworks/models/llama-v3p3-70b-instruct', name: 'Llama 3.3 70B', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
119
|
+
{ id: 'accounts/fireworks/models/deepseek-v3', name: 'DeepSeek V3', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
120
|
+
{ id: 'accounts/fireworks/models/qwen2p5-72b-instruct', name: 'Qwen 2.5 72B', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'deepseek',
|
|
125
|
+
name: 'DeepSeek',
|
|
126
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
127
|
+
authMethod: 'bearer',
|
|
128
|
+
envVars: ['DEEPSEEK_API_KEY'],
|
|
129
|
+
requiresApiKey: true,
|
|
130
|
+
isLocal: false,
|
|
131
|
+
modelsEndpoint: '/models',
|
|
132
|
+
openaiCompatible: true,
|
|
133
|
+
knownModels: [
|
|
134
|
+
{ id: 'deepseek-chat', name: 'DeepSeek Chat (V3)', maxTokens: 128000, capabilities: ['tools', 'json_mode', 'streaming'] },
|
|
135
|
+
{ id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', maxTokens: 64000, capabilities: ['extended_thinking', 'streaming'] },
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'xai',
|
|
140
|
+
name: 'xAI (Grok)',
|
|
141
|
+
baseUrl: 'https://api.x.ai/v1',
|
|
142
|
+
authMethod: 'bearer',
|
|
143
|
+
envVars: ['XAI_API_KEY'],
|
|
144
|
+
requiresApiKey: true,
|
|
145
|
+
isLocal: false,
|
|
146
|
+
modelsEndpoint: '/models',
|
|
147
|
+
openaiCompatible: true,
|
|
148
|
+
knownModels: [
|
|
149
|
+
{ id: 'grok-4', name: 'Grok 4', maxTokens: 131072, capabilities: ['vision', 'tools', 'streaming'] },
|
|
150
|
+
{ id: 'grok-3', name: 'Grok 3', maxTokens: 131072, capabilities: ['vision', 'tools', 'streaming'] },
|
|
151
|
+
{ id: 'grok-3-mini', name: 'Grok 3 Mini', maxTokens: 131072, capabilities: ['tools', 'streaming'] },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'perplexity',
|
|
156
|
+
name: 'Perplexity',
|
|
157
|
+
baseUrl: 'https://api.perplexity.ai',
|
|
158
|
+
authMethod: 'bearer',
|
|
159
|
+
envVars: ['PERPLEXITY_API_KEY'],
|
|
160
|
+
requiresApiKey: true,
|
|
161
|
+
isLocal: false,
|
|
162
|
+
openaiCompatible: true,
|
|
163
|
+
knownModels: [
|
|
164
|
+
{ id: 'sonar-pro', name: 'Sonar Pro', maxTokens: 200000, capabilities: ['streaming'] },
|
|
165
|
+
{ id: 'sonar', name: 'Sonar', maxTokens: 128000, capabilities: ['streaming'] },
|
|
166
|
+
{ id: 'sonar-reasoning-pro', name: 'Sonar Reasoning Pro', maxTokens: 128000, capabilities: ['extended_thinking', 'streaming'] },
|
|
167
|
+
{ id: 'sonar-reasoning', name: 'Sonar Reasoning', maxTokens: 128000, capabilities: ['extended_thinking', 'streaming'] },
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'openrouter',
|
|
172
|
+
name: 'OpenRouter',
|
|
173
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
174
|
+
authMethod: 'bearer',
|
|
175
|
+
envVars: ['OPENROUTER_API_KEY'],
|
|
176
|
+
requiresApiKey: true,
|
|
177
|
+
isLocal: false,
|
|
178
|
+
modelsEndpoint: '/models',
|
|
179
|
+
openaiCompatible: true,
|
|
180
|
+
knownModels: [
|
|
181
|
+
{ id: 'anthropic/claude-sonnet-4', name: 'Claude Sonnet 4', maxTokens: 200000, capabilities: ['vision', 'tools', 'streaming'] },
|
|
182
|
+
{ id: 'openai/gpt-4o', name: 'GPT-4o', maxTokens: 128000, capabilities: ['vision', 'tools', 'json_mode', 'streaming'] },
|
|
183
|
+
{ id: 'google/gemini-2.0-flash-001', name: 'Gemini 2.0 Flash', maxTokens: 1048576, capabilities: ['vision', 'tools', 'streaming'] },
|
|
184
|
+
{ id: 'meta-llama/llama-3.3-70b-instruct', name: 'Llama 3.3 70B', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
185
|
+
{ id: 'deepseek/deepseek-chat', name: 'DeepSeek Chat', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'ollama',
|
|
190
|
+
name: 'Ollama',
|
|
191
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
192
|
+
authMethod: 'none',
|
|
193
|
+
envVars: [],
|
|
194
|
+
requiresApiKey: false,
|
|
195
|
+
isLocal: true,
|
|
196
|
+
localPort: 11434,
|
|
197
|
+
modelsEndpoint: '/models',
|
|
198
|
+
openaiCompatible: true,
|
|
199
|
+
healthCheckPath: 'http://localhost:11434/',
|
|
200
|
+
knownModels: [
|
|
201
|
+
{ id: 'llama3.3', name: 'Llama 3.3', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
202
|
+
{ id: 'codellama', name: 'Code Llama', maxTokens: 16384, capabilities: ['streaming'] },
|
|
203
|
+
{ id: 'mistral', name: 'Mistral 7B', maxTokens: 32000, capabilities: ['tools', 'streaming'] },
|
|
204
|
+
{ id: 'deepseek-coder-v2', name: 'DeepSeek Coder V2', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
205
|
+
{ id: 'qwen2.5-coder', name: 'Qwen 2.5 Coder', maxTokens: 128000, capabilities: ['tools', 'streaming'] },
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'lmstudio',
|
|
210
|
+
name: 'LM Studio',
|
|
211
|
+
baseUrl: 'http://localhost:1234/v1',
|
|
212
|
+
authMethod: 'none',
|
|
213
|
+
envVars: [],
|
|
214
|
+
requiresApiKey: false,
|
|
215
|
+
isLocal: true,
|
|
216
|
+
localPort: 1234,
|
|
217
|
+
modelsEndpoint: '/models',
|
|
218
|
+
openaiCompatible: true,
|
|
219
|
+
knownModels: [],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'vllm',
|
|
223
|
+
name: 'vLLM',
|
|
224
|
+
baseUrl: 'http://localhost:8000/v1',
|
|
225
|
+
authMethod: 'none',
|
|
226
|
+
envVars: [],
|
|
227
|
+
requiresApiKey: false,
|
|
228
|
+
isLocal: true,
|
|
229
|
+
localPort: 8000,
|
|
230
|
+
modelsEndpoint: '/models',
|
|
231
|
+
openaiCompatible: true,
|
|
232
|
+
knownModels: [],
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
export function getProvider(id) {
|
|
236
|
+
return PROVIDERS.find((p) => p.id === id);
|
|
237
|
+
}
|
|
238
|
+
export function detectProviderFromUrl(baseUrl) {
|
|
239
|
+
const url = baseUrl.toLowerCase();
|
|
240
|
+
for (const provider of PROVIDERS) {
|
|
241
|
+
const providerHost = new URL(provider.baseUrl).hostname;
|
|
242
|
+
if (url.includes(providerHost))
|
|
243
|
+
return provider;
|
|
244
|
+
}
|
|
245
|
+
for (const provider of PROVIDERS.filter((p) => p.isLocal && p.localPort)) {
|
|
246
|
+
if (url.includes(`:${provider.localPort}`))
|
|
247
|
+
return provider;
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
export function modelHasCapability(providerId, modelId, capability) {
|
|
252
|
+
const provider = getProvider(providerId);
|
|
253
|
+
if (!provider)
|
|
254
|
+
return false;
|
|
255
|
+
const model = provider.knownModels.find((m) => m.id === modelId);
|
|
256
|
+
return model?.capabilities?.includes(capability) ?? false;
|
|
257
|
+
}
|
|
258
|
+
export function buildAuthHeaders(provider, apiKey) {
|
|
259
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
260
|
+
switch (provider.authMethod) {
|
|
261
|
+
case 'bearer':
|
|
262
|
+
if (apiKey)
|
|
263
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
264
|
+
break;
|
|
265
|
+
case 'x-api-key':
|
|
266
|
+
if (apiKey)
|
|
267
|
+
headers['x-api-key'] = apiKey;
|
|
268
|
+
break;
|
|
269
|
+
case 'none':
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
if (provider.extraHeaders) {
|
|
273
|
+
Object.assign(headers, provider.extraHeaders);
|
|
274
|
+
}
|
|
275
|
+
return headers;
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { configManager } from '../config/config-manager.js';
|
|
3
|
+
import { getProvider, buildAuthHeaders } from '../config/providers.js';
|
|
3
4
|
import { NetworkError, APIError, TimeoutError, ConnectionError, } from '../../errors/network.js';
|
|
4
5
|
import { LLMError, TokenLimitError, RateLimitError, ContextLengthError, } from '../../errors/llm.js';
|
|
5
6
|
import { logger, isLLMLogEnabled } from '../../utils/logger.js';
|
|
@@ -23,12 +24,20 @@ export class LLMClient {
|
|
|
23
24
|
this.apiKey = endpoint.apiKey || '';
|
|
24
25
|
this.model = currentModel.id;
|
|
25
26
|
this.modelName = currentModel.name;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
let headers;
|
|
28
|
+
const providerDef = endpoint.provider ? getProvider(endpoint.provider) : undefined;
|
|
29
|
+
if (providerDef) {
|
|
30
|
+
headers = buildAuthHeaders(providerDef, this.apiKey);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
headers = {
|
|
29
34
|
'Content-Type': 'application/json',
|
|
30
35
|
...(this.apiKey && { Authorization: `Bearer ${this.apiKey}` }),
|
|
31
|
-
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
this.axiosInstance = axios.create({
|
|
39
|
+
baseURL: this.baseUrl,
|
|
40
|
+
headers,
|
|
32
41
|
timeout: 600000,
|
|
33
42
|
});
|
|
34
43
|
}
|
|
@@ -151,7 +160,14 @@ export class LLMClient {
|
|
|
151
160
|
throw new Error('INTERRUPTED');
|
|
152
161
|
}
|
|
153
162
|
if (currentAttempt < maxRetries && this.isRetryableError(error)) {
|
|
154
|
-
|
|
163
|
+
let delay = Math.pow(2, currentAttempt - 1) * 1000;
|
|
164
|
+
if (axios.isAxiosError(error) && error.response?.status === 429) {
|
|
165
|
+
const retryAfter = error.response.headers['retry-after'];
|
|
166
|
+
if (retryAfter) {
|
|
167
|
+
const retryAfterMs = (parseInt(retryAfter, 10) || 1) * 1000;
|
|
168
|
+
delay = Math.max(delay, retryAfterMs);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
155
171
|
logger.debug(`LLM call failed (${currentAttempt}/${maxRetries}), retrying after ${delay}ms...`, {
|
|
156
172
|
error: error.message,
|
|
157
173
|
attempt: currentAttempt,
|
|
@@ -123,7 +123,12 @@ export async function pushConfigsToOrquesta() {
|
|
|
123
123
|
Authorization: `Bearer ${orquestaConfig.token}`,
|
|
124
124
|
'Content-Type': 'application/json',
|
|
125
125
|
},
|
|
126
|
-
body: JSON.stringify({
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
endpoints: endpoints.map(ep => ({
|
|
128
|
+
...ep,
|
|
129
|
+
metadata: { provider: ep.provider },
|
|
130
|
+
})),
|
|
131
|
+
}),
|
|
127
132
|
});
|
|
128
133
|
if (!response.ok) {
|
|
129
134
|
const errorData = (await response.json());
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export declare class OrquestaConnection {
|
|
2
2
|
private token;
|
|
3
|
-
private
|
|
4
|
-
private channel;
|
|
3
|
+
private socket;
|
|
5
4
|
private connectionInfo;
|
|
6
5
|
private connected;
|
|
7
6
|
private heartbeatInterval;
|
|
@@ -22,6 +21,7 @@ export declare class OrquestaConnection {
|
|
|
22
21
|
data: Record<string, unknown>;
|
|
23
22
|
}): Promise<void>;
|
|
24
23
|
private validateToken;
|
|
24
|
+
private connectSocket;
|
|
25
25
|
private subscribeToChannel;
|
|
26
26
|
private startHeartbeat;
|
|
27
27
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
2
|
import * as os from 'os';
|
|
3
|
-
import WebSocket from 'ws';
|
|
4
|
-
if (typeof globalThis.WebSocket === 'undefined') {
|
|
5
|
-
globalThis.WebSocket = WebSocket;
|
|
6
|
-
}
|
|
7
3
|
const ORQUESTA_API = process.env['ORQUESTA_API_URL'] || 'https://orquesta.live';
|
|
4
|
+
const ORQUESTA_WS = process.env['ORQUESTA_WS_URL'] || 'wss://ws.orquesta.live';
|
|
8
5
|
export class OrquestaConnection {
|
|
9
6
|
token;
|
|
10
|
-
|
|
11
|
-
channel = null;
|
|
7
|
+
socket = null;
|
|
12
8
|
connectionInfo = null;
|
|
13
9
|
connected = false;
|
|
14
10
|
heartbeatInterval = null;
|
|
@@ -18,7 +14,7 @@ export class OrquestaConnection {
|
|
|
18
14
|
async connect() {
|
|
19
15
|
try {
|
|
20
16
|
const validation = await this.validateToken();
|
|
21
|
-
if (!validation.valid
|
|
17
|
+
if (!validation.valid) {
|
|
22
18
|
return {
|
|
23
19
|
success: false,
|
|
24
20
|
error: validation.error || 'Invalid response from server'
|
|
@@ -28,19 +24,9 @@ export class OrquestaConnection {
|
|
|
28
24
|
projectId: validation.projectId,
|
|
29
25
|
projectName: validation.projectName,
|
|
30
26
|
tokenName: validation.tokenName,
|
|
31
|
-
supabaseUrl: validation.supabaseUrl,
|
|
32
|
-
supabaseAnonKey: validation.supabaseAnonKey,
|
|
33
27
|
channelName: validation.channelName,
|
|
34
28
|
};
|
|
35
|
-
this.
|
|
36
|
-
realtime: {
|
|
37
|
-
params: {
|
|
38
|
-
eventsPerSecond: 50,
|
|
39
|
-
},
|
|
40
|
-
timeout: 30000,
|
|
41
|
-
heartbeatIntervalMs: 15000,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
29
|
+
await this.connectSocket();
|
|
44
30
|
await this.subscribeToChannel();
|
|
45
31
|
this.startHeartbeat();
|
|
46
32
|
this.connected = true;
|
|
@@ -63,9 +49,13 @@ export class OrquestaConnection {
|
|
|
63
49
|
clearInterval(this.heartbeatInterval);
|
|
64
50
|
this.heartbeatInterval = null;
|
|
65
51
|
}
|
|
66
|
-
if (this.
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
if (this.socket) {
|
|
53
|
+
if (this.connectionInfo) {
|
|
54
|
+
this.socket.emit('unsubscribe', { channel: this.connectionInfo.channelName });
|
|
55
|
+
}
|
|
56
|
+
this.socket.removeAllListeners();
|
|
57
|
+
this.socket.disconnect();
|
|
58
|
+
this.socket = null;
|
|
69
59
|
}
|
|
70
60
|
if (this.token) {
|
|
71
61
|
try {
|
|
@@ -91,17 +81,18 @@ export class OrquestaConnection {
|
|
|
91
81
|
};
|
|
92
82
|
}
|
|
93
83
|
async sendSessionEvent(event) {
|
|
94
|
-
if (!this.
|
|
84
|
+
if (!this.socket || !this.connected || !this.connectionInfo) {
|
|
95
85
|
console.warn('Not connected to Orquesta - session event not sent');
|
|
96
86
|
return;
|
|
97
87
|
}
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
this.socket.emit('broadcast', {
|
|
89
|
+
channel: this.connectionInfo.channelName,
|
|
100
90
|
event: event.type,
|
|
101
91
|
payload: {
|
|
102
92
|
...event.data,
|
|
103
93
|
timestamp: Date.now(),
|
|
104
94
|
},
|
|
95
|
+
self: true,
|
|
105
96
|
});
|
|
106
97
|
}
|
|
107
98
|
async validateToken() {
|
|
@@ -123,9 +114,39 @@ export class OrquestaConnection {
|
|
|
123
114
|
}
|
|
124
115
|
return data;
|
|
125
116
|
}
|
|
117
|
+
connectSocket() {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
this.socket = io(ORQUESTA_WS, {
|
|
120
|
+
auth: { agentToken: this.token },
|
|
121
|
+
transports: ['websocket'],
|
|
122
|
+
reconnection: true,
|
|
123
|
+
reconnectionAttempts: Infinity,
|
|
124
|
+
reconnectionDelay: 1000,
|
|
125
|
+
reconnectionDelayMax: 30000,
|
|
126
|
+
});
|
|
127
|
+
const onConnect = () => {
|
|
128
|
+
this.socket?.off('connect_error', onError);
|
|
129
|
+
resolve();
|
|
130
|
+
};
|
|
131
|
+
const onError = (err) => {
|
|
132
|
+
this.socket?.off('connect', onConnect);
|
|
133
|
+
this.socket?.disconnect();
|
|
134
|
+
this.socket = null;
|
|
135
|
+
reject(new Error(`Socket.io connection failed: ${err.message}`));
|
|
136
|
+
};
|
|
137
|
+
this.socket.once('connect', onConnect);
|
|
138
|
+
this.socket.once('connect_error', onError);
|
|
139
|
+
this.socket.on('reconnect', () => {
|
|
140
|
+
if (this.connectionInfo) {
|
|
141
|
+
this.subscribeToChannel().catch(() => {
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
126
147
|
async subscribeToChannel() {
|
|
127
|
-
if (!this.
|
|
128
|
-
throw new Error('
|
|
148
|
+
if (!this.socket || !this.connectionInfo) {
|
|
149
|
+
throw new Error('Socket not initialized');
|
|
129
150
|
}
|
|
130
151
|
const agentInfo = {
|
|
131
152
|
hostname: os.hostname(),
|
|
@@ -134,28 +155,16 @@ export class OrquestaConnection {
|
|
|
134
155
|
type: 'orquesta-cli',
|
|
135
156
|
workingDirectory: process.cwd(),
|
|
136
157
|
};
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
broadcast: { self: true, ack: true },
|
|
140
|
-
presence: { key: agentInfo.hostname },
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
this.channel.on('broadcast', { event: 'team_message' }, ({ payload }) => {
|
|
158
|
+
this.socket.emit('subscribe', { channel: this.connectionInfo.channelName });
|
|
159
|
+
this.socket.on('team_message', (payload) => {
|
|
144
160
|
console.log('Team message:', payload);
|
|
145
161
|
});
|
|
146
|
-
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
153
|
-
resolve();
|
|
154
|
-
}
|
|
155
|
-
else if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {
|
|
156
|
-
reject(new Error(`Channel subscription failed: ${status}`));
|
|
157
|
-
}
|
|
158
|
-
});
|
|
162
|
+
this.socket.emit('presence:join', {
|
|
163
|
+
channel: this.connectionInfo.channelName,
|
|
164
|
+
user: {
|
|
165
|
+
...agentInfo,
|
|
166
|
+
connectedAt: new Date().toISOString(),
|
|
167
|
+
},
|
|
159
168
|
});
|
|
160
169
|
}
|
|
161
170
|
startHeartbeat() {
|
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { configManager } from '../core/config/config-manager.js';
|
|
4
4
|
import { syncOrquestaConfigs, fetchOrquestaProjects } from '../orquesta/config-sync.js';
|
|
5
|
+
import { scanProviders, toEndpointConfig } from '../core/config/auto-detect.js';
|
|
5
6
|
export function needsFirstRunSetup() {
|
|
6
7
|
return !configManager.hasEndpoints() && !configManager.hasOrquestaConnection();
|
|
7
8
|
}
|
|
@@ -9,8 +10,39 @@ export async function runFirstRunSetup(token) {
|
|
|
9
10
|
if (token) {
|
|
10
11
|
return await connectWithToken(token);
|
|
11
12
|
}
|
|
13
|
+
if (!configManager.hasEndpoints()) {
|
|
14
|
+
await autoDetectProviders();
|
|
15
|
+
}
|
|
12
16
|
return { connected: false, skipped: true };
|
|
13
17
|
}
|
|
18
|
+
async function autoDetectProviders() {
|
|
19
|
+
const spinner = ora({
|
|
20
|
+
text: chalk.cyan('Scanning for LLM providers...'),
|
|
21
|
+
color: 'cyan',
|
|
22
|
+
}).start();
|
|
23
|
+
try {
|
|
24
|
+
const result = await scanProviders();
|
|
25
|
+
if (result.detected.length === 0) {
|
|
26
|
+
spinner.info(chalk.yellow('No LLM providers detected. Use /config to add endpoints or --scan for details.'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let addedCount = 0;
|
|
30
|
+
for (const detected of result.detected) {
|
|
31
|
+
const endpoint = toEndpointConfig(detected);
|
|
32
|
+
await configManager.addEndpoint(endpoint);
|
|
33
|
+
addedCount++;
|
|
34
|
+
if (addedCount === 1 && endpoint.models.length > 0) {
|
|
35
|
+
await configManager.setCurrentEndpoint(endpoint.id);
|
|
36
|
+
await configManager.setCurrentModel(endpoint.models[0].id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const names = result.detected.map(d => d.provider.name).join(', ');
|
|
40
|
+
spinner.succeed(chalk.green(`Auto-detected ${addedCount} provider(s): ${names}`));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
spinner.info(chalk.dim('Provider auto-detection skipped (network error).'));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
14
46
|
export async function connectWithToken(token, projectId) {
|
|
15
47
|
const spinner = ora({
|
|
16
48
|
text: chalk.cyan('Validating token...'),
|
package/dist/types/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquesta-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -88,7 +88,6 @@
|
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
90
|
"@anthropic-ai/sdk": "^0.32.1",
|
|
91
|
-
"@supabase/supabase-js": "^2.39.0",
|
|
92
91
|
"axios": "^1.6.2",
|
|
93
92
|
"chalk": "^4.1.2",
|
|
94
93
|
"commander": "^11.1.0",
|
|
@@ -99,6 +98,7 @@
|
|
|
99
98
|
"inquirer": "^8.2.6",
|
|
100
99
|
"ora": "^5.4.1",
|
|
101
100
|
"semver": "^7.7.3",
|
|
101
|
+
"socket.io-client": "^4.8.3",
|
|
102
102
|
"ws": "^8.18.3"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|