kiku-agent 1.0.6 → 1.0.7
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 +22 -18
- package/package.json +5 -5
- package/server/agent/cli.js +22 -13
- package/server/agent/commandRegistry.js +2 -2
- package/server/agent/commands.js +11 -5
- package/server/agent/config.js +64 -48
- package/server/agent/llmClient.js +26 -19
- package/server/agent/modelManager.js +30 -13
- package/server/agent/youtubeAuth.js +1 -2
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Automatic installation is available for global npm installs. Source checkouts, l
|
|
|
65
65
|
|
|
66
66
|
After an update or rollback, restart `kiku-agent` and any application hosting `kiku-mcp`. Update settings and rollback history are stored in the normal Kiku user data directory as `update-state.json`; sessions, memory, `.env`, and YouTube cookies are not modified.
|
|
67
67
|
|
|
68
|
-
For a local checkout before the package is published, use `npm pack` followed by `npm install -g ./kiku-agent-1.0.
|
|
68
|
+
For a local checkout before the package is published, use `npm pack` followed by `npm install -g ./kiku-agent-1.0.7.tgz`.
|
|
69
69
|
|
|
70
70
|
### ✨ Features
|
|
71
71
|
- **LM Studio Integration**: Connects seamlessly to LM Studio's OpenAI-compatible server at `http://127.0.0.1:1234/v1` (default model `google/gemma-4-e4b`, with auto-detection of loaded models).
|
|
@@ -101,10 +101,11 @@ npm run kiku-agent
|
|
|
101
101
|
|
|
102
102
|
### Launch with Specific Cloud Provider & Model
|
|
103
103
|
```bash
|
|
104
|
-
npm run kiku-agent -- --provider openai --model gpt-
|
|
105
|
-
npm run kiku-agent -- --provider gemini --model gemini-
|
|
106
|
-
npm run kiku-agent -- --provider groq --model
|
|
107
|
-
npm run kiku-agent -- --provider
|
|
104
|
+
npm run kiku-agent -- --provider openai --model gpt-5.6-luna
|
|
105
|
+
npm run kiku-agent -- --provider gemini --model gemini-3.7-flash
|
|
106
|
+
npm run kiku-agent -- --provider groq --model openai/gpt-oss-120b
|
|
107
|
+
npm run kiku-agent -- --provider deepseek --model deepseek-v4-flash
|
|
108
|
+
npm run kiku-agent -- --provider openrouter --model openrouter/auto
|
|
108
109
|
```
|
|
109
110
|
|
|
110
111
|
### One-shot Question Query
|
|
@@ -124,7 +125,7 @@ npm run kiku-agent -- --model google/gemma-4-e4b-Q4_K_M --url http://127.0.0.1:1
|
|
|
124
125
|
| `-V`, `--version` | Print the installed package version and exit |
|
|
125
126
|
| `-P`, `--provider <name>` | Select active provider (`lmstudio`, `openai`, `gemini`, `groq`, `deepseek`, `openrouter`, `custom`) |
|
|
126
127
|
| `-k`, `--api-key <key>` | Specify API key for cloud provider |
|
|
127
|
-
| `-m`, `--model <id>` | Override model ID (e.g. `gpt-
|
|
128
|
+
| `-m`, `--model <id>` | Override model ID (e.g. `gpt-5.6-luna`, `google/gemma-4-e4b-Q4_K_M`) |
|
|
128
129
|
| `-u`, `--url <url>` | Override provider base URL |
|
|
129
130
|
| `-t`, `--tools` | List all registered tools |
|
|
130
131
|
| `-s`, `--stats` | Print catalog overview statistics |
|
|
@@ -227,30 +228,33 @@ Run `/help` inside a session for the full list. Commonly used:
|
|
|
227
228
|
| Provider | Recommended Active Models | Pricing / Cost Tier | Doc & API Reference | Required ENV Variable |
|
|
228
229
|
|---|---|---|---|---|
|
|
229
230
|
| **LM Studio** (Local) | `google/gemma-4-e4b` *(Q4_K_M, Q6_K, Q8_0)* | **100% Free / Local** (Zero token cost) | [Docs](https://lmstudio.ai/docs) • [API Ref](https://lmstudio.ai/docs/api/local-server) | `LMSTUDIO_BASE_URL` (default: `http://127.0.0.1:1234/v1`) |
|
|
230
|
-
| **OpenAI** | `gpt-
|
|
231
|
-
| **Google Gemini** | `gemini-
|
|
232
|
-
| **Groq** | `
|
|
233
|
-
| **DeepSeek** | `deepseek-
|
|
234
|
-
| **OpenRouter** | `
|
|
231
|
+
| **OpenAI** | `gpt-5.6-luna` *(default)*, `gpt-5.6-terra`, `gpt-5.6-sol` | **Cost-sensitive → Flagship** ($0.20 - $4.00 / 1M input) | [Docs](https://developers.openai.com/api/docs/models) • [API Ref](https://developers.openai.com/api/reference) | `OPENAI_API_KEY` |
|
|
232
|
+
| **Google Gemini** | `gemini-3.7-flash` *(default)*, `gemini-3.5-flash-lite`, `gemini-3.6-flash` | **Free tier / Stable Flash** ($0.30 - $0.75 / 1M input) | [Docs](https://ai.google.dev/gemini-api/docs/models) • [API Ref](https://ai.google.dev/api/generate-content) | `GEMINI_API_KEY` |
|
|
233
|
+
| **Groq** | `openai/gpt-oss-120b` *(default)*, `openai/gpt-oss-20b` | **LPU Ultra-Fast** ($0.075 - $0.15 / 1M input) | [Docs](https://console.groq.com/docs/quickstart) • [API Ref](https://console.groq.com/docs/api-reference) | `GROQ_API_KEY` |
|
|
234
|
+
| **DeepSeek** | `deepseek-v4-flash` *(default)*, `deepseek-v4-pro` | **V4 / 1M context** ($0.22 - $1.32 / 1M uncached input) | [Docs](https://api-docs.deepseek.com/quick_start/pricing/) • [API Ref](https://api-docs.deepseek.com/api/create-chat-completion) | `DEEPSEEK_API_KEY` |
|
|
235
|
+
| **OpenRouter** | `openrouter/auto` *(default, dynamic catalog)* | **Automatic routing** (billed at routed model rate) | [Docs](https://openrouter.ai/docs/guides/routing/routers/auto-router) • [API Ref](https://openrouter.ai/docs/api/reference/overview) | `OPENROUTER_API_KEY` |
|
|
235
236
|
| **Custom Gateway** | Any OpenAI-compatible server (vLLM, Ollama, TGI, Azure) | **Custom** | [OpenAI Spec](https://platform.openai.com/docs/api-reference) | `LLM_BASE_URL`, `LLM_API_KEY` |
|
|
236
237
|
|
|
237
238
|
```bash
|
|
238
239
|
# Launch with a specific cloud provider and model
|
|
239
|
-
npm run kiku-agent -- --provider openai --model gpt-
|
|
240
|
-
npm run kiku-agent -- --provider gemini --model gemini-
|
|
241
|
-
npm run kiku-agent -- --provider groq --model
|
|
242
|
-
npm run kiku-agent -- --provider
|
|
240
|
+
npm run kiku-agent -- --provider openai --model gpt-5.6-luna
|
|
241
|
+
npm run kiku-agent -- --provider gemini --model gemini-3.7-flash
|
|
242
|
+
npm run kiku-agent -- --provider groq --model openai/gpt-oss-120b
|
|
243
|
+
npm run kiku-agent -- --provider deepseek --model deepseek-v4-flash
|
|
244
|
+
npm run kiku-agent -- --provider openrouter --model openrouter/auto
|
|
243
245
|
|
|
244
246
|
# Manage providers interactively within the agent CLI
|
|
245
247
|
/provider list # View all 7 providers and key status
|
|
246
|
-
/provider use groq
|
|
248
|
+
/provider use groq openai/gpt-oss-120b # Switch provider and model in one command
|
|
247
249
|
/provider models gemini # List curated cost-effective models with pricing & context
|
|
248
250
|
/provider info openai # Show developer documentation & API details
|
|
249
251
|
/provider test groq # Test live endpoint connection and measure latency
|
|
250
252
|
/provider setkey groq gsk-... # Set or update API key dynamically at runtime
|
|
251
253
|
/model recommend # View recommended models for active provider
|
|
252
|
-
/model use gpt-
|
|
253
|
-
```
|
|
254
|
+
/model use gpt-5.6-luna # Switch active model on current provider
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
At interactive startup, provider switches, and before a one-shot query, the CLI checks the provider's live `/models` catalog. If a configured model has been retired, it selects the first available model from that provider's maintained recommendation list and reports the fallback. Legacy IDs for Groq, DeepSeek, and selected renamed models are normalized automatically.
|
|
254
258
|
|
|
255
259
|
---
|
|
256
260
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiku-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Kiku AI terminal agent and MCP server for Ju Jingyi knowledge, media, and career data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"kikuUpdate": {
|
|
27
27
|
"channel": "stable",
|
|
28
28
|
"releaseSummary": [
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
29
|
+
"Updated production model defaults and live fallback resolution across cloud providers",
|
|
30
|
+
"Hardened REST API key validation and isolated rate-limit probes",
|
|
31
|
+
"Fixed discography filtering, MCP configuration isolation, and environment loading order",
|
|
32
|
+
"Secured YouTube iframe messaging and expanded provider and API regression coverage"
|
|
33
33
|
],
|
|
34
34
|
"releaseUrl": "https://www.npmjs.com/package/kiku-agent"
|
|
35
35
|
},
|
package/server/agent/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import readline from 'node:readline';
|
|
4
|
-
import { stdin as input } from 'node:process';
|
|
5
|
-
import { existsSync } from 'node:fs';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
7
|
-
import
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { stdin as input } from 'node:process';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import './youtubeAuth.js';
|
|
8
|
+
import { KikuAgent } from './agentCore.js';
|
|
8
9
|
import { LLMClient, LMStudioClient } from './llmClient.js';
|
|
9
10
|
import { toolsSchema, toolHandlers, executeTool } from './tools.js';
|
|
10
11
|
import { config, CLOUD_PROVIDERS } from './config.js';
|
|
@@ -1540,15 +1541,23 @@ async function main() {
|
|
|
1540
1541
|
|
|
1541
1542
|
if (opts.showStats) { printWelcomeHero(); const r = await executeTool('get_system_stats', {}); console.log(JSON.stringify(r, null, 2)); process.exit(0); }
|
|
1542
1543
|
|
|
1543
|
-
if (opts.query) {
|
|
1544
|
-
printWelcomeHero();
|
|
1545
|
-
const client = new LLMClient({
|
|
1544
|
+
if (opts.query) {
|
|
1545
|
+
printWelcomeHero();
|
|
1546
|
+
const client = new LLMClient({
|
|
1546
1547
|
provider: opts.provider || config.activeProvider,
|
|
1547
1548
|
baseUrl: opts.url,
|
|
1548
1549
|
apiKey: opts.apiKey,
|
|
1549
|
-
model: opts.model
|
|
1550
|
-
});
|
|
1551
|
-
|
|
1550
|
+
model: opts.model
|
|
1551
|
+
});
|
|
1552
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1553
|
+
const modelResolution = await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
1554
|
+
if (!modelResolution.connected || !modelResolution.selectedModel) {
|
|
1555
|
+
throw new Error(modelResolution.error || `No available chat model found for ${client.providerName}`);
|
|
1556
|
+
}
|
|
1557
|
+
if (modelResolution.usedFallback) {
|
|
1558
|
+
console.log(` ${C.amber}Model ${modelResolution.requestedModel} is unavailable; using ${modelResolution.selectedModel}.${C.reset}\n`);
|
|
1559
|
+
}
|
|
1560
|
+
console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${C.white}${opts.query}${C.reset}\n`);
|
|
1552
1561
|
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
1553
1562
|
await executeQueryWithUI(agent, opts.query, agent.session.thinkingMode === 'show', null, 1);
|
|
1554
1563
|
process.exit(0);
|
|
@@ -1665,7 +1674,7 @@ async function main() {
|
|
|
1665
1674
|
}
|
|
1666
1675
|
}
|
|
1667
1676
|
|
|
1668
|
-
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1677
|
+
const modelManager = new ModelManager(client, { requestedModel: opts.model || client.model });
|
|
1669
1678
|
const modelResolution = catalogOnlyMode
|
|
1670
1679
|
? { connected: false, state: 'catalog_only' }
|
|
1671
1680
|
: await modelManager.resolve({ requestedModel: opts.model || client.model });
|
|
@@ -1685,7 +1694,7 @@ async function main() {
|
|
|
1685
1694
|
title: 'Connected & Ready',
|
|
1686
1695
|
status: 'connected',
|
|
1687
1696
|
lines: [
|
|
1688
|
-
`• Provider: ${client.providerName} (Local) • Model: ${modelResolution.selectedModel}`,
|
|
1697
|
+
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${modelResolution.selectedModel}`,
|
|
1689
1698
|
`• Status: Chat & Tool DAG Active • Tools: ${toolsSchema.length} Loaded`,
|
|
1690
1699
|
],
|
|
1691
1700
|
});
|
|
@@ -123,7 +123,7 @@ export const COMMAND_DETAILS = Object.freeze({
|
|
|
123
123
|
subcommands: [
|
|
124
124
|
{ cmd: 'list', desc: 'List all supported providers, availability, and active status', example: '/provider list' },
|
|
125
125
|
{ cmd: 'status', desc: 'Show detailed connection metrics, latency, and credentials for active provider', example: '/provider status' },
|
|
126
|
-
{ cmd: 'use <name> [model]', desc: 'Switch active AI provider and optionally specify initial model', example: '/provider use gemini gemini-
|
|
126
|
+
{ cmd: 'use <name> [model]', desc: 'Switch active AI provider and optionally specify initial model', example: '/provider use gemini gemini-3.7-flash' },
|
|
127
127
|
{ cmd: 'models [name]', desc: 'Fetch live model catalog from the specified or active provider API', example: '/provider models groq' },
|
|
128
128
|
{ cmd: 'setkey <key>', desc: 'Configure and persist API key for the active provider', example: '/provider setkey sk-...' },
|
|
129
129
|
{ cmd: 'test', desc: 'Perform a lightweight ping/test call to verify provider responsiveness', example: '/provider test' },
|
|
@@ -132,7 +132,7 @@ export const COMMAND_DETAILS = Object.freeze({
|
|
|
132
132
|
examples: [
|
|
133
133
|
'/provider list',
|
|
134
134
|
'/provider use gemini',
|
|
135
|
-
'/provider use openai gpt-
|
|
135
|
+
'/provider use openai gpt-5.6-luna',
|
|
136
136
|
'/provider setkey YOUR_API_KEY',
|
|
137
137
|
'/provider models',
|
|
138
138
|
],
|
package/server/agent/commands.js
CHANGED
|
@@ -1198,7 +1198,7 @@ export const handlers = {
|
|
|
1198
1198
|
|
|
1199
1199
|
if (!targetProvider) {
|
|
1200
1200
|
console.log(`\n ${C.amber}Usage: /provider use <provider_name> [model_id]${C.reset}`);
|
|
1201
|
-
console.log(` ${C.dim}Example: /provider use groq
|
|
1201
|
+
console.log(` ${C.dim}Example: /provider use groq openai/gpt-oss-120b${C.reset}\n`);
|
|
1202
1202
|
return 'HANDLED';
|
|
1203
1203
|
}
|
|
1204
1204
|
|
|
@@ -1206,10 +1206,16 @@ export const handlers = {
|
|
|
1206
1206
|
const options = targetModel ? { model: targetModel } : {};
|
|
1207
1207
|
const info = ctx.client.switchProvider(targetProvider, options);
|
|
1208
1208
|
if (ctx.agent?.client) ctx.agent.client.switchProvider(targetProvider, options);
|
|
1209
|
-
if (ctx.modelManager) {
|
|
1210
|
-
ctx.modelManager.client = ctx.client;
|
|
1211
|
-
ctx.modelManager.requestedModel = info.model;
|
|
1212
|
-
|
|
1209
|
+
if (ctx.modelManager) {
|
|
1210
|
+
ctx.modelManager.client = ctx.client;
|
|
1211
|
+
ctx.modelManager.requestedModel = info.model;
|
|
1212
|
+
const resolution = await ctx.modelManager.resolve({ requestedModel: info.model });
|
|
1213
|
+
if (resolution.connected && resolution.usedFallback) {
|
|
1214
|
+
console.log(` ${C.amber}• Requested model unavailable; using ${resolution.selectedModel}${C.reset}`);
|
|
1215
|
+
} else if (!resolution.connected) {
|
|
1216
|
+
console.log(` ${C.amber}• Provider is not ready: ${resolution.error}${C.reset}`);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1213
1219
|
ctx.baseUrl = info.baseUrl;
|
|
1214
1220
|
|
|
1215
1221
|
console.log(`\n ${C.emerald}[OK] Switched provider to ${C.bold}${info.providerName}${C.reset} (${info.provider})`);
|
package/server/agent/config.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
const PROVIDER_MODEL_ALIASES = {
|
|
2
|
+
openai: {
|
|
3
|
+
'o1-mini': 'gpt-5.6-luna',
|
|
4
|
+
'o3-mini': 'gpt-5.6-luna',
|
|
5
|
+
},
|
|
6
|
+
openrouter: {
|
|
7
|
+
auto: 'openrouter/auto',
|
|
8
|
+
},
|
|
9
|
+
groq: {
|
|
10
|
+
'gpt-oss-120b': 'openai/gpt-oss-120b',
|
|
11
|
+
'gpt-oss-20b': 'openai/gpt-oss-20b',
|
|
12
|
+
},
|
|
13
|
+
deepseek: {
|
|
14
|
+
'deepseek-chat': 'deepseek-v4-flash',
|
|
15
|
+
'deepseek-reasoner': 'deepseek-v4-flash',
|
|
16
|
+
},
|
|
17
|
+
gemini: {
|
|
18
|
+
'gemini-3.1-pro': 'gemini-3.1-pro-preview',
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function normalizeProviderModel(providerId, modelId) {
|
|
23
|
+
const normalizedProvider = String(providerId || '').toLowerCase().trim();
|
|
24
|
+
const normalizedModel = String(modelId || '').trim();
|
|
25
|
+
return PROVIDER_MODEL_ALIASES[normalizedProvider]?.[normalizedModel] || normalizedModel;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const CLOUD_PROVIDERS = {
|
|
2
29
|
lmstudio: {
|
|
3
30
|
id: 'lmstudio',
|
|
4
31
|
name: 'LM Studio (Local)',
|
|
@@ -23,18 +50,17 @@ export const CLOUD_PROVIDERS = {
|
|
|
23
50
|
name: 'OpenAI Cloud',
|
|
24
51
|
baseUrl: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
|
|
25
52
|
apiKey: process.env.OPENAI_API_KEY || '',
|
|
26
|
-
defaultModel: process.env.OPENAI_MODEL || 'gpt-
|
|
53
|
+
defaultModel: normalizeProviderModel('openai', process.env.OPENAI_MODEL || 'gpt-5.6-luna'),
|
|
27
54
|
isLocal: false,
|
|
28
55
|
envKey: 'OPENAI_API_KEY',
|
|
29
|
-
docUrl: 'https://
|
|
30
|
-
apiRefUrl: 'https://
|
|
31
|
-
pricingSummary: '
|
|
56
|
+
docUrl: 'https://developers.openai.com/api/docs/models',
|
|
57
|
+
apiRefUrl: 'https://developers.openai.com/api/reference',
|
|
58
|
+
pricingSummary: 'GPT-5.6 Luna: $0.20/1M in, $1.20/1M out; Terra/Sol available for higher capability',
|
|
32
59
|
features: ['tools', 'streaming', 'vision', 'reasoning', 'system-prompt'],
|
|
33
60
|
recommendedModels: [
|
|
34
|
-
{ id: 'gpt-
|
|
35
|
-
{ id: 'gpt-
|
|
36
|
-
{ id: '
|
|
37
|
-
{ id: 'o1-mini', name: 'o1 Mini', tier: 'reasoning', cost: '$1.10 / 1M in', context: '128k', hasTools: true, description: 'Reasoning model specialized for complex STEM logic and coding.' },
|
|
61
|
+
{ id: 'gpt-5.6-luna', name: 'GPT-5.6 Luna', tier: 'cost-sensitive', cost: '$0.20 / 1M in', context: '1.05M', hasTools: true, description: 'Recommended default for efficient, high-volume multilingual agent workloads.' },
|
|
62
|
+
{ id: 'gpt-5.6-terra', name: 'GPT-5.6 Terra', tier: 'balanced', cost: '$2.00 / 1M in', context: '1.05M', hasTools: true, description: 'Balances intelligence and cost for production reasoning and tool use.' },
|
|
63
|
+
{ id: 'gpt-5.6-sol', name: 'GPT-5.6 Sol', tier: 'flagship', cost: '$4.00 / 1M in', context: '1.05M', hasTools: true, description: 'Flagship model for complex professional reasoning and coding.' },
|
|
38
64
|
],
|
|
39
65
|
},
|
|
40
66
|
openrouter: {
|
|
@@ -42,62 +68,53 @@ export const CLOUD_PROVIDERS = {
|
|
|
42
68
|
name: 'OpenRouter Cloud',
|
|
43
69
|
baseUrl: process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1',
|
|
44
70
|
apiKey: process.env.OPENROUTER_API_KEY || '',
|
|
45
|
-
defaultModel: process.env.OPENROUTER_MODEL || '
|
|
46
|
-
extraHeaders: {
|
|
47
|
-
'HTTP-Referer': 'https://jujingyi-kikuuu.vercel.app/',
|
|
48
|
-
'X-Title': 'Kiku Agent',
|
|
49
|
-
},
|
|
71
|
+
defaultModel: normalizeProviderModel('openrouter', process.env.OPENROUTER_MODEL || 'openrouter/auto'),
|
|
72
|
+
extraHeaders: {
|
|
73
|
+
'HTTP-Referer': 'https://jujingyi-kikuuu.vercel.app/',
|
|
74
|
+
'X-OpenRouter-Title': 'Kiku Agent',
|
|
75
|
+
},
|
|
50
76
|
isLocal: false,
|
|
51
77
|
envKey: 'OPENROUTER_API_KEY',
|
|
52
|
-
docUrl: 'https://openrouter.ai/docs/
|
|
53
|
-
apiRefUrl: 'https://openrouter.ai/docs/
|
|
54
|
-
pricingSummary: '
|
|
78
|
+
docUrl: 'https://openrouter.ai/docs/guides/routing/routers/auto-router',
|
|
79
|
+
apiRefUrl: 'https://openrouter.ai/docs/api/reference/overview',
|
|
80
|
+
pricingSummary: 'Auto Router selects a current model dynamically; usage is billed at the routed model rate',
|
|
55
81
|
features: ['universal-routing', 'tools', 'streaming', 'free-tier', 'fallback'],
|
|
56
82
|
recommendedModels: [
|
|
57
|
-
{ id: '
|
|
58
|
-
{ id: 'deepseek/deepseek-chat', name: 'DeepSeek Chat (V3)', tier: 'ultra-cheap', cost: '$0.14 / 1M in', context: '64k', hasTools: true, description: 'DeepSeek-V3 671B MoE via OpenRouter at ultra-low price.' },
|
|
59
|
-
{ id: 'deepseek/deepseek-r1', name: 'DeepSeek Reasoner (R1)', tier: 'reasoning', cost: '$0.55 / 1M in', context: '64k', hasTools: false, description: 'Full DeepSeek-R1 reasoning with Chain-of-Thought output.' },
|
|
60
|
-
{ id: 'meta-llama/llama-3.3-70b-instruct', name: 'Llama 3.3 70B Instruct', tier: 'balanced', cost: '$0.40 / 1M in', context: '128k', hasTools: true, description: 'State-of-the-art open-weights flagship model.' },
|
|
61
|
-
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', tier: 'ultra-cheap', cost: '$0.15 / 1M in', context: '128k', hasTools: true, description: 'OpenAI mini model routed via OpenRouter.' },
|
|
62
|
-
{ id: 'qwen/qwen-2.5-72b-instruct', name: 'Qwen 2.5 72B Instruct', tier: 'balanced', cost: '$0.35 / 1M in', context: '128k', hasTools: true, description: 'Top-tier open-weights multilingual and coding model.' },
|
|
63
|
-
{ id: 'meta-llama/llama-3.1-8b-instruct:free', name: 'Llama 3.1 8B (Free)', tier: 'free', cost: '$0.00', context: '128k', hasTools: true, description: 'Free tier Llama 3.1 8B on OpenRouter.' },
|
|
83
|
+
{ id: 'openrouter/auto', name: 'OpenRouter Auto Router', tier: 'dynamic', cost: 'Routed model rate', context: 'Model-dependent', hasTools: true, description: 'Recommended default. Chooses from the current catalog so a retired third-party model does not break startup.' },
|
|
64
84
|
],
|
|
65
85
|
},
|
|
66
|
-
groq: {
|
|
86
|
+
groq: {
|
|
67
87
|
id: 'groq',
|
|
68
88
|
name: 'Groq Cloud',
|
|
69
89
|
baseUrl: process.env.GROQ_BASE_URL || 'https://api.groq.com/openai/v1',
|
|
70
90
|
apiKey: process.env.GROQ_API_KEY || '',
|
|
71
|
-
defaultModel: process.env.GROQ_MODEL || '
|
|
91
|
+
defaultModel: normalizeProviderModel('groq', process.env.GROQ_MODEL || 'openai/gpt-oss-120b'),
|
|
72
92
|
isLocal: false,
|
|
73
93
|
envKey: 'GROQ_API_KEY',
|
|
74
94
|
docUrl: 'https://console.groq.com/docs/quickstart',
|
|
75
95
|
apiRefUrl: 'https://console.groq.com/docs/api-reference',
|
|
76
|
-
pricingSummary: 'Ultra-fast LPU inference (
|
|
96
|
+
pricingSummary: 'Ultra-fast LPU inference (GPT-OSS 20B: $0.075/1M in, GPT-OSS 120B: $0.15/1M in)',
|
|
77
97
|
features: ['lpu-speed', 'tools', 'streaming', 'openai-compatible'],
|
|
78
|
-
recommendedModels: [
|
|
79
|
-
{ id: '
|
|
80
|
-
{ id: '
|
|
81
|
-
|
|
82
|
-
{ id: 'mixtral-8x7b-32768', name: 'Mixtral 8x7B', tier: 'cheap', cost: '$0.24 / 1M in', context: '32k', hasTools: true, description: 'Mistral MoE architecture with 32k context.' },
|
|
83
|
-
{ id: 'gemma2-9b-it', name: 'Gemma 2 9B IT', tier: 'cheap', cost: '$0.20 / 1M in', context: '8k', hasTools: true, description: 'Google Gemma 2 on Groq LPU hardware.' },
|
|
84
|
-
],
|
|
98
|
+
recommendedModels: [
|
|
99
|
+
{ id: 'openai/gpt-oss-120b', name: 'GPT-OSS 120B', tier: 'flagship', cost: '$0.15 / 1M in', context: '131k', hasTools: true, description: 'Recommended default. Production-grade 120B reasoning model with tool use on Groq.' },
|
|
100
|
+
{ id: 'openai/gpt-oss-20b', name: 'GPT-OSS 20B', tier: 'ultra-cheap', cost: '$0.075 / 1M in', context: '131k', hasTools: true, description: 'Fast production model for lower-cost, high-volume requests.' },
|
|
101
|
+
],
|
|
85
102
|
},
|
|
86
103
|
deepseek: {
|
|
87
104
|
id: 'deepseek',
|
|
88
105
|
name: 'DeepSeek Cloud',
|
|
89
|
-
baseUrl: process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com
|
|
106
|
+
baseUrl: process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com',
|
|
90
107
|
apiKey: process.env.DEEPSEEK_API_KEY || '',
|
|
91
|
-
defaultModel: process.env.DEEPSEEK_MODEL || 'deepseek-
|
|
108
|
+
defaultModel: normalizeProviderModel('deepseek', process.env.DEEPSEEK_MODEL || 'deepseek-v4-flash'),
|
|
92
109
|
isLocal: false,
|
|
93
110
|
envKey: 'DEEPSEEK_API_KEY',
|
|
94
111
|
docUrl: 'https://api-docs.deepseek.com',
|
|
95
112
|
apiRefUrl: 'https://api-docs.deepseek.com/api/create-chat-completion',
|
|
96
|
-
pricingSummary: '
|
|
97
|
-
features: ['deepseek-
|
|
113
|
+
pricingSummary: 'V4 Flash: $0.22-$0.44/1M uncached input; V4 Pro: $0.66-$1.32/1M (off-peak/peak)',
|
|
114
|
+
features: ['deepseek-v4', 'thinking', 'tools', 'streaming', 'context-cache', '1m-context'],
|
|
98
115
|
recommendedModels: [
|
|
99
|
-
{ id: 'deepseek-
|
|
100
|
-
{ id: 'deepseek-
|
|
116
|
+
{ id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash', tier: 'balanced', cost: '$0.22-$0.44 / 1M uncached in', context: '1M', hasTools: true, description: 'Recommended default. Current V4 model with tool calls and thinking/non-thinking modes.' },
|
|
117
|
+
{ id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro', tier: 'flagship', cost: '$0.66-$1.32 / 1M uncached in', context: '1M', hasTools: true, description: 'Higher-capability V4 model with tool calls and thinking mode.' },
|
|
101
118
|
],
|
|
102
119
|
},
|
|
103
120
|
gemini: {
|
|
@@ -105,18 +122,17 @@ export const CLOUD_PROVIDERS = {
|
|
|
105
122
|
name: 'Google Gemini Cloud',
|
|
106
123
|
baseUrl: process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta/openai/',
|
|
107
124
|
apiKey: process.env.GEMINI_API_KEY || '',
|
|
108
|
-
defaultModel: process.env.GEMINI_MODEL || 'gemini-
|
|
125
|
+
defaultModel: normalizeProviderModel('gemini', process.env.GEMINI_MODEL || 'gemini-3.7-flash'),
|
|
109
126
|
isLocal: false,
|
|
110
127
|
envKey: 'GEMINI_API_KEY',
|
|
111
|
-
docUrl: 'https://ai.google.dev/docs',
|
|
112
|
-
apiRefUrl: 'https://ai.google.dev/api',
|
|
113
|
-
pricingSummary: '
|
|
128
|
+
docUrl: 'https://ai.google.dev/gemini-api/docs',
|
|
129
|
+
apiRefUrl: 'https://ai.google.dev/api/generate-content',
|
|
130
|
+
pricingSummary: 'Gemini 3.7 Flash: free tier available; paid standard $0.75/1M input through 2026',
|
|
114
131
|
features: ['1m-context', 'multimodal', 'tools', 'streaming', 'thinking'],
|
|
115
132
|
recommendedModels: [
|
|
116
|
-
{ id: 'gemini-
|
|
117
|
-
{ id: 'gemini-
|
|
118
|
-
{ id: 'gemini-
|
|
119
|
-
{ id: 'gemini-3.1-pro', name: 'Gemini 3.1 Pro', tier: 'flagship', cost: '$1.50 / 1M in', context: '2M', hasTools: true, description: 'Next-gen Gemini 3.1 flagship with state-of-the-art coding and multimodal reasoning.' },
|
|
133
|
+
{ id: 'gemini-3.7-flash', name: 'Gemini 3.7 Flash', tier: 'latest-stable', cost: '$0.75 / 1M in (2026)', context: '1M', hasTools: true, description: 'Recommended default. Latest stable Flash model for agentic workflows and multimodal reasoning.' },
|
|
134
|
+
{ id: 'gemini-3.5-flash-lite', name: 'Gemini 3.5 Flash-Lite', tier: 'lowest-cost', cost: '$0.30 / 1M in', context: '1M', hasTools: true, description: 'Stable, cost-efficient model for high-volume translation and simple processing.' },
|
|
135
|
+
{ id: 'gemini-3.6-flash', name: 'Gemini 3.6 Flash', tier: 'stable', cost: '$0.75 / 1M in (2026)', context: '1M', hasTools: true, description: 'Stable previous-generation Flash fallback with function calling and thinking.' },
|
|
120
136
|
],
|
|
121
137
|
},
|
|
122
138
|
custom: {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { config, CLOUD_PROVIDERS } from './config.js';
|
|
1
|
+
import { config, CLOUD_PROVIDERS, normalizeProviderModel } from './config.js';
|
|
2
2
|
|
|
3
3
|
export class LLMClient {
|
|
4
4
|
constructor(options = {}) {
|
|
5
|
-
const providerId = (options.provider || config.activeProvider || 'lmstudio').toLowerCase();
|
|
6
|
-
const providerDef = CLOUD_PROVIDERS[providerId]
|
|
5
|
+
const providerId = (options.provider || config.activeProvider || 'lmstudio').toLowerCase();
|
|
6
|
+
const providerDef = CLOUD_PROVIDERS[providerId];
|
|
7
|
+
if (!providerDef) {
|
|
8
|
+
throw new Error(`Unknown provider "${providerId}". Supported: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`);
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
this.provider = providerId;
|
|
9
12
|
this.providerName = providerDef.name || providerId;
|
|
@@ -11,7 +14,10 @@ export class LLMClient {
|
|
|
11
14
|
this.apiKey = options.apiKey !== undefined
|
|
12
15
|
? options.apiKey
|
|
13
16
|
: (providerDef.apiKey !== undefined ? providerDef.apiKey : (providerDef.isLocal ? (config.lmStudio.apiKey || 'lm-studio') : ''));
|
|
14
|
-
this.model =
|
|
17
|
+
this.model = normalizeProviderModel(
|
|
18
|
+
providerId,
|
|
19
|
+
options.model || providerDef.defaultModel || config.lmStudio.model || 'google/gemma-4-e4b'
|
|
20
|
+
);
|
|
15
21
|
this.timeout = options.timeout || config.lmStudio.requestTimeoutMs || 180000;
|
|
16
22
|
this.extraHeaders = { ...(providerDef.extraHeaders || {}), ...(options.extraHeaders || {}) };
|
|
17
23
|
}
|
|
@@ -27,7 +33,7 @@ export class LLMClient {
|
|
|
27
33
|
this.providerName = providerDef.name || pId;
|
|
28
34
|
this.baseUrl = (options.baseUrl || providerDef.baseUrl).replace(/\/+$/, '');
|
|
29
35
|
this.apiKey = options.apiKey !== undefined ? options.apiKey : (providerDef.apiKey || '');
|
|
30
|
-
this.model = options.model || providerDef.defaultModel;
|
|
36
|
+
this.model = normalizeProviderModel(pId, options.model || providerDef.defaultModel);
|
|
31
37
|
this.extraHeaders = { ...(providerDef.extraHeaders || {}), ...(options.extraHeaders || {}) };
|
|
32
38
|
return this.getProviderInfo();
|
|
33
39
|
}
|
|
@@ -40,7 +46,7 @@ export class LLMClient {
|
|
|
40
46
|
setModel(model) {
|
|
41
47
|
const trimmed = String(model || '').trim();
|
|
42
48
|
if (!trimmed) throw new Error('A model ID is required.');
|
|
43
|
-
this.model = trimmed;
|
|
49
|
+
this.model = normalizeProviderModel(this.provider, trimmed);
|
|
44
50
|
return this.model;
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -187,8 +193,9 @@ export class LLMClient {
|
|
|
187
193
|
onToken = null,
|
|
188
194
|
onThought = null
|
|
189
195
|
}) {
|
|
190
|
-
const
|
|
191
|
-
const
|
|
196
|
+
const isLegacyReasoningModel = /^(o1|o3|deepseek-reasoner)/i.test(this.model) || this.model.includes('reasoner');
|
|
197
|
+
const usesCompletionTokenLimit = /^(o1|o3|gpt-5(?:\.|-|$))/i.test(this.model);
|
|
198
|
+
const rejectsTemperature = /^(o1|o3|gpt-5(?:\.|-|$))/i.test(this.model);
|
|
192
199
|
|
|
193
200
|
const buildPayload = (includeTools = true, includeTemp = true) => {
|
|
194
201
|
const p = {
|
|
@@ -197,17 +204,17 @@ export class LLMClient {
|
|
|
197
204
|
stream
|
|
198
205
|
};
|
|
199
206
|
|
|
200
|
-
if (!
|
|
201
|
-
p.temperature = temperature;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
p.max_completion_tokens = maxTokens;
|
|
206
|
-
} else {
|
|
207
|
-
p.max_tokens = maxTokens;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (includeTools && tools && Array.isArray(tools) && tools.length > 0 && !
|
|
207
|
+
if (!rejectsTemperature && includeTemp && temperature !== undefined && temperature !== null) {
|
|
208
|
+
p.temperature = temperature;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (usesCompletionTokenLimit) {
|
|
212
|
+
p.max_completion_tokens = maxTokens;
|
|
213
|
+
} else {
|
|
214
|
+
p.max_tokens = maxTokens;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (includeTools && tools && Array.isArray(tools) && tools.length > 0 && !isLegacyReasoningModel) {
|
|
211
218
|
p.tools = tools;
|
|
212
219
|
p.tool_choice = "auto";
|
|
213
220
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { normalizeProviderModel } from './config.js';
|
|
2
|
+
|
|
3
|
+
const NON_CHAT_MODEL = /(embed|embedding|rerank|whisper|tts|clip)/i;
|
|
2
4
|
|
|
3
5
|
export class ModelManager {
|
|
4
|
-
constructor(client, options = {}) {
|
|
6
|
+
constructor(client, options = {}) {
|
|
5
7
|
if (!client) throw new Error('ModelManager requires an LLM client.');
|
|
6
8
|
this.client = client;
|
|
7
|
-
this.requestedModel = options.requestedModel || client.model;
|
|
8
|
-
this.fallbackModel = options.fallbackModel || null;
|
|
9
|
+
this.requestedModel = options.requestedModel || client.model;
|
|
10
|
+
this.fallbackModel = options.fallbackModel || null;
|
|
11
|
+
this.fallbackModels = Array.isArray(options.fallbackModels) ? options.fallbackModels : [];
|
|
9
12
|
this.lastResolution = null;
|
|
10
13
|
}
|
|
11
14
|
|
|
@@ -26,8 +29,8 @@ export class ModelManager {
|
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
select(model, availableModels = []) {
|
|
30
|
-
const requested = String(model || '').trim();
|
|
32
|
+
select(model, availableModels = []) {
|
|
33
|
+
const requested = normalizeProviderModel(this.client.provider, String(model || '').trim());
|
|
31
34
|
if (!requested) throw new Error('A model ID is required.');
|
|
32
35
|
if (availableModels.length > 0 && !availableModels.includes(requested)) {
|
|
33
36
|
throw new Error(`Model is not available: ${requested}`);
|
|
@@ -35,7 +38,11 @@ export class ModelManager {
|
|
|
35
38
|
if (NON_CHAT_MODEL.test(requested)) {
|
|
36
39
|
throw new Error(`Model does not appear to support chat: ${requested}`);
|
|
37
40
|
}
|
|
38
|
-
this.client.
|
|
41
|
+
if (typeof this.client.setModel === 'function') {
|
|
42
|
+
this.client.setModel(requested);
|
|
43
|
+
} else {
|
|
44
|
+
this.client.model = requested;
|
|
45
|
+
}
|
|
39
46
|
this.requestedModel = requested;
|
|
40
47
|
return requested;
|
|
41
48
|
}
|
|
@@ -50,12 +57,22 @@ export class ModelManager {
|
|
|
50
57
|
return this._record({ state: 'server_online_no_models', connected: true, models, error: `${this.client.providerName || 'LLM server'} is online but no model is loaded.` });
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
const requested = options.requestedModel || this.requestedModel;
|
|
54
|
-
const exact = models.find(id => id === requested);
|
|
55
|
-
const configuredFallback = models.find(id => id === (options.fallbackModel || this.fallbackModel));
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
60
|
+
const requested = options.requestedModel || this.requestedModel;
|
|
61
|
+
const exact = models.find(id => id === requested);
|
|
62
|
+
const configuredFallback = models.find(id => id === (options.fallbackModel || this.fallbackModel));
|
|
63
|
+
const providerRecommendations = this.client.getProviderInfo?.().recommendedModels || [];
|
|
64
|
+
const fallbackCandidates = [
|
|
65
|
+
...(Array.isArray(options.fallbackModels) ? options.fallbackModels : this.fallbackModels),
|
|
66
|
+
...providerRecommendations.map(model => model.id),
|
|
67
|
+
];
|
|
68
|
+
const recommendedFallback = fallbackCandidates
|
|
69
|
+
.map(candidate => models.find(id => id === candidate))
|
|
70
|
+
.find(Boolean);
|
|
71
|
+
const gemma = this.client.provider === 'lmstudio'
|
|
72
|
+
? models.find(id => /gemma/i.test(id) && !NON_CHAT_MODEL.test(id))
|
|
73
|
+
: null;
|
|
74
|
+
const chatFallback = models.find(id => !NON_CHAT_MODEL.test(id));
|
|
75
|
+
const selected = exact || configuredFallback || recommendedFallback || gemma || chatFallback;
|
|
59
76
|
if (!selected) {
|
|
60
77
|
return this._record({ state: 'no_chat_model', connected: true, models, error: 'No chat-capable model was found.' });
|
|
61
78
|
}
|
|
@@ -8,6 +8,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
8
8
|
const ENV_PATH = path.resolve(__dirname, '..', '..', '.env');
|
|
9
9
|
const AUTH_CONFIG_PATH = path.join(KIKU_DATA_DIR, 'yt_auth.json');
|
|
10
10
|
const SECURE_COOKIE_PATH = path.join(KIKU_DATA_DIR, 'cookies.txt');
|
|
11
|
+
loadEnv();
|
|
11
12
|
const YTDLP_CMD = process.env.KIKU_YTDLP || 'yt-dlp';
|
|
12
13
|
|
|
13
14
|
|
|
@@ -374,5 +375,3 @@ export function getCookieStatus() {
|
|
|
374
375
|
authConfigPath: AUTH_CONFIG_PATH
|
|
375
376
|
};
|
|
376
377
|
}
|
|
377
|
-
|
|
378
|
-
loadEnv();
|