nothumanallowed 16.0.60 → 16.0.62
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/constants.mjs +1 -1
- package/src/services/llm.mjs +46 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.62",
|
|
4
4
|
"description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '16.0.
|
|
8
|
+
export const VERSION = '16.0.62';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/llm.mjs
CHANGED
|
@@ -634,34 +634,23 @@ export async function callOpenRouter(apiKey, model, systemPrompt, userMessage, s
|
|
|
634
634
|
});
|
|
635
635
|
if (!res.ok) {
|
|
636
636
|
const err = await res.text();
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
const isBYOKFailure = res.status === 401 &&
|
|
642
|
-
/platform\.openai\.com|Incorrect API key/i.test(err) &&
|
|
643
|
-
model.startsWith('openai/') &&
|
|
644
|
-
!opts._retriedFromByokFailure;
|
|
645
|
-
if (isBYOKFailure) {
|
|
646
|
-
if (process.env.NHA_LOG_OPENROUTER === '1') {
|
|
647
|
-
console.error(`[openrouter] BYOK failure on ${model}, retrying with anthropic/claude-sonnet-4.5`);
|
|
648
|
-
}
|
|
649
|
-
return callOpenRouter(apiKey, 'anthropic/claude-sonnet-4.5', systemPrompt, userMessage, stream, { ...opts, _retriedFromByokFailure: true });
|
|
650
|
-
}
|
|
651
|
-
// OpenRouter often passes through misleading "platform.openai.com" key errors
|
|
652
|
-
// when the real cause is (a) invalid OpenRouter key, (b) model name without
|
|
653
|
-
// vendor prefix, (c) model requires BYOK. Provide an actionable hint.
|
|
637
|
+
// OpenRouter passes through provider-side errors verbatim. The most confusing
|
|
638
|
+
// case is "platform.openai.com" 401 — this means: OpenRouter forwarded the
|
|
639
|
+
// request to OpenAI in BYOK mode (Bring Your Own Key) and OpenAI rejected
|
|
640
|
+
// the key. We DO NOT silently swap the user's model — they chose it on purpose.
|
|
654
641
|
let hint = '';
|
|
655
642
|
if (res.status === 401) {
|
|
656
643
|
if (/platform\.openai\.com/i.test(err)) {
|
|
657
|
-
hint = `\n →
|
|
644
|
+
hint = `\n → BYOK mode rejected: model "${model}" is being routed via OpenAI in BYOK (Bring Your Own Key) mode, and the OpenAI key configured on your OpenRouter account is invalid or missing.\n Fix (keep using ${model}):\n 1. Go to openrouter.ai/settings/integrations\n 2. Either DISABLE the OpenAI integration (so OpenRouter uses its own credit), OR add a valid OpenAI key there.\n Note: NHA will NOT silently switch to a different model — your config stays as you set it.`;
|
|
658
645
|
} else {
|
|
659
|
-
hint = '\n → 401 from OpenRouter: your sk-or-v1-... key is invalid/expired. Regenerate at openrouter.ai/keys
|
|
646
|
+
hint = '\n → 401 from OpenRouter: your sk-or-v1-... key is invalid/expired. Regenerate at openrouter.ai/keys and run `nha config set key <new-key>`.';
|
|
660
647
|
}
|
|
661
648
|
} else if (res.status === 404) {
|
|
662
|
-
hint =
|
|
649
|
+
hint = `\n → 404 from OpenRouter: model "${model}" does not exist. See openrouter.ai/models for valid IDs (need vendor prefix like "openai/", "anthropic/", "google/").`;
|
|
663
650
|
} else if (res.status === 402) {
|
|
664
651
|
hint = '\n → 402 from OpenRouter: insufficient credits. Top up at openrouter.ai/credits.';
|
|
652
|
+
} else if (res.status === 429) {
|
|
653
|
+
hint = '\n → 429 from OpenRouter: rate limited. Wait a few seconds or upgrade your tier at openrouter.ai/settings.';
|
|
665
654
|
}
|
|
666
655
|
throw new Error(`OpenRouter ${res.status}: ${err}${hint}`);
|
|
667
656
|
}
|
|
@@ -1099,7 +1088,19 @@ async function _callOpenAIWithTools(apiKey, model, systemPrompt, messages, tools
|
|
|
1099
1088
|
headers,
|
|
1100
1089
|
body: JSON.stringify(body),
|
|
1101
1090
|
});
|
|
1102
|
-
if (!res.ok) {
|
|
1091
|
+
if (!res.ok) {
|
|
1092
|
+
const err = await res.text();
|
|
1093
|
+
const isOR = !!opts.baseUrl;
|
|
1094
|
+
let hint = '';
|
|
1095
|
+
if (isOR && res.status === 401 && /platform\.openai\.com/i.test(err)) {
|
|
1096
|
+
hint = `\n → BYOK mode rejected for model "${body.model}". OpenRouter forwarded the request to OpenAI in BYOK (Bring Your Own Key) mode, and the OpenAI key on your OpenRouter account is invalid/missing.\n Fix: openrouter.ai/settings/integrations → either DISABLE the OpenAI integration (so OpenRouter uses its own credit), OR add a valid OpenAI key there. NHA will keep your chosen model.`;
|
|
1097
|
+
} else if (isOR && res.status === 404) {
|
|
1098
|
+
hint = `\n → 404 from OpenRouter: model "${body.model}" not found. Must be in vendor/model format (e.g. "openai/gpt-4o", "anthropic/claude-sonnet-4.5").`;
|
|
1099
|
+
} else if (isOR && res.status === 402) {
|
|
1100
|
+
hint = '\n → 402 from OpenRouter: insufficient credits. Top up at openrouter.ai/credits.';
|
|
1101
|
+
}
|
|
1102
|
+
throw new Error(`${isOR ? 'OpenRouter' : 'OpenAI'} ${res.status}: ${err}${hint}`);
|
|
1103
|
+
}
|
|
1103
1104
|
|
|
1104
1105
|
const reader = res.body.getReader();
|
|
1105
1106
|
const dec = new TextDecoder();
|
|
@@ -1456,16 +1457,23 @@ function buildRequestBody(provider, model, systemPrompt, userMessage, stream) {
|
|
|
1456
1457
|
stream,
|
|
1457
1458
|
};
|
|
1458
1459
|
}
|
|
1459
|
-
// OpenAI-compatible format (OpenAI, DeepSeek, Grok, Mistral)
|
|
1460
|
+
// OpenAI-compatible format (OpenAI, OpenRouter, DeepSeek, Grok, Mistral)
|
|
1460
1461
|
const modelDefaults = {
|
|
1461
1462
|
nha: '/opt/models/qwen3-32b',
|
|
1462
1463
|
openai: 'gpt-4o',
|
|
1464
|
+
openrouter: 'anthropic/claude-sonnet-4.5',
|
|
1463
1465
|
deepseek: 'deepseek-chat',
|
|
1464
1466
|
grok: 'grok-3-latest',
|
|
1465
1467
|
mistral: 'mistral-large-latest',
|
|
1466
1468
|
};
|
|
1469
|
+
let resolvedModel = model || modelDefaults[provider] || 'gpt-4o';
|
|
1470
|
+
// OpenRouter requires vendor/model format. Auto-prefix bare names so users
|
|
1471
|
+
// who set "gpt-4o" or "claude-sonnet-4.5" don't hit a misleading 404/401.
|
|
1472
|
+
if (provider === 'openrouter' && resolvedModel && !resolvedModel.includes('/')) {
|
|
1473
|
+
resolvedModel = _autoPrefixOpenRouterModel(resolvedModel);
|
|
1474
|
+
}
|
|
1467
1475
|
const req = {
|
|
1468
|
-
model:
|
|
1476
|
+
model: resolvedModel,
|
|
1469
1477
|
max_tokens: 8192,
|
|
1470
1478
|
messages: [
|
|
1471
1479
|
{ role: 'system', content: systemPrompt },
|
|
@@ -1473,7 +1481,6 @@ function buildRequestBody(provider, model, systemPrompt, userMessage, stream) {
|
|
|
1473
1481
|
],
|
|
1474
1482
|
stream,
|
|
1475
1483
|
};
|
|
1476
|
-
// NHA: add thinking control
|
|
1477
1484
|
if (provider === 'nha') {
|
|
1478
1485
|
req.chat_template_kwargs = { enable_thinking: false };
|
|
1479
1486
|
}
|
|
@@ -1486,11 +1493,16 @@ function getProviderUrl(provider, model, apiKey) {
|
|
|
1486
1493
|
nha: 'https://nothumanallowed.com/api/v1/liara/chat',
|
|
1487
1494
|
anthropic: 'https://api.anthropic.com/v1/messages',
|
|
1488
1495
|
openai: 'https://api.openai.com/v1/chat/completions',
|
|
1496
|
+
openrouter: 'https://openrouter.ai/api/v1/chat/completions',
|
|
1489
1497
|
deepseek: 'https://api.deepseek.com/v1/chat/completions',
|
|
1490
1498
|
grok: 'https://api.x.ai/v1/chat/completions',
|
|
1491
1499
|
mistral: 'https://api.mistral.ai/v1/chat/completions',
|
|
1500
|
+
cohere: 'https://api.cohere.ai/v2/chat',
|
|
1492
1501
|
};
|
|
1493
|
-
|
|
1502
|
+
if (!urls[provider]) {
|
|
1503
|
+
throw new Error(`Unknown provider "${provider}". Refusing to silently fall back to OpenAI — set a known provider via "nha config set provider <name>".`);
|
|
1504
|
+
}
|
|
1505
|
+
return urls[provider];
|
|
1494
1506
|
}
|
|
1495
1507
|
|
|
1496
1508
|
/** Get provider request headers */
|
|
@@ -1503,7 +1515,15 @@ function getProviderHeaders(provider, apiKey) {
|
|
|
1503
1515
|
};
|
|
1504
1516
|
}
|
|
1505
1517
|
if (provider === 'nha') {
|
|
1506
|
-
return { 'Content-Type': 'application/json' };
|
|
1518
|
+
return { 'Content-Type': 'application/json' };
|
|
1519
|
+
}
|
|
1520
|
+
if (provider === 'openrouter') {
|
|
1521
|
+
return {
|
|
1522
|
+
'Content-Type': 'application/json',
|
|
1523
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
1524
|
+
'HTTP-Referer': 'https://nothumanallowed.com',
|
|
1525
|
+
'X-Title': 'NotHumanAllowed CLI',
|
|
1526
|
+
};
|
|
1507
1527
|
}
|
|
1508
1528
|
return {
|
|
1509
1529
|
'Content-Type': 'application/json',
|