@yeaft/webchat-agent 1.0.114 → 1.0.115
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/claude.js +12 -2
- package/connection/message-router.js +6 -0
- package/context.js +11 -0
- package/metrics.js +109 -0
- package/package.json +1 -1
- package/yeaft/web-bridge.js +8 -0
package/claude.js
CHANGED
|
@@ -2,6 +2,7 @@ import { writeFile, mkdir } from 'fs/promises';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { query, Stream } from './sdk/index.js';
|
|
4
4
|
import ctx from './context.js';
|
|
5
|
+
import { recordAgentTokenUsage, recordAgentTurn } from './metrics.js';
|
|
5
6
|
import { sendConversationList, sendOutput, sendError, handleAskUserQuestion } from './conversation.js';
|
|
6
7
|
import { startSubagentWatcher, stopSubagentWatcher, cleanupSubagentWatchers } from './subagent.js';
|
|
7
8
|
import { SYNTHETIC_TOOL_NAMES } from './synthetic-tools.js';
|
|
@@ -627,10 +628,18 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
627
628
|
if (message.usage) {
|
|
628
629
|
const inputDelta = (message.usage.input_tokens || 0) - (state.lastResultInputTokens || 0);
|
|
629
630
|
const outputDelta = (message.usage.output_tokens || 0) - (state.lastResultOutputTokens || 0);
|
|
631
|
+
const cacheReadDelta = Math.max(0, (message.usage.cache_read_input_tokens || 0) - (state.lastResultCacheRead || 0));
|
|
632
|
+
const cacheCreationDelta = Math.max(0, (message.usage.cache_creation_input_tokens || 0) - (state.lastResultCacheCreation || 0));
|
|
630
633
|
if (inputDelta > 0) state.usage.inputTokens += inputDelta;
|
|
631
634
|
if (outputDelta > 0) state.usage.outputTokens += outputDelta;
|
|
632
|
-
state.usage.cacheRead +=
|
|
633
|
-
state.usage.cacheCreation +=
|
|
635
|
+
state.usage.cacheRead += cacheReadDelta;
|
|
636
|
+
state.usage.cacheCreation += cacheCreationDelta;
|
|
637
|
+
recordAgentTokenUsage({
|
|
638
|
+
inputTokens: inputDelta,
|
|
639
|
+
outputTokens: outputDelta,
|
|
640
|
+
cacheReadTokens: cacheReadDelta,
|
|
641
|
+
cacheWriteTokens: cacheCreationDelta,
|
|
642
|
+
});
|
|
634
643
|
state.lastResultInputTokens = message.usage.input_tokens || 0;
|
|
635
644
|
state.lastResultOutputTokens = message.usage.output_tokens || 0;
|
|
636
645
|
state.lastResultCacheRead = message.usage.cache_read_input_tokens || 0;
|
|
@@ -664,6 +673,7 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
664
673
|
// ★ Turn 完成:发送 turn_completed,进程继续运行等待下一条消息
|
|
665
674
|
// stream-json 模式下 Claude 进程是持久运行的,for-await 在 result 后继续等待
|
|
666
675
|
// 不清空 state.query 和 state.inputStream,下次用户消息直接通过同一个 inputStream 发送
|
|
676
|
+
recordAgentTurn('chat');
|
|
667
677
|
state.turnResultReceived = true;
|
|
668
678
|
resultHandled = true;
|
|
669
679
|
state.turnActive = false;
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
handlePingSession
|
|
24
24
|
} from '../conversation.js';
|
|
25
25
|
import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
26
|
+
import { sendAgentMetricsSnapshot } from '../metrics.js';
|
|
26
27
|
import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
27
28
|
import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
28
29
|
import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
|
|
@@ -69,6 +70,7 @@ export async function handleMessage(msg) {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
sendConversationList();
|
|
73
|
+
sendAgentMetricsSnapshot();
|
|
72
74
|
startYeaftStatusRefresh();
|
|
73
75
|
|
|
74
76
|
// fix-yeaft-session-per-agent: eagerly broadcast this agent's
|
|
@@ -120,6 +122,10 @@ export async function handleMessage(msg) {
|
|
|
120
122
|
sendConversationList();
|
|
121
123
|
break;
|
|
122
124
|
|
|
125
|
+
case 'get_agent_metrics':
|
|
126
|
+
sendAgentMetricsSnapshot();
|
|
127
|
+
break;
|
|
128
|
+
|
|
123
129
|
case 'list_history_sessions':
|
|
124
130
|
await handleListHistorySessions(msg);
|
|
125
131
|
break;
|
package/context.js
CHANGED
|
@@ -21,6 +21,17 @@ export default {
|
|
|
21
21
|
slashCommands: [],
|
|
22
22
|
// Slash command 描述映射: { commandName: description } — 从 plugin commands/*.md 提取
|
|
23
23
|
slashCommandDescriptions: {},
|
|
24
|
+
agentMetrics: {
|
|
25
|
+
chatTurns: 0,
|
|
26
|
+
yeaftTurns: 0,
|
|
27
|
+
sessionsCreated: 0,
|
|
28
|
+
inputTokens: 0,
|
|
29
|
+
outputTokens: 0,
|
|
30
|
+
cacheReadTokens: 0,
|
|
31
|
+
cacheWriteTokens: 0,
|
|
32
|
+
totalTokens: 0,
|
|
33
|
+
lastUpdatedAt: 0,
|
|
34
|
+
},
|
|
24
35
|
// MCP servers 列表 (从 ~/.claude.json 读取): [{ name, enabled, source }]
|
|
25
36
|
mcpServers: [],
|
|
26
37
|
// 连接相关
|
package/metrics.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import ctx from './context.js';
|
|
2
|
+
|
|
3
|
+
function finiteNumber(value) {
|
|
4
|
+
const n = Number(value);
|
|
5
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function ensureMetrics() {
|
|
9
|
+
if (!ctx.agentMetrics || typeof ctx.agentMetrics !== 'object') {
|
|
10
|
+
ctx.agentMetrics = {};
|
|
11
|
+
}
|
|
12
|
+
const metrics = ctx.agentMetrics;
|
|
13
|
+
metrics.chatTurns = finiteNumber(metrics.chatTurns);
|
|
14
|
+
metrics.yeaftTurns = finiteNumber(metrics.yeaftTurns);
|
|
15
|
+
metrics.sessionsCreated = finiteNumber(metrics.sessionsCreated);
|
|
16
|
+
metrics.inputTokens = finiteNumber(metrics.inputTokens);
|
|
17
|
+
metrics.outputTokens = finiteNumber(metrics.outputTokens);
|
|
18
|
+
metrics.cacheReadTokens = finiteNumber(metrics.cacheReadTokens);
|
|
19
|
+
metrics.cacheWriteTokens = finiteNumber(metrics.cacheWriteTokens);
|
|
20
|
+
metrics.totalTokens = finiteNumber(metrics.totalTokens);
|
|
21
|
+
metrics.lastUpdatedAt = finiteNumber(metrics.lastUpdatedAt);
|
|
22
|
+
return metrics;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const AGENT_METRICS_EMIT_INTERVAL_MS = 2000;
|
|
26
|
+
|
|
27
|
+
let pendingMetricsEmit = null;
|
|
28
|
+
let lastMetricsEmitAt = 0;
|
|
29
|
+
|
|
30
|
+
function touch(metrics) {
|
|
31
|
+
metrics.lastUpdatedAt = Date.now();
|
|
32
|
+
scheduleAgentMetricsSnapshot();
|
|
33
|
+
return metrics;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function scheduleAgentMetricsSnapshot() {
|
|
37
|
+
if (pendingMetricsEmit) return;
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const delay = lastMetricsEmitAt > 0
|
|
40
|
+
? Math.max(AGENT_METRICS_EMIT_INTERVAL_MS - (now - lastMetricsEmitAt), 0)
|
|
41
|
+
: AGENT_METRICS_EMIT_INTERVAL_MS;
|
|
42
|
+
pendingMetricsEmit = setTimeout(() => {
|
|
43
|
+
pendingMetricsEmit = null;
|
|
44
|
+
emitAgentMetricsSnapshot();
|
|
45
|
+
}, delay);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function recordAgentTurn(kind = 'chat') {
|
|
49
|
+
const metrics = ensureMetrics();
|
|
50
|
+
if (kind === 'yeaft') metrics.yeaftTurns += 1;
|
|
51
|
+
else metrics.chatTurns += 1;
|
|
52
|
+
touch(metrics);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function recordAgentSessionCreated() {
|
|
56
|
+
const metrics = ensureMetrics();
|
|
57
|
+
metrics.sessionsCreated += 1;
|
|
58
|
+
touch(metrics);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function recordAgentTokenUsage(usage = {}) {
|
|
62
|
+
const metrics = ensureMetrics();
|
|
63
|
+
const inputTokens = finiteNumber(usage.inputTokens ?? usage.input_tokens ?? usage.promptTokens ?? usage.prompt_tokens);
|
|
64
|
+
const outputTokens = finiteNumber(usage.outputTokens ?? usage.output_tokens ?? usage.completionTokens ?? usage.completion_tokens);
|
|
65
|
+
const cacheReadTokens = finiteNumber(usage.cacheReadTokens ?? usage.cache_read_tokens ?? usage.cache_read_input_tokens);
|
|
66
|
+
const cacheWriteTokens = finiteNumber(usage.cacheWriteTokens ?? usage.cacheWriteInputTokens ?? usage.cache_creation_input_tokens);
|
|
67
|
+
const explicitTotal = finiteNumber(usage.totalTokens ?? usage.total_tokens);
|
|
68
|
+
const totalTokens = explicitTotal || inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
|
|
69
|
+
|
|
70
|
+
if (inputTokens > 0) metrics.inputTokens += inputTokens;
|
|
71
|
+
if (outputTokens > 0) metrics.outputTokens += outputTokens;
|
|
72
|
+
if (cacheReadTokens > 0) metrics.cacheReadTokens += cacheReadTokens;
|
|
73
|
+
if (cacheWriteTokens > 0) metrics.cacheWriteTokens += cacheWriteTokens;
|
|
74
|
+
if (totalTokens > 0) metrics.totalTokens += totalTokens;
|
|
75
|
+
if (inputTokens || outputTokens || cacheReadTokens || cacheWriteTokens || totalTokens) touch(metrics);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function snapshotAgentMetrics() {
|
|
79
|
+
const metrics = ensureMetrics();
|
|
80
|
+
return {
|
|
81
|
+
chatTurns: metrics.chatTurns,
|
|
82
|
+
yeaftTurns: metrics.yeaftTurns,
|
|
83
|
+
totalTurns: metrics.chatTurns + metrics.yeaftTurns,
|
|
84
|
+
sessionsCreated: metrics.sessionsCreated,
|
|
85
|
+
inputTokens: metrics.inputTokens,
|
|
86
|
+
outputTokens: metrics.outputTokens,
|
|
87
|
+
cacheReadTokens: metrics.cacheReadTokens,
|
|
88
|
+
cacheWriteTokens: metrics.cacheWriteTokens,
|
|
89
|
+
totalTokens: metrics.totalTokens,
|
|
90
|
+
lastUpdatedAt: metrics.lastUpdatedAt || null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function emitAgentMetricsSnapshot() {
|
|
95
|
+
lastMetricsEmitAt = Date.now();
|
|
96
|
+
if (typeof ctx.sendToServer !== 'function') return;
|
|
97
|
+
ctx.sendToServer({
|
|
98
|
+
type: 'agent_metrics',
|
|
99
|
+
metrics: snapshotAgentMetrics(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function sendAgentMetricsSnapshot() {
|
|
104
|
+
if (pendingMetricsEmit) {
|
|
105
|
+
clearTimeout(pendingMetricsEmit);
|
|
106
|
+
pendingMetricsEmit = null;
|
|
107
|
+
}
|
|
108
|
+
emitAgentMetricsSnapshot();
|
|
109
|
+
}
|
package/package.json
CHANGED
package/yeaft/web-bridge.js
CHANGED
|
@@ -72,6 +72,7 @@ import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
|
|
|
72
72
|
import { getAgentRegistry, agentBelongsToScope } from './tools/agent.js';
|
|
73
73
|
import { isPromptableAgentStatus } from './sub-agent/status.js';
|
|
74
74
|
import { perfNowMs, recordAgentPerfTrace } from './perf-trace.js';
|
|
75
|
+
import { recordAgentSessionCreated, recordAgentTokenUsage, recordAgentTurn } from '../metrics.js';
|
|
75
76
|
|
|
76
77
|
const LEGACY_SKILL_COMMAND_PREFIX = 'skill:';
|
|
77
78
|
const YEAFT_SKILL_COMMAND_PREFIX = 'yeaft-skills:';
|
|
@@ -2211,6 +2212,9 @@ function mergedStatusForProjectRuntime(runtime) {
|
|
|
2211
2212
|
|
|
2212
2213
|
/** Send a Yeaft Session metadata event over the legacy-compatible envelope. */
|
|
2213
2214
|
function sendSessionEvent(event, { sessionId, chatId, vpId, turnId, threadId, perfTraceId } = {}) {
|
|
2215
|
+
if (event?.type === 'loop') {
|
|
2216
|
+
recordAgentTokenUsage(event.usage || {});
|
|
2217
|
+
}
|
|
2214
2218
|
sendToServer({
|
|
2215
2219
|
type: 'yeaft_output',
|
|
2216
2220
|
conversationId: yeaftConversationId,
|
|
@@ -2468,6 +2472,7 @@ export function handleYeaftCreateSession(msg) {
|
|
|
2468
2472
|
try {
|
|
2469
2473
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
2470
2474
|
const group = createSessionFromSpec(yeaftDir, withDefaultSessionConfig(payload));
|
|
2475
|
+
recordAgentSessionCreated();
|
|
2471
2476
|
group.config = loadSessionConfig(yeaftDir, group.id);
|
|
2472
2477
|
sendSessionCrudResult({ op: 'create', requestId, ok: true, session: group });
|
|
2473
2478
|
sendSessionSnapshotBroadcast();
|
|
@@ -3289,12 +3294,15 @@ function handleEngineEvent(event, hctx) {
|
|
|
3289
3294
|
break;
|
|
3290
3295
|
|
|
3291
3296
|
case 'turn_close':
|
|
3297
|
+
recordAgentTurn('yeaft');
|
|
3292
3298
|
sendSessionEvent({
|
|
3293
3299
|
type: 'turn_close',
|
|
3294
3300
|
turnId: event.turnId,
|
|
3301
|
+
threadId: event.threadId,
|
|
3295
3302
|
totalMs: event.totalMs,
|
|
3296
3303
|
totalTokens: event.totalTokens,
|
|
3297
3304
|
loopCount: event.loopCount,
|
|
3305
|
+
ts: Date.now(),
|
|
3298
3306
|
}, envelope);
|
|
3299
3307
|
break;
|
|
3300
3308
|
|