@yeaft/webchat-agent 0.1.413 → 0.1.414

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.
@@ -24,6 +24,7 @@ import {
24
24
  import { sendToServer, flushMessageBuffer } from './buffer.js';
25
25
  import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
26
26
  import { loadMcpServers, updateMcpConfig } from '../mcp.js';
27
+ import { getLlmConfig, updateLlmConfig } from '../unify/config-api.js';
27
28
 
28
29
  export async function handleMessage(msg) {
29
30
  switch (msg.type) {
@@ -304,5 +305,18 @@ export async function handleMessage(msg) {
304
305
  sendToServer({ type: 'mcp_config_updated', servers: updated });
305
306
  break;
306
307
  }
308
+
309
+ // LLM configuration (read/write ~/.yeaft/config.json)
310
+ case 'get_llm_config': {
311
+ const config = getLlmConfig(ctx.CONFIG?.yeaftDir);
312
+ sendToServer({ type: 'llm_config', ...config });
313
+ break;
314
+ }
315
+
316
+ case 'update_llm_config': {
317
+ const result = updateLlmConfig(msg.config || {}, ctx.CONFIG?.yeaftDir);
318
+ sendToServer({ type: 'llm_config_updated', ...result });
319
+ break;
320
+ }
307
321
  }
308
322
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.413",
3
+ "version": "0.1.414",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,117 @@
1
+ /**
2
+ * config-api.js — Read/write Yeaft config.json for remote management
3
+ *
4
+ * Provides functions to get and update the LLM-related portion of
5
+ * ~/.yeaft/config.json via WebSocket messages from the web UI.
6
+ *
7
+ * Only exposes provider/model configuration — not internal fields
8
+ * like maxContinueTurns or debug that don't belong in the UI.
9
+ */
10
+
11
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { DEFAULT_YEAFT_DIR } from './init.js';
14
+
15
+ /**
16
+ * Read the LLM-relevant portion of config.json.
17
+ *
18
+ * @param {string} [dir] — Yeaft data directory
19
+ * @returns {{ providers, primaryModel, fastModel, language } | { error: string }}
20
+ */
21
+ export function getLlmConfig(dir) {
22
+ const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
23
+ const configPath = join(root, 'config.json');
24
+
25
+ if (!existsSync(configPath)) {
26
+ return { providers: [], primaryModel: null, fastModel: null, language: 'en', needsSetup: true };
27
+ }
28
+
29
+ try {
30
+ const raw = readFileSync(configPath, 'utf8');
31
+ const json = JSON.parse(raw);
32
+ const providers = Array.isArray(json.providers) ? json.providers : [];
33
+
34
+ // Detect if config still has default/placeholder values (first-time setup needed)
35
+ const needsSetup = providers.length === 0 || providers.every(p =>
36
+ p.apiKey === 'proxy' || p.apiKey === '' || !p.apiKey
37
+ );
38
+
39
+ return {
40
+ providers,
41
+ primaryModel: json.primaryModel || null,
42
+ fastModel: json.fastModel || null,
43
+ language: json.language || 'en',
44
+ needsSetup,
45
+ };
46
+ } catch (e) {
47
+ return { error: `Failed to read config.json: ${e.message}` };
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Update the LLM-relevant portion of config.json.
53
+ * Merges into existing config — preserves fields like debug, maxContextTokens, etc.
54
+ *
55
+ * @param {object} update — { providers?, primaryModel?, fastModel?, language? }
56
+ * @param {string} [dir] — Yeaft data directory
57
+ * @returns {{ providers, primaryModel, fastModel, language } | { error: string }}
58
+ */
59
+ export function updateLlmConfig(update, dir) {
60
+ const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
61
+ const configPath = join(root, 'config.json');
62
+
63
+ // Read existing config (preserve non-LLM fields)
64
+ let existing = {};
65
+ if (existsSync(configPath)) {
66
+ try {
67
+ existing = JSON.parse(readFileSync(configPath, 'utf8'));
68
+ } catch {
69
+ // Start fresh if corrupt
70
+ existing = {};
71
+ }
72
+ }
73
+
74
+ // Validate providers structure
75
+ if (update.providers !== undefined) {
76
+ if (!Array.isArray(update.providers)) {
77
+ return { error: 'providers must be an array' };
78
+ }
79
+ for (const p of update.providers) {
80
+ if (!p.name || typeof p.name !== 'string') {
81
+ return { error: 'Each provider must have a name' };
82
+ }
83
+ if (!p.baseUrl || typeof p.baseUrl !== 'string') {
84
+ return { error: `Provider "${p.name}" must have a baseUrl` };
85
+ }
86
+ if (!Array.isArray(p.models) || p.models.length === 0) {
87
+ return { error: `Provider "${p.name}" must have at least one model` };
88
+ }
89
+ }
90
+ existing.providers = update.providers;
91
+ }
92
+
93
+ // Update model selections
94
+ if (update.primaryModel !== undefined) {
95
+ existing.primaryModel = update.primaryModel || null;
96
+ }
97
+ if (update.fastModel !== undefined) {
98
+ existing.fastModel = update.fastModel || null;
99
+ }
100
+ if (update.language !== undefined) {
101
+ existing.language = update.language;
102
+ }
103
+
104
+ // Write back
105
+ try {
106
+ writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
107
+ } catch (e) {
108
+ return { error: `Failed to write config.json: ${e.message}` };
109
+ }
110
+
111
+ return {
112
+ providers: Array.isArray(existing.providers) ? existing.providers : [],
113
+ primaryModel: existing.primaryModel || null,
114
+ fastModel: existing.fastModel || null,
115
+ language: existing.language || 'en',
116
+ };
117
+ }