@yeaft/webchat-agent 1.0.113 → 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 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 += Math.max(0, (message.usage.cache_read_input_tokens || 0) - (state.lastResultCacheRead || 0));
633
- state.usage.cacheCreation += Math.max(0, (message.usage.cache_creation_input_tokens || 0) - (state.lastResultCacheCreation || 0));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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