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 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.6.tgz`.
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-4o-mini
105
- npm run kiku-agent -- --provider gemini --model gemini-2.5-flash
106
- npm run kiku-agent -- --provider groq --model llama-3.1-8b-instant
107
- npm run kiku-agent -- --provider openrouter --model google/gemini-2.5-flash
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-4o-mini`, `google/gemma-4-e4b-Q4_K_M`) |
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-4o-mini` *(default)*, `gpt-4o`, `o3-mini` | **Ultra-Cheap** (~$0.15 / 1M input) | [Docs](https://platform.openai.com/docs) • [API Ref](https://platform.openai.com/docs/api-reference) | `OPENAI_API_KEY` |
231
- | **Google Gemini** | `gemini-2.5-flash` *(default)*, `gemini-2.5-flash-lite`, `gemini-2.5-pro`, `gemini-3.1-pro` | **Ultra-Cheap** ($0.10 / 1M in, 1M-2M context) | [Docs](https://ai.google.dev/docs) • [API Ref](https://ai.google.dev/api) | `GEMINI_API_KEY` |
232
- | **Groq** | `llama-3.3-70b-versatile` *(default)*, `llama-3.1-8b-instant`, `gpt-oss-120b` | **LPU Ultra-Fast** ($0.05 - $0.59 / 1M in) | [Docs](https://console.groq.com/docs/quickstart) • [API Ref](https://console.groq.com/docs/api-reference) | `GROQ_API_KEY` |
233
- | **DeepSeek** | `deepseek-chat` *(default, V3)*, `deepseek-reasoner` *(R1 CoT)* | **Ultra-Low Cost** ($0.14 - $0.27 / 1M in) | [Docs](https://api-docs.deepseek.com) • [API Ref](https://api-docs.deepseek.com/api/create-chat-completion) | `DEEPSEEK_API_KEY` |
234
- | **OpenRouter** | `google/gemini-2.5-flash`, `deepseek/deepseek-chat`, `deepseek/deepseek-r1`, `meta-llama/llama-3.3-70b-instruct`, `qwen/qwen-2.5-72b-instruct`, `:free` | **Flexible Aggregator** (Free & Paid) | [Docs](https://openrouter.ai/docs/quick-start) • [API Ref](https://openrouter.ai/docs/api_reference/overview) | `OPENROUTER_API_KEY` |
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-4o-mini
240
- npm run kiku-agent -- --provider gemini --model gemini-2.5-flash
241
- npm run kiku-agent -- --provider groq --model llama-3.1-8b-instant
242
- npm run kiku-agent -- --provider openrouter --model google/gemini-2.5-flash
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 llama-3.1-8b-instant # Switch provider and model in one command
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-4o-mini # Switch active model on current provider
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.6",
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
- "AI research suggestion engine and batch subagent execution",
30
- "Enhanced web search fallbacks and missing catalog tool hints",
31
- "Updated brand endorsements (Versace) and dual catalog sync",
32
- "Secure npm-backed updates and terminal markdown renderer"
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
  },
@@ -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 { KikuAgent } from './agentCore.js';
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
- console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${C.white}${opts.query}${C.reset}\n`);
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-2.5-flash' },
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-4o-mini',
135
+ '/provider use openai gpt-5.6-luna',
136
136
  '/provider setkey YOUR_API_KEY',
137
137
  '/provider models',
138
138
  ],
@@ -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 llama-3.1-8b-instant${C.reset}\n`);
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})`);
@@ -1,4 +1,31 @@
1
- export const CLOUD_PROVIDERS = {
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-4o-mini',
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://platform.openai.com/docs',
30
- apiRefUrl: 'https://platform.openai.com/docs/api-reference',
31
- pricingSummary: 'Pay-per-token (gpt-4o-mini: ~$0.15/1M in, $0.60/1M out)',
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-4o-mini', name: 'GPT-4o Mini', tier: 'ultra-cheap', cost: '$0.15 / 1M in', context: '128k', hasTools: true, description: 'Recommended default. Fast, intelligent, and highly cost-effective with tool use.' },
35
- { id: 'gpt-4o', name: 'GPT-4o', tier: 'flagship', cost: '$2.50 / 1M in', context: '128k', hasTools: true, description: 'OpenAI flagship multimodal model for complex multimodal & reasoning tasks.' },
36
- { id: 'o3-mini', name: 'o3 Mini', tier: 'reasoning', cost: '$1.10 / 1M in', context: '200k', hasTools: true, description: 'Fast, cost-efficient reasoning model with function calling capabilities.' },
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 || 'google/gemini-2.5-flash',
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/quick-start',
53
- apiRefUrl: 'https://openrouter.ai/docs/api_reference/overview',
54
- pricingSummary: 'Universal aggregator with free tiers & flexible pay-as-you-go',
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: 'google/gemini-2.5-flash', name: 'Gemini 2.5 Flash', tier: 'ultra-cheap', cost: '$0.10 / 1M in', context: '1M', hasTools: true, description: 'Super fast with huge 1M context window and native function calling.' },
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 || 'llama-3.3-70b-versatile',
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 (llama-3.1-8b: $0.05/1M, llama-3.3-70b: $0.59/1M)',
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: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B Versatile', tier: 'balanced', cost: '$0.59 / 1M in', context: '128k', hasTools: true, description: 'Recommended default. 70B flagship quality with fast LPU speed and tool use.' },
80
- { id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B Instant', tier: 'ultra-cheap', cost: '$0.05 / 1M in', context: '128k', hasTools: true, description: 'Blazing fast (~800 tokens/s) and ultra-low cost for quick inquiries.' },
81
- { id: 'gpt-oss-120b', name: 'GPT-OSS 120B', tier: 'balanced', cost: '$0.59 / 1M in', context: '128k', hasTools: true, description: 'Open-weights 120B flagship model running with ultra-fast Groq LPU inference.' },
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/v1',
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-chat',
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: 'Ultra-low cost (deepseek-chat: $0.14 cached / $0.27 uncached / 1M)',
97
- features: ['deepseek-v3', 'deepseek-r1-reasoning', 'tools', 'streaming', 'context-cache'],
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-chat', name: 'DeepSeek Chat (V3)', tier: 'ultra-cheap', cost: '$0.14-$0.27 / 1M in', context: '64k', hasTools: true, description: 'Recommended default. DeepSeek-V3 671B MoE, top-tier reasoning, tool calling, fast & ultra-cheap.' },
100
- { id: 'deepseek-reasoner', name: 'DeepSeek Reasoner (R1)', tier: 'reasoning', cost: '$0.55 / 1M in', context: '64k', hasTools: false, description: 'DeepSeek-R1 full Chain-of-Thought reasoning with native thinking output.' },
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-2.5-flash',
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: 'Ultra-cheap (gemini-2.5-flash: $0.10/1M in, $0.40/1M out)',
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-2.5-flash', name: 'Gemini 2.5 Flash', tier: 'ultra-cheap', cost: '$0.10 / 1M in', context: '1M', hasTools: true, description: 'Recommended default. Advanced reasoning and thinking capabilities with 1M context.' },
117
- { id: 'gemini-2.5-flash-lite', name: 'Gemini 2.5 Flash Lite', tier: 'lowest-cost', cost: '$0.075 / 1M in', context: '1M', hasTools: true, description: 'Cost-optimized 2.5 lightweight model for high volume queries.' },
118
- { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', tier: 'flagship', cost: '$1.25 / 1M in', context: '2M', hasTools: true, description: 'Deep reasoning and 2M context for complex multimodal analysis.' },
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] || CLOUD_PROVIDERS.lmstudio;
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 = options.model || providerDef.defaultModel || config.lmStudio.model || 'google/gemma-4-e4b';
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 isReasoningModel = /^(o1|o3|deepseek-reasoner)/i.test(this.model) || this.model.includes('reasoner');
191
- const isO1OrO3 = /^(o1|o3)/i.test(this.model);
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 (!isO1OrO3 && includeTemp && temperature !== undefined && temperature !== null) {
201
- p.temperature = temperature;
202
- }
203
-
204
- if (isO1OrO3) {
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 && !isReasoningModel) {
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
- const NON_CHAT_MODEL = /(embed|embedding|rerank|whisper|tts|clip)/i;
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.model = requested;
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 gemma = models.find(id => /gemma/i.test(id) && !NON_CHAT_MODEL.test(id));
57
- const chatFallback = models.find(id => !NON_CHAT_MODEL.test(id));
58
- const selected = exact || configuredFallback || gemma || chatFallback;
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();