Skip to content

Commit eed717d

Browse files
committed
fixing MLX native support, the option wasn't showing for mac silicon chips, removed secret requirement for feedback.
1 parent f92e70a commit eed717d

10 files changed

Lines changed: 1114 additions & 20 deletions

File tree

src/config.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const DEFAULT_BASE_URL = 'https://openrouter.ai/api/v1';
1717
const DEFAULT_OLLAMA_URL = 'http://localhost:11434';
1818
const DEFAULT_LLAMACPP_URL = 'http://localhost:8080';
1919
const DEFAULT_OPENAI_URL = 'https://api.openai.com/v1';
20+
const DEFAULT_MLX_URL = 'http://localhost:8080';
2021

2122
interface LegacyConfigShape {
2223
api_key?: string;
@@ -176,7 +177,8 @@ function isModernConfig(config: AutohandConfig | LegacyConfigShape): config is A
176177
return typeof (config as AutohandConfig).openrouter === 'object' ||
177178
typeof (config as AutohandConfig).ollama === 'object' ||
178179
typeof (config as AutohandConfig).llamacpp === 'object' ||
179-
typeof (config as AutohandConfig).openai === 'object';
180+
typeof (config as AutohandConfig).openai === 'object' ||
181+
typeof (config as AutohandConfig).mlx === 'object';
180182
}
181183

182184
function isLegacyConfig(config: AutohandConfig | LegacyConfigShape): config is LegacyConfigShape {
@@ -241,7 +243,8 @@ export function getProviderConfig(config: AutohandConfig, provider?: ProviderNam
241243
openrouter: config.openrouter,
242244
ollama: config.ollama,
243245
llamacpp: config.llamacpp,
244-
openai: config.openai
246+
openai: config.openai,
247+
mlx: config.mlx
245248
};
246249

247250
const entry = configByProvider[chosen];
@@ -279,6 +282,8 @@ function defaultBaseUrlFor(provider: ProviderName, port?: number): string | unde
279282
return p ? `http://localhost:${p}` : DEFAULT_LLAMACPP_URL;
280283
case 'openai':
281284
return DEFAULT_OPENAI_URL;
285+
case 'mlx':
286+
return p ? `http://localhost:${p}` : DEFAULT_MLX_URL;
282287
default:
283288
return undefined;
284289
}

src/core/agent.ts

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { FileActionManager } from '../actions/filesystem.js';
1515
import { saveConfig, getProviderConfig } from '../config.js';
1616
import type { LLMProvider } from '../providers/LLMProvider.js';
1717
import { ProviderFactory, ProviderNotConfiguredError } from '../providers/ProviderFactory.js';
18+
import { isMLXSupported } from '../utils/platform.js';
1819
import { readInstruction } from '../ui/inputPrompt.js';
1920
// showFilePalette is imported dynamically to avoid bundling ink in standalone binary
2021
import {
@@ -238,7 +239,10 @@ export class AutohandAgent {
238239
}
239240
});
240241
this.errorLogger = new ErrorLogger(packageJson.version);
241-
this.feedbackManager = new FeedbackManager();
242+
this.feedbackManager = new FeedbackManager({
243+
apiBaseUrl: runtime.config.api?.baseUrl || 'https://api.autohand.ai',
244+
cliVersion: packageJson.version
245+
});
242246
this.skillsRegistry = new SkillsRegistry(AUTOHAND_PATHS.skills);
243247
this.telemetryManager = new TelemetryManager({
244248
enabled: runtime.config.telemetry?.enabled === true,
@@ -1018,14 +1022,17 @@ If lint or tests fail, report the issues but do NOT commit.`;
10181022
private async promptModelSelection(): Promise<void> {
10191023
try {
10201024
// Show all providers with status indicators
1021-
const allProviders: ProviderName[] = ['openrouter', 'ollama', 'llamacpp', 'openai'];
1025+
// Use ProviderFactory to get platform-aware list (includes MLX on Apple Silicon)
1026+
const allProviders = ProviderFactory.getProviderNames();
10221027
const providerChoices = allProviders.map(name => {
10231028
const isConfigured = this.isProviderConfigured(name);
10241029
const indicator = isConfigured ? chalk.green('●') : chalk.red('○');
10251030
const current = name === this.activeProvider ? chalk.cyan(' (current)') : '';
1031+
// Add Apple Silicon indicator for MLX
1032+
const siliconNote = name === 'mlx' ? chalk.gray(' (Apple Silicon)') : '';
10261033
return {
10271034
name,
1028-
message: `${indicator} ${name}${current}`,
1035+
message: `${indicator} ${name}${current}${siliconNote}`,
10291036
value: name
10301037
};
10311038
});
@@ -1088,6 +1095,9 @@ If lint or tests fail, report the issues but do NOT commit.`;
10881095
case 'openai':
10891096
await this.configureOpenAI();
10901097
break;
1098+
case 'mlx':
1099+
await this.configureMLX();
1100+
break;
10911101
}
10921102
}
10931103

@@ -1281,6 +1291,67 @@ If lint or tests fail, report the issues but do NOT commit.`;
12811291
}
12821292
}
12831293

1294+
private async configureMLX(): Promise<void> {
1295+
try {
1296+
console.log(chalk.cyan('MLX Configuration (Apple Silicon)'));
1297+
console.log(chalk.gray('MLX provides local LLM inference optimized for Apple Silicon.'));
1298+
console.log(chalk.gray('Make sure mlx-lm server is running: mlx_lm.server --model <model-name>\n'));
1299+
1300+
// Try to fetch available models from MLX server
1301+
const mlxUrl = 'http://localhost:8080';
1302+
let availableModels: string[] = [];
1303+
1304+
try {
1305+
const response = await fetch(`${mlxUrl}/v1/models`);
1306+
if (response.ok) {
1307+
const data = await response.json();
1308+
availableModels = data.data?.map((m: any) => m.id) || [];
1309+
}
1310+
} catch {
1311+
console.log(chalk.yellow('⚠ Could not connect to MLX server. Make sure it\'s running.\n'));
1312+
}
1313+
1314+
let modelAnswer;
1315+
if (availableModels.length > 0) {
1316+
modelAnswer = await enquirer.prompt<{ model: string }>([
1317+
{
1318+
type: 'select',
1319+
name: 'model',
1320+
message: 'Select a model',
1321+
choices: availableModels
1322+
}
1323+
]);
1324+
} else {
1325+
modelAnswer = await enquirer.prompt<{ model: string }>([
1326+
{
1327+
type: 'input',
1328+
name: 'model',
1329+
message: 'Enter model name (e.g., mlx-community/Llama-3.2-3B-Instruct-4bit)',
1330+
initial: 'mlx-community/Llama-3.2-3B-Instruct-4bit'
1331+
}
1332+
]);
1333+
}
1334+
1335+
this.runtime.config.mlx = {
1336+
baseUrl: mlxUrl,
1337+
model: modelAnswer.model
1338+
};
1339+
1340+
this.runtime.config.provider = 'mlx';
1341+
this.runtime.options.model = modelAnswer.model;
1342+
await saveConfig(this.runtime.config);
1343+
this.resetLlmClient('mlx', modelAnswer.model);
1344+
1345+
console.log(chalk.green('\n✓ MLX configured successfully!'));
1346+
} catch (error) {
1347+
if ((error as any).name === 'ExitPromptError' || (error as Error).message?.includes('canceled')) {
1348+
console.log(chalk.gray('\nConfiguration cancelled.'));
1349+
return;
1350+
}
1351+
throw error;
1352+
}
1353+
}
1354+
12841355
private async changeProviderModel(provider: ProviderName): Promise<void> {
12851356
try {
12861357
const currentSettings = getProviderConfig(this.runtime.config, provider);
@@ -3956,6 +4027,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
39564027
if (this.runtime.config.ollama) providers.push('ollama');
39574028
if (this.runtime.config.llamacpp) providers.push('llamacpp');
39584029
if (this.runtime.config.openai) providers.push('openai');
4030+
if (this.runtime.config.mlx) providers.push('mlx');
39594031
return providers.length ? providers : ['openrouter'];
39604032
}
39614033

@@ -3964,7 +4036,8 @@ If lint or tests fail, report the issues but do NOT commit.`;
39644036
openrouter: this.runtime.config.openrouter ?? (this.runtime.config.openrouter = { apiKey: '', model }),
39654037
ollama: this.runtime.config.ollama ?? (this.runtime.config.ollama = { model }),
39664038
llamacpp: this.runtime.config.llamacpp ?? (this.runtime.config.llamacpp = { model }),
3967-
openai: this.runtime.config.openai ?? (this.runtime.config.openai = { model })
4039+
openai: this.runtime.config.openai ?? (this.runtime.config.openai = { model }),
4040+
mlx: this.runtime.config.mlx ?? (this.runtime.config.mlx = { model })
39684041
};
39694042
cfgMap[provider].model = model;
39704043
this.activeProvider = provider;

src/feedback/FeedbackApiClient.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ export interface FeedbackApiConfig {
2525
offlineQueue: boolean;
2626
/** CLI version for analytics */
2727
cliVersion: string;
28-
/** Company secret for API authentication */
29-
companySecret: string;
3028
}
3129

3230
export interface FeedbackSubmission extends FeedbackResponse {
@@ -62,8 +60,7 @@ const DEFAULT_CONFIG: FeedbackApiConfig = {
6260
timeout: 5000,
6361
maxRetries: 3,
6462
offlineQueue: true,
65-
cliVersion: '0.1.0',
66-
companySecret: ''
63+
cliVersion: '0.1.0'
6764
};
6865

6966
// ============ FeedbackApiClient ============
@@ -163,21 +160,19 @@ export class FeedbackApiClient {
163160

164161
/**
165162
* Send feedback to API endpoint
163+
* No authentication required - endpoint is rate-limited instead
166164
*/
167165
private async sendToApi(submission: FeedbackSubmission): Promise<ApiResponse> {
168166
const controller = new AbortController();
169167
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
170168

171-
// Build auth token: {device_id}.{company_secret}
172-
const authToken = `${submission.deviceId}.${this.config.companySecret}`;
173-
174169
try {
175170
const response = await fetch(`${this.config.baseUrl}/v1/feedback`, {
176171
method: 'POST',
177172
headers: {
178173
'Content-Type': 'application/json',
179-
'Authorization': `Bearer ${authToken}`,
180-
'X-CLI-Version': this.config.cliVersion
174+
'X-CLI-Version': this.config.cliVersion,
175+
'X-Device-ID': submission.deviceId
181176
},
182177
body: JSON.stringify(submission),
183178
signal: controller.signal

0 commit comments

Comments
 (0)