@botcord/daemon 0.2.84 → 0.2.85

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.
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
4
4
  import path from "node:path";
5
5
  const MODEL_LIST_TIMEOUT_MS = 5000;
6
6
  const MODEL_LIST_MAX_BUFFER = 16 * 1024 * 1024;
7
- const RUNTIME_CATALOG_CACHE_VERSION = 1;
7
+ const RUNTIME_CATALOG_CACHE_VERSION = 2;
8
8
  const RUNTIME_CATALOG_CACHE_FRESH_MS = 10 * 60 * 1000;
9
9
  const DEFAULT_RUNTIME_CATALOG_CACHE_DIR = path.join(homedir(), ".botcord", "daemon", "runtime-catalog-cache");
10
10
  const CLAUDE_ALIAS_MODELS = [
@@ -102,7 +102,7 @@ function runtimeCatalogStrategy(entry) {
102
102
  discoverFresh: () => discoverDeepseekCatalog(entry.result.path),
103
103
  fallback: () => ({
104
104
  models: DEEPSEEK_FALLBACK_MODELS.slice(),
105
- parameters: discoverDeepseekParameters(),
105
+ parameters: discoverDeepseekParameters(entry.result.path),
106
106
  }),
107
107
  };
108
108
  case "kimi-cli":
@@ -392,7 +392,7 @@ function discoverCodexParameters(rawCatalog) {
392
392
  function discoverDeepseekCatalog(command) {
393
393
  return {
394
394
  models: discoverDeepseekModels(command),
395
- parameters: discoverDeepseekParameters(),
395
+ parameters: discoverDeepseekParameters(command),
396
396
  };
397
397
  }
398
398
  export function discoverDeepseekModels(command) {
@@ -416,8 +416,9 @@ export function parseDeepseekModelList(raw) {
416
416
  }
417
417
  return out.length ? out : undefined;
418
418
  }
419
- function discoverDeepseekParameters() {
419
+ function discoverDeepseekParameters(command) {
420
420
  const config = readConfigScalars(path.join(homedir(), ".deepseek", "config.toml"));
421
+ const reasoningEffortValues = discoverDeepseekReasoningEffortValues(command);
421
422
  return [
422
423
  compactParameter({
423
424
  id: "model",
@@ -439,8 +440,9 @@ function discoverDeepseekParameters() {
439
440
  compactParameter({
440
441
  id: "reasoning_effort",
441
442
  displayName: "Reasoning effort",
442
- type: "string",
443
- flag: "reasoning_effort",
443
+ type: reasoningEffortValues.length > 0 ? "enum" : "string",
444
+ flag: "--reasoning-effort",
445
+ values: reasoningEffortValues.length > 0 ? reasoningEffortValues : undefined,
444
446
  defaultValue: config.reasoning_effort,
445
447
  source: config.reasoning_effort ? "config" : "cli",
446
448
  }),
@@ -462,6 +464,46 @@ function discoverDeepseekParameters() {
462
464
  }),
463
465
  ];
464
466
  }
467
+ function discoverDeepseekReasoningEffortValues(command) {
468
+ const candidates = deepseekRuntimeTemplateCandidates(command);
469
+ const values = new Set();
470
+ for (const candidate of candidates) {
471
+ try {
472
+ const raw = readFileSync(candidate)
473
+ .toString("latin1")
474
+ .replace(/[^\x20-\x7E]+/g, "\n");
475
+ const templateRe = /Thinking mode \(DeepSeek V4 reasoning effort\):[\s\S]{0,256}?#\s*((?:"[^"]+"\s*(?:\|\s*)?)+)/g;
476
+ for (const match of raw.matchAll(templateRe)) {
477
+ const line = match[1] ?? "";
478
+ for (const valueMatch of line.matchAll(/"([^"]+)"/g)) {
479
+ const value = valueMatch[1]?.trim();
480
+ if (value && /^[A-Za-z0-9_.-]+$/.test(value))
481
+ values.add(value);
482
+ }
483
+ }
484
+ }
485
+ catch {
486
+ // Try the next candidate; runtime discovery should stay best-effort.
487
+ }
488
+ }
489
+ return Array.from(values);
490
+ }
491
+ function deepseekRuntimeTemplateCandidates(command) {
492
+ if (!command)
493
+ return [];
494
+ const candidates = new Set();
495
+ if (existsSync(command))
496
+ candidates.add(command);
497
+ const dir = path.dirname(command);
498
+ for (const candidate of [
499
+ path.join(dir, "deepseek-tui"),
500
+ path.join(dir, "downloads", "deepseek-tui"),
501
+ ]) {
502
+ if (existsSync(candidate))
503
+ candidates.add(candidate);
504
+ }
505
+ return Array.from(candidates);
506
+ }
465
507
  function discoverKimiCatalog() {
466
508
  const configPath = path.join(homedir(), ".kimi", "config.toml");
467
509
  if (!existsSync(configPath))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botcord/daemon",
3
- "version": "0.2.84",
3
+ "version": "0.2.85",
4
4
  "description": "BotCord local daemon — bridges Hub inbox push to local Claude Code / Codex / Gemini CLIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -199,6 +199,56 @@ describe("runtime model discovery parsers", () => {
199
199
  ]);
200
200
  });
201
201
 
202
+ it("reads DeepSeek reasoning effort choices from the installed runtime template", () => {
203
+ const tmp = mkdtempSync(path.join(tmpdir(), "daemon-deepseek-catalog-"));
204
+ const prevHome = process.env.HOME;
205
+ const prevCacheDir = process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR;
206
+ try {
207
+ const home = path.join(tmp, "home");
208
+ mkdirSync(path.join(home, ".deepseek"), { recursive: true });
209
+ process.env.HOME = home;
210
+ process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR = path.join(tmp, "catalog-cache");
211
+ writeFileSync(
212
+ path.join(home, ".deepseek", "config.toml"),
213
+ 'default_text_model = "deepseek-v4-pro"\nreasoning_effort = "turbo"\n',
214
+ );
215
+ const fakeDeepseek = path.join(tmp, "deepseek");
216
+ writeFileSync(
217
+ fakeDeepseek,
218
+ [
219
+ "binary prefix",
220
+ "# Thinking mode (DeepSeek V4 reasoning effort):",
221
+ '# "adaptive" | "disabled" | "turbo"',
222
+ 'reasoning_effort = "adaptive"',
223
+ ].join("\n"),
224
+ );
225
+
226
+ const catalog = discoverRuntimeModelCatalog({
227
+ id: "deepseek-tui",
228
+ displayName: "DeepSeek TUI",
229
+ binary: "deepseek",
230
+ supportsRun: true,
231
+ result: { available: true, path: fakeDeepseek },
232
+ });
233
+
234
+ expect(catalog.parameters).toContainEqual({
235
+ id: "reasoning_effort",
236
+ displayName: "Reasoning effort",
237
+ type: "enum",
238
+ flag: "--reasoning-effort",
239
+ values: ["adaptive", "disabled", "turbo"],
240
+ defaultValue: "turbo",
241
+ source: "config",
242
+ });
243
+ } finally {
244
+ if (prevHome === undefined) delete process.env.HOME;
245
+ else process.env.HOME = prevHome;
246
+ if (prevCacheDir === undefined) delete process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR;
247
+ else process.env.BOTCORD_RUNTIME_CATALOG_CACHE_DIR = prevCacheDir;
248
+ rmSync(tmp, { recursive: true, force: true });
249
+ }
250
+ });
251
+
202
252
  it("parses Kimi models from config.toml", () => {
203
253
  expect(
204
254
  parseKimiConfigModels(
@@ -7,7 +7,7 @@ import type { RuntimeProbeEntry } from "./adapters/runtimes.js";
7
7
 
8
8
  const MODEL_LIST_TIMEOUT_MS = 5000;
9
9
  const MODEL_LIST_MAX_BUFFER = 16 * 1024 * 1024;
10
- const RUNTIME_CATALOG_CACHE_VERSION = 1;
10
+ const RUNTIME_CATALOG_CACHE_VERSION = 2;
11
11
  const RUNTIME_CATALOG_CACHE_FRESH_MS = 10 * 60 * 1000;
12
12
  const DEFAULT_RUNTIME_CATALOG_CACHE_DIR = path.join(
13
13
  homedir(),
@@ -136,7 +136,7 @@ function runtimeCatalogStrategy(entry: RuntimeProbeEntry): RuntimeCatalogStrateg
136
136
  discoverFresh: () => discoverDeepseekCatalog(entry.result.path),
137
137
  fallback: () => ({
138
138
  models: DEEPSEEK_FALLBACK_MODELS.slice(),
139
- parameters: discoverDeepseekParameters(),
139
+ parameters: discoverDeepseekParameters(entry.result.path),
140
140
  }),
141
141
  };
142
142
  case "kimi-cli":
@@ -433,7 +433,7 @@ function discoverCodexParameters(rawCatalog: string | null): RuntimeParameterPro
433
433
  function discoverDeepseekCatalog(command: string | undefined): RuntimeModelDiscovery {
434
434
  return {
435
435
  models: discoverDeepseekModels(command),
436
- parameters: discoverDeepseekParameters(),
436
+ parameters: discoverDeepseekParameters(command),
437
437
  };
438
438
  }
439
439
 
@@ -457,8 +457,9 @@ export function parseDeepseekModelList(raw: string): RuntimeModelProbe[] | undef
457
457
  return out.length ? out : undefined;
458
458
  }
459
459
 
460
- function discoverDeepseekParameters(): RuntimeParameterProbe[] {
460
+ function discoverDeepseekParameters(command?: string): RuntimeParameterProbe[] {
461
461
  const config = readConfigScalars(path.join(homedir(), ".deepseek", "config.toml"));
462
+ const reasoningEffortValues = discoverDeepseekReasoningEffortValues(command);
462
463
  return [
463
464
  compactParameter({
464
465
  id: "model",
@@ -480,8 +481,9 @@ function discoverDeepseekParameters(): RuntimeParameterProbe[] {
480
481
  compactParameter({
481
482
  id: "reasoning_effort",
482
483
  displayName: "Reasoning effort",
483
- type: "string",
484
- flag: "reasoning_effort",
484
+ type: reasoningEffortValues.length > 0 ? "enum" : "string",
485
+ flag: "--reasoning-effort",
486
+ values: reasoningEffortValues.length > 0 ? reasoningEffortValues : undefined,
485
487
  defaultValue: config.reasoning_effort,
486
488
  source: config.reasoning_effort ? "config" : "cli",
487
489
  }),
@@ -504,6 +506,44 @@ function discoverDeepseekParameters(): RuntimeParameterProbe[] {
504
506
  ];
505
507
  }
506
508
 
509
+ function discoverDeepseekReasoningEffortValues(command: string | undefined): string[] {
510
+ const candidates = deepseekRuntimeTemplateCandidates(command);
511
+ const values = new Set<string>();
512
+ for (const candidate of candidates) {
513
+ try {
514
+ const raw = readFileSync(candidate)
515
+ .toString("latin1")
516
+ .replace(/[^\x20-\x7E]+/g, "\n");
517
+ const templateRe =
518
+ /Thinking mode \(DeepSeek V4 reasoning effort\):[\s\S]{0,256}?#\s*((?:"[^"]+"\s*(?:\|\s*)?)+)/g;
519
+ for (const match of raw.matchAll(templateRe)) {
520
+ const line = match[1] ?? "";
521
+ for (const valueMatch of line.matchAll(/"([^"]+)"/g)) {
522
+ const value = valueMatch[1]?.trim();
523
+ if (value && /^[A-Za-z0-9_.-]+$/.test(value)) values.add(value);
524
+ }
525
+ }
526
+ } catch {
527
+ // Try the next candidate; runtime discovery should stay best-effort.
528
+ }
529
+ }
530
+ return Array.from(values);
531
+ }
532
+
533
+ function deepseekRuntimeTemplateCandidates(command: string | undefined): string[] {
534
+ if (!command) return [];
535
+ const candidates = new Set<string>();
536
+ if (existsSync(command)) candidates.add(command);
537
+ const dir = path.dirname(command);
538
+ for (const candidate of [
539
+ path.join(dir, "deepseek-tui"),
540
+ path.join(dir, "downloads", "deepseek-tui"),
541
+ ]) {
542
+ if (existsSync(candidate)) candidates.add(candidate);
543
+ }
544
+ return Array.from(candidates);
545
+ }
546
+
507
547
  function discoverKimiCatalog(): RuntimeModelDiscovery {
508
548
  const configPath = path.join(homedir(), ".kimi", "config.toml");
509
549
  if (!existsSync(configPath)) return {};