@kernel.chat/kbot 3.99.7 → 3.99.9

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/dist/agent.js CHANGED
@@ -105,6 +105,8 @@ async function safeReadBody(res, maxBytes = MAX_RESPONSE_BODY) {
105
105
  }
106
106
  return decoder.decode(merged);
107
107
  }
108
+ // Track whether we've already warned about a weak-tool-calling model this process.
109
+ let weakModelWarningShown = false;
108
110
  // ── Local-first execution ──
109
111
  async function tryLocalFirst(message) {
110
112
  const lower = message.toLowerCase().trim();
@@ -790,6 +792,18 @@ export async function runAgent(message, options = {}) {
790
792
  if (isLocal && byokProvider === 'ollama') {
791
793
  warmOllamaModelCache().catch(() => { }); // non-blocking
792
794
  }
795
+ // Step 0b: One-time-per-session warning if configured model lacks tool calling
796
+ if (isLocal && !weakModelWarningShown) {
797
+ weakModelWarningShown = true;
798
+ try {
799
+ const { getWeakModelWarning } = await import('./model-capabilities.js');
800
+ const model = getProviderModel(byokProvider, 'default');
801
+ const warning = await getWeakModelWarning(byokProvider, model);
802
+ if (warning)
803
+ ui.onWarning(warning);
804
+ }
805
+ catch { /* capability check is non-critical */ }
806
+ }
793
807
  // Step 0: Parse multimodal content (images in message)
794
808
  const parsed = options.multimodal || parseMultimodalMessage(message);
795
809
  if (parsed.isMultimodal) {
package/dist/auth.js CHANGED
@@ -490,8 +490,34 @@ export function getProviderModel(provider, speed, taskHint) {
490
490
  if (provider === 'ollama' && speed === 'default' && taskHint) {
491
491
  return selectOllamaModel(taskHint);
492
492
  }
493
+ // Respect user's configured local_model (Ollama) / fast_model / thinking_model
494
+ // from ~/.kbot/config.json BEFORE falling back to hardcoded defaults.
495
+ if (provider === 'ollama') {
496
+ try {
497
+ const cfg = loadConfigRaw();
498
+ if (speed === 'fast' && cfg.fast_model)
499
+ return cfg.fast_model;
500
+ if (speed === 'default' && cfg.local_model)
501
+ return cfg.local_model;
502
+ }
503
+ catch { /* config missing — fall through */ }
504
+ }
493
505
  return speed === 'fast' ? p.fastModel : p.defaultModel;
494
506
  }
507
+ /** Lightweight config read — avoids circular dep with full loadConfig. */
508
+ function loadConfigRaw() {
509
+ try {
510
+ const { readFileSync } = require('node:fs');
511
+ const { join } = require('node:path');
512
+ const { homedir } = require('node:os');
513
+ const path = join(homedir(), '.kbot', 'config.json');
514
+ const content = readFileSync(path, 'utf-8');
515
+ return JSON.parse(content);
516
+ }
517
+ catch {
518
+ return {};
519
+ }
520
+ }
495
521
  /** Get the provider API URL */
496
522
  export function getProviderUrl(provider) {
497
523
  return PROVIDERS[provider].apiUrl;
package/dist/doctor.js CHANGED
@@ -404,6 +404,29 @@ async function checkOllamaMLX() {
404
404
  message: `Ollama ${version} — upgrade to 0.19+ for MLX backend (2x faster on Apple Silicon)`,
405
405
  };
406
406
  }
407
+ async function checkToolCapability() {
408
+ try {
409
+ const { getByokProvider, getProviderModel } = await import('./auth.js');
410
+ const { supportsToolCalls } = await import('./model-capabilities.js');
411
+ const provider = getByokProvider();
412
+ const model = getProviderModel(provider, 'default');
413
+ const ok = await supportsToolCalls(provider, model);
414
+ if (ok === true) {
415
+ return { name: 'Tool calling', status: 'pass', message: `${model} supports tool calls` };
416
+ }
417
+ if (ok === false) {
418
+ return {
419
+ name: 'Tool calling',
420
+ status: 'fail',
421
+ message: `${model} does NOT support tool calls — file reads, bash, git will hallucinate. Switch with \`kbot auth\` to qwen2.5-coder:14b, mistral:7b, or any Ollama model with the "tools" capability.`,
422
+ };
423
+ }
424
+ return { name: 'Tool calling', status: 'warn', message: `could not determine tool-call support for ${model}` };
425
+ }
426
+ catch {
427
+ return { name: 'Tool calling', status: 'warn', message: 'check failed unexpectedly' };
428
+ }
429
+ }
407
430
  // ── Hardware checks (uses machine.ts) ──
408
431
  async function checkHardware() {
409
432
  const results = [];
@@ -491,6 +514,8 @@ export async function runDoctor() {
491
514
  // Ollama MLX check (Apple Silicon only)
492
515
  if (ollamaMLXResult)
493
516
  checks.push(ollamaMLXResult);
517
+ // Tool-calling capability of configured model
518
+ checks.push(await checkToolCapability());
494
519
  // Hardware checks
495
520
  checks.push(...hardwareResults);
496
521
  // More synchronous checks
@@ -0,0 +1,14 @@
1
+ import type { ByokProvider } from './auth.js';
2
+ /**
3
+ * Check whether a specific model can invoke tools.
4
+ * For Ollama/local: queries the model's capabilities endpoint.
5
+ * For cloud providers: returns true for the known-capable list.
6
+ * Returns `null` if the answer can't be determined (non-fatal — caller decides).
7
+ */
8
+ export declare function supportsToolCalls(provider: ByokProvider, model: string): Promise<boolean | null>;
9
+ /**
10
+ * Human-readable recommendation when a model lacks tool support.
11
+ * Returns null if the model is fine OR if we can't tell.
12
+ */
13
+ export declare function getWeakModelWarning(provider: ByokProvider, model: string): Promise<string | null>;
14
+ //# sourceMappingURL=model-capabilities.d.ts.map
@@ -0,0 +1,71 @@
1
+ // Model Capability Detection
2
+ //
3
+ // kbot's agent loop depends on tool calling. When the configured model
4
+ // doesn't support tools, users see weird bugs: answers hallucinated instead
5
+ // of reading files, capabilities denied instead of exercised, tool-call
6
+ // syntax printed as markdown instead of invoked.
7
+ //
8
+ // This module answers one question reliably: does the configured model
9
+ // support tool calls?
10
+ //
11
+ // For Ollama, we query `/api/show` which returns `capabilities: ['tools', ...]`
12
+ // for tool-capable models. For cloud providers, tool support is assumed
13
+ // (Anthropic, OpenAI, Google, Groq, Mistral, DeepSeek all support it).
14
+ /** Cloud providers with reliable tool-calling support */
15
+ const CLOUD_TOOL_CAPABLE = new Set([
16
+ 'anthropic', 'openai', 'google', 'groq', 'mistral',
17
+ 'deepseek', 'cohere', 'xai', 'openrouter', 'together',
18
+ ]);
19
+ /**
20
+ * Check whether a specific model can invoke tools.
21
+ * For Ollama/local: queries the model's capabilities endpoint.
22
+ * For cloud providers: returns true for the known-capable list.
23
+ * Returns `null` if the answer can't be determined (non-fatal — caller decides).
24
+ */
25
+ export async function supportsToolCalls(provider, model) {
26
+ // Cloud providers — trust the allowlist
27
+ if (CLOUD_TOOL_CAPABLE.has(provider))
28
+ return true;
29
+ // Ollama — ask the server directly
30
+ if (provider === 'ollama') {
31
+ try {
32
+ const res = await fetch('http://localhost:11434/api/show', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ name: model }),
36
+ signal: AbortSignal.timeout(3000),
37
+ });
38
+ if (!res.ok)
39
+ return null;
40
+ const data = await res.json();
41
+ if (!Array.isArray(data.capabilities))
42
+ return null;
43
+ return data.capabilities.includes('tools');
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
49
+ // Unknown — don't speculate
50
+ return null;
51
+ }
52
+ /**
53
+ * Human-readable recommendation when a model lacks tool support.
54
+ * Returns null if the model is fine OR if we can't tell.
55
+ */
56
+ export async function getWeakModelWarning(provider, model) {
57
+ const ok = await supportsToolCalls(provider, model);
58
+ if (ok !== false)
59
+ return null; // true or null → no warning
60
+ const recs = {
61
+ ollama: ['qwen2.5-coder:14b', 'qwen2.5-coder:7b', 'mistral:7b', 'llama3.1:8b', 'kernel-coder:latest'],
62
+ };
63
+ const suggestions = recs[provider]?.filter(s => s !== model).slice(0, 3) ?? [];
64
+ const suggestionStr = suggestions.length > 0
65
+ ? ` Suggested tool-capable alternatives: ${suggestions.join(', ')}.`
66
+ : '';
67
+ return `Model "${model}" does not support tool calls. ` +
68
+ `kbot's file reads, shell commands, and git operations will silently fail or be hallucinated.${suggestionStr} ` +
69
+ `Switch with: \`kbot auth\` or set a tool-capable model in ~/.kbot/config.json.`;
70
+ }
71
+ //# sourceMappingURL=model-capabilities.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.99.7",
3
+ "version": "3.99.9",
4
4
  "description": "Open-source terminal AI agent. 787+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
5
5
  "type": "module",
6
6
  "repository": {