@yeaft/webchat-agent 0.1.832 → 0.1.834
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/connection/message-router.js +26 -0
- package/package.json +1 -1
- package/unify/llm/models-dev.js +160 -0
- package/unify/web-bridge.js +13 -1
|
@@ -36,6 +36,7 @@ import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
|
36
36
|
import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
37
37
|
import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
38
38
|
import { getLlmConfig, updateLlmConfig, getUnifySettings, updateUnifySettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../unify/config-api.js';
|
|
39
|
+
import { fetchModelsDev } from '../unify/llm/models-dev.js';
|
|
39
40
|
import { handleUnifyGroupChat, handleUnifyModeSwitch, handleUnifyModelSwitch, resetUnifySession, handleUnifyLoadHistory, handleUnifyLoadMoreHistory, handleUnifyAbortThread, handleUnifyAbortAll, handleUnifyAbortTurn, handleUnifyVpSubscribe, handleUnifyVpCreate, handleUnifyVpUpdate, handleUnifyVpDelete, handleUnifyVpRead, handleUnifyListGroups, handleUnifyCreateGroup, handleUnifyRenameGroup, handleUnifyUpdateGroup, handleUnifyUpdateGroupConfig, handleUnifyArchiveGroup, handleUnifyDeleteGroup, handleUnifyAddMember, handleUnifyRemoveMember, handleUnifySetDefaultVp, handleUnifyDreamTrigger, handleUnifyFetchToolStats, handleUnifyFetchDebugHistory, broadcastLanguageChange } from '../unify/web-bridge.js';
|
|
40
41
|
|
|
41
42
|
export async function handleMessage(msg) {
|
|
@@ -347,6 +348,31 @@ export async function handleMessage(msg) {
|
|
|
347
348
|
break;
|
|
348
349
|
}
|
|
349
350
|
|
|
351
|
+
// models.dev registry (community-maintained provider/model catalog).
|
|
352
|
+
// Used by the LLM settings preset picker to populate provider + model lists.
|
|
353
|
+
case 'get_models_dev_registry': {
|
|
354
|
+
try {
|
|
355
|
+
const data = await fetchModelsDev({
|
|
356
|
+
forceRefresh: !!msg.forceRefresh,
|
|
357
|
+
yeaftDir: ctx.CONFIG?.yeaftDir,
|
|
358
|
+
});
|
|
359
|
+
sendToServer({
|
|
360
|
+
type: 'models_dev_registry',
|
|
361
|
+
requestId: msg.requestId || null,
|
|
362
|
+
registry: data,
|
|
363
|
+
fetchedAt: Date.now(),
|
|
364
|
+
});
|
|
365
|
+
} catch (err) {
|
|
366
|
+
sendToServer({
|
|
367
|
+
type: 'models_dev_registry',
|
|
368
|
+
requestId: msg.requestId || null,
|
|
369
|
+
registry: {},
|
|
370
|
+
error: err?.message || String(err),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
|
|
350
376
|
// task-318: Unify runtime settings (thread concurrency + auto-archive).
|
|
351
377
|
// Read/write the nested `unify` section of config.json — LLM fields
|
|
352
378
|
// untouched. On update we broadcast a `unify_settings_updated` event
|
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* models.dev registry integration — community-maintained provider/model database.
|
|
3
|
+
*
|
|
4
|
+
* Fetches https://models.dev/api.json (4000+ models across 109+ providers) and
|
|
5
|
+
* exposes provider/model metadata to the rest of the agent. Ported from hermes
|
|
6
|
+
* `agent/models_dev.py`.
|
|
7
|
+
*
|
|
8
|
+
* Cache hierarchy (when forceRefresh=false):
|
|
9
|
+
* 1. In-memory cache, populated and < TTL old → return immediately.
|
|
10
|
+
* 2. Disk cache file < TTL old by mtime → load, populate in-mem, return.
|
|
11
|
+
* 3. Network fetch → on success, save to disk + in-mem and return.
|
|
12
|
+
* 4. Network fails → fall back to ANY available disk cache (even stale)
|
|
13
|
+
* with a short 5 min in-mem grace period.
|
|
14
|
+
*
|
|
15
|
+
* forceRefresh=true skips stages 1 and 2 (used by manual refresh button).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFile, writeFile, stat, mkdir, rename } from 'fs/promises';
|
|
19
|
+
import { join, dirname } from 'path';
|
|
20
|
+
import { homedir } from 'os';
|
|
21
|
+
|
|
22
|
+
const MODELS_DEV_URL = 'https://models.dev/api.json';
|
|
23
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
24
|
+
const NETWORK_TIMEOUT_MS = 15000;
|
|
25
|
+
const STALE_GRACE_MS = 5 * 60 * 1000; // 5 minutes when serving stale-after-failure
|
|
26
|
+
|
|
27
|
+
let _memCache = null;
|
|
28
|
+
let _memCacheTime = 0;
|
|
29
|
+
|
|
30
|
+
function getCachePath(yeaftDir) {
|
|
31
|
+
const base = yeaftDir || join(homedir(), '.yeaft');
|
|
32
|
+
return join(base, 'models_dev_cache.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function diskCacheAgeMs(yeaftDir) {
|
|
36
|
+
try {
|
|
37
|
+
const s = await stat(getCachePath(yeaftDir));
|
|
38
|
+
const age = Date.now() - s.mtimeMs;
|
|
39
|
+
return age < 0 ? null : age;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function loadDiskCache(yeaftDir) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = await readFile(getCachePath(yeaftDir), 'utf8');
|
|
48
|
+
return JSON.parse(raw);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function saveDiskCache(yeaftDir, data) {
|
|
55
|
+
try {
|
|
56
|
+
const path = getCachePath(yeaftDir);
|
|
57
|
+
await mkdir(dirname(path), { recursive: true });
|
|
58
|
+
const tmp = path + '.tmp';
|
|
59
|
+
await writeFile(tmp, JSON.stringify(data), 'utf8');
|
|
60
|
+
await rename(tmp, path);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Cache is best-effort. Failure to persist must not break callers.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fetch the models.dev registry with layered caching.
|
|
68
|
+
* @param {object} [opts]
|
|
69
|
+
* @param {boolean} [opts.forceRefresh] Skip in-mem + fresh-disk stages.
|
|
70
|
+
* @param {string} [opts.yeaftDir] Override ~/.yeaft cache directory.
|
|
71
|
+
* @returns {Promise<object>} provider-id → provider entry; {} on total failure.
|
|
72
|
+
*/
|
|
73
|
+
export async function fetchModelsDev({ forceRefresh = false, yeaftDir = null } = {}) {
|
|
74
|
+
// Stage 1: in-memory cache.
|
|
75
|
+
if (!forceRefresh && _memCache && Date.now() - _memCacheTime < CACHE_TTL_MS) {
|
|
76
|
+
return _memCache;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Stage 2: fresh-by-mtime disk cache short-circuits the network.
|
|
80
|
+
if (!forceRefresh) {
|
|
81
|
+
const age = await diskCacheAgeMs(yeaftDir);
|
|
82
|
+
if (age !== null && age < CACHE_TTL_MS) {
|
|
83
|
+
const data = await loadDiskCache(yeaftDir);
|
|
84
|
+
if (data && typeof data === 'object') {
|
|
85
|
+
_memCache = data;
|
|
86
|
+
_memCacheTime = Date.now() - age;
|
|
87
|
+
return _memCache;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Stage 3: network fetch.
|
|
93
|
+
try {
|
|
94
|
+
const ctrl = new AbortController();
|
|
95
|
+
const timer = setTimeout(() => ctrl.abort(), NETWORK_TIMEOUT_MS);
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(MODELS_DEV_URL, { signal: ctrl.signal });
|
|
98
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
99
|
+
const data = await res.json();
|
|
100
|
+
if (data && typeof data === 'object') {
|
|
101
|
+
_memCache = data;
|
|
102
|
+
_memCacheTime = Date.now();
|
|
103
|
+
await saveDiskCache(yeaftDir, data);
|
|
104
|
+
return data;
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// Fall through to stale cache.
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Stage 4: stale disk cache fallback with short grace TTL so we retry soon.
|
|
114
|
+
if (!_memCache) {
|
|
115
|
+
const data = await loadDiskCache(yeaftDir);
|
|
116
|
+
if (data && typeof data === 'object') {
|
|
117
|
+
_memCache = data;
|
|
118
|
+
_memCacheTime = Date.now() - CACHE_TTL_MS + STALE_GRACE_MS;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return _memCache || {};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* List all provider ids in the registry.
|
|
127
|
+
* @returns {Promise<string[]>}
|
|
128
|
+
*/
|
|
129
|
+
export async function listProviders({ yeaftDir = null } = {}) {
|
|
130
|
+
const data = await fetchModelsDev({ yeaftDir });
|
|
131
|
+
return Object.keys(data).sort();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Return raw provider entry from models.dev (with name, env, api, models).
|
|
136
|
+
*/
|
|
137
|
+
export async function getProviderInfo(providerId, { yeaftDir = null } = {}) {
|
|
138
|
+
const data = await fetchModelsDev({ yeaftDir });
|
|
139
|
+
const entry = data[providerId];
|
|
140
|
+
return entry && typeof entry === 'object' ? entry : null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* List model ids advertised by a provider.
|
|
145
|
+
* @returns {Promise<string[]>}
|
|
146
|
+
*/
|
|
147
|
+
export async function listProviderModels(providerId, { yeaftDir = null } = {}) {
|
|
148
|
+
const info = await getProviderInfo(providerId, { yeaftDir });
|
|
149
|
+
const models = info?.models;
|
|
150
|
+
if (!models || typeof models !== 'object') return [];
|
|
151
|
+
return Object.keys(models);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Reset the in-memory cache. Test seam.
|
|
156
|
+
*/
|
|
157
|
+
export function _resetMemCache() {
|
|
158
|
+
_memCache = null;
|
|
159
|
+
_memCacheTime = 0;
|
|
160
|
+
}
|
package/unify/web-bridge.js
CHANGED
|
@@ -1231,12 +1231,24 @@ export async function __testResetVpState() {
|
|
|
1231
1231
|
* Envelope fields: conversationId, groupId, vpId, turnId, threadId let the
|
|
1232
1232
|
* frontend route incremental deltas to the correct per-VP/thread block.
|
|
1233
1233
|
*/
|
|
1234
|
+
function resolveGroupDefaultVpId(groupId) {
|
|
1235
|
+
if (!groupId) return null;
|
|
1236
|
+
try {
|
|
1237
|
+
const meta = ensureGroupCoordinator(groupId)?.group?.getMeta?.();
|
|
1238
|
+
const vpId = typeof meta?.defaultVpId === 'string' ? meta.defaultVpId.trim() : '';
|
|
1239
|
+
return vpId || null;
|
|
1240
|
+
} catch {
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1234
1245
|
function sendUnifyOutput(data, { groupId, vpId, turnId, threadId } = {}) {
|
|
1246
|
+
const resolvedVpId = vpId || resolveGroupDefaultVpId(groupId);
|
|
1235
1247
|
sendToServer({
|
|
1236
1248
|
type: 'unify_output',
|
|
1237
1249
|
conversationId: unifyConversationId,
|
|
1238
1250
|
...(groupId ? { groupId } : {}),
|
|
1239
|
-
...(
|
|
1251
|
+
...(resolvedVpId ? { vpId: resolvedVpId } : {}),
|
|
1240
1252
|
...(turnId ? { turnId } : {}),
|
|
1241
1253
|
...(threadId ? { threadId } : {}),
|
|
1242
1254
|
data,
|