nothumanallowed 16.0.50 → 16.0.51

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "16.0.50",
3
+ "version": "16.0.51",
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": {
@@ -182,11 +182,11 @@ export async function cmdAsk(args) {
182
182
  const callFn = getProviderCall(provider);
183
183
  if (!callFn) {
184
184
  fail(`Unknown provider: ${provider}`);
185
- info('Supported: anthropic, openai, gemini, deepseek, grok, mistral, cohere');
185
+ info('Supported: anthropic, openai, gemini, deepseek, grok, mistral, cohere, openrouter');
186
186
  process.exit(1);
187
187
  }
188
188
 
189
- const useStream = stream && (provider === 'anthropic' || provider === 'openai' || provider === 'deepseek' || provider === 'grok' || provider === 'mistral');
189
+ const useStream = stream && (provider === 'anthropic' || provider === 'openai' || provider === 'deepseek' || provider === 'grok' || provider === 'mistral' || provider === 'openrouter');
190
190
  const result = await callFn(apiKey, model, systemPrompt, userMessage, useStream);
191
191
 
192
192
  if (!useStream && result) {
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.50';
8
+ export const VERSION = '16.0.51';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -1373,9 +1373,11 @@ RULES:
1373
1373
  return `Error: unknown tool ${toolName}`;
1374
1374
  }
1375
1375
 
1376
- // Try native tool calling first (Anthropic, OpenAI)
1376
+ // Try native tool calling first. Providers that support OpenAI-style native
1377
+ // tool_use: Anthropic, OpenAI, and OpenRouter (which proxies to Claude/GPT/etc.
1378
+ // with the same OpenAI-compatible schema).
1377
1379
  const provider = config.llm?.provider || 'anthropic';
1378
- const useNativeTools = provider === 'anthropic' || provider === 'openai';
1380
+ const useNativeTools = provider === 'anthropic' || provider === 'openai' || provider === 'openrouter';
1379
1381
 
1380
1382
  if (useNativeTools) {
1381
1383
  const systemPrompt = buildSystemPrompt();
@@ -563,6 +563,44 @@ export async function callGrok(apiKey, model, systemPrompt, userMessage, stream
563
563
  return data.choices?.[0]?.message?.content || '';
564
564
  }
565
565
 
566
+ /**
567
+ * OpenRouter — aggregator that exposes 100+ models (Claude, GPT, Gemini,
568
+ * Mistral, Llama, Qwen, DeepSeek, etc.) via a single OpenAI-compatible API.
569
+ * Endpoint: https://openrouter.ai/api/v1/chat/completions
570
+ * Model names: "anthropic/claude-sonnet-4.5", "openai/gpt-4o", "google/gemini-2.5-pro"...
571
+ */
572
+ export async function callOpenRouter(apiKey, model, systemPrompt, userMessage, stream = false, opts = {}) {
573
+ const body = {
574
+ model: model || 'anthropic/claude-sonnet-4.5',
575
+ max_tokens: opts.max_tokens || 8192,
576
+ messages: [
577
+ { role: 'system', content: systemPrompt },
578
+ ..._openaiHistory(opts),
579
+ { role: 'user', content: userMessage },
580
+ ],
581
+ stream,
582
+ };
583
+ if (opts.temperature !== undefined) body.temperature = opts.temperature;
584
+ const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
585
+ method: 'POST',
586
+ headers: {
587
+ 'Content-Type': 'application/json',
588
+ 'Authorization': `Bearer ${apiKey}`,
589
+ // OpenRouter recommends these for proper attribution + ranking
590
+ 'HTTP-Referer': 'https://nothumanallowed.com',
591
+ 'X-Title': 'NotHumanAllowed CLI',
592
+ },
593
+ body: JSON.stringify(body),
594
+ });
595
+ if (!res.ok) {
596
+ const err = await res.text();
597
+ throw new Error(`OpenRouter ${res.status}: ${err}`);
598
+ }
599
+ if (stream) return streamSSE(res, 'openai');
600
+ const data = await res.json();
601
+ return data.choices?.[0]?.message?.content || '';
602
+ }
603
+
566
604
  export async function callMistral(apiKey, model, systemPrompt, userMessage, stream = false, opts = {}) {
567
605
  const body = {
568
606
  model: model || 'mistral-large-latest',
@@ -753,6 +791,7 @@ const PROVIDERS = {
753
791
  grok: callGrok,
754
792
  mistral: callMistral,
755
793
  cohere: callCohere,
794
+ openrouter: callOpenRouter,
756
795
  };
757
796
 
758
797
  export function getProviderCall(provider) {
@@ -788,6 +827,12 @@ export async function callLLMWithTools(config, systemPrompt, messages, tools, on
788
827
  return _callOpenAIWithTools(apiKey, model, systemPrompt, messages, tools, onText, onToolCall, opts);
789
828
  }
790
829
 
830
+ // OpenRouter — uses OpenAI-compatible function calling schema. We reuse the
831
+ // OpenAI tool-call implementation but point to OpenRouter's endpoint.
832
+ if (provider === 'openrouter') {
833
+ return _callOpenAIWithTools(apiKey, model || 'anthropic/claude-sonnet-4.5', systemPrompt, messages, tools, onText, onToolCall, { ...opts, baseUrl: 'https://openrouter.ai/api/v1', referer: 'https://nothumanallowed.com', xTitle: 'NotHumanAllowed CLI' });
834
+ }
835
+
791
836
  // Other providers: fallback to text-based tool calling (old system)
792
837
  // The caller should handle this by checking the return value
793
838
  return null;
@@ -972,12 +1017,17 @@ async function _callOpenAIWithTools(apiKey, model, systemPrompt, messages, tools
972
1017
  stream: true,
973
1018
  };
974
1019
 
975
- const res = await fetch('https://api.openai.com/v1/chat/completions', {
1020
+ // Support baseUrl override so OpenRouter (OpenAI-compatible) can reuse this loop.
1021
+ const endpoint = (opts.baseUrl || 'https://api.openai.com/v1') + '/chat/completions';
1022
+ const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` };
1023
+ if (opts.referer) headers['HTTP-Referer'] = opts.referer;
1024
+ if (opts.xTitle) headers['X-Title'] = opts.xTitle;
1025
+ const res = await fetch(endpoint, {
976
1026
  method: 'POST',
977
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
1027
+ headers,
978
1028
  body: JSON.stringify(body),
979
1029
  });
980
- if (!res.ok) { const err = await res.text(); throw new Error(`OpenAI ${res.status}: ${err}`); }
1030
+ if (!res.ok) { const err = await res.text(); throw new Error(`${opts.baseUrl ? 'OpenRouter' : 'OpenAI'} ${res.status}: ${err}`); }
981
1031
 
982
1032
  const reader = res.body.getReader();
983
1033
  const dec = new TextDecoder();
@@ -1053,6 +1103,7 @@ export function getApiKey(config, provider) {
1053
1103
  grok: config.llm.grokKey || config.llm.apiKey,
1054
1104
  mistral: config.llm.mistralKey || config.llm.apiKey,
1055
1105
  cohere: config.llm.cohereKey || config.llm.apiKey,
1106
+ openrouter: config.llm.openrouterKey || config.llm.openrouter_key || config.llm.apiKey,
1056
1107
  };
1057
1108
  return keyMap[provider] || config.llm.apiKey;
1058
1109
  }