@yeaft/webchat-agent 1.0.141 → 1.0.143

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/context.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from 'crypto';
2
+
1
3
  // 共享上下文对象 - 所有模块通过 import 访问
2
4
  // 由 index.js 在启动时初始化
3
5
 
@@ -22,6 +24,7 @@ export default {
22
24
  // Slash command 描述映射: { commandName: description } — 从 plugin commands/*.md 提取
23
25
  slashCommandDescriptions: {},
24
26
  agentMetrics: {
27
+ metricEpoch: randomUUID(),
25
28
  chatTurns: 0,
26
29
  yeaftTurns: 0,
27
30
  sessionsCreated: 0,
package/metrics.js CHANGED
@@ -10,6 +10,9 @@ function ensureMetrics() {
10
10
  ctx.agentMetrics = {};
11
11
  }
12
12
  const metrics = ctx.agentMetrics;
13
+ if (typeof metrics.metricEpoch !== 'string' || !metrics.metricEpoch) {
14
+ metrics.metricEpoch = `legacy-${process.pid}-${Date.now()}`;
15
+ }
13
16
  metrics.chatTurns = finiteNumber(metrics.chatTurns);
14
17
  metrics.yeaftTurns = finiteNumber(metrics.yeaftTurns);
15
18
  metrics.sessionsCreated = finiteNumber(metrics.sessionsCreated);
@@ -78,6 +81,7 @@ export function recordAgentTokenUsage(usage = {}) {
78
81
  export function snapshotAgentMetrics() {
79
82
  const metrics = ensureMetrics();
80
83
  return {
84
+ metricEpoch: metrics.metricEpoch,
81
85
  chatTurns: metrics.chatTurns,
82
86
  yeaftTurns: metrics.yeaftTurns,
83
87
  totalTurns: metrics.chatTurns + metrics.yeaftTurns,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.141",
3
+ "version": "1.0.143",
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",
@@ -572,6 +572,9 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
572
572
  usage: {
573
573
  inputTokens: usage.input_tokens || 0,
574
574
  outputTokens: usage.output_tokens || 0,
575
+ cacheReadTokens: usage.input_tokens_details?.cached_tokens || 0,
576
+ cacheWriteTokens: 0,
577
+ cacheTokensAreIncludedInInput: true,
575
578
  },
576
579
  };
577
580
  }
@@ -0,0 +1,112 @@
1
+ import { LLMAdapter } from './adapter.js';
2
+
3
+ function tokenCount(value) {
4
+ const number = Number(value);
5
+ return Number.isFinite(number) && number > 0 ? number : 0;
6
+ }
7
+
8
+ /**
9
+ * Normalize one provider usage payload without double-counting cached input.
10
+ * OpenAI includes cached tokens in input_tokens; Anthropic reports cache input
11
+ * separately. Adapters expose that distinction through
12
+ * `cacheTokensAreIncludedInInput`.
13
+ */
14
+ export function normalizeTokenUsage(usage = {}) {
15
+ const inputTokens = tokenCount(usage.inputTokens ?? usage.input_tokens);
16
+ const outputTokens = tokenCount(usage.outputTokens ?? usage.output_tokens);
17
+ const cacheReadTokens = tokenCount(
18
+ usage.cacheReadTokens ?? usage.cache_read_tokens ?? usage.cache_read_input_tokens
19
+ );
20
+ const cacheWriteTokens = tokenCount(
21
+ usage.cacheWriteTokens ?? usage.cache_write_tokens ?? usage.cache_creation_input_tokens
22
+ );
23
+ const explicitTotal = tokenCount(usage.totalTokens ?? usage.total_tokens);
24
+ const cacheInputTokens = usage.cacheTokensAreIncludedInInput === true
25
+ ? 0
26
+ : cacheReadTokens + cacheWriteTokens;
27
+
28
+ return {
29
+ inputTokens,
30
+ outputTokens,
31
+ cacheReadTokens,
32
+ cacheWriteTokens,
33
+ totalTokens: explicitTotal || inputTokens + outputTokens + cacheInputTokens,
34
+ };
35
+ }
36
+
37
+ function addUsage(total, usage) {
38
+ const normalized = normalizeTokenUsage(usage);
39
+ total.inputTokens += normalized.inputTokens;
40
+ total.outputTokens += normalized.outputTokens;
41
+ total.cacheReadTokens += normalized.cacheReadTokens;
42
+ total.cacheWriteTokens += normalized.cacheWriteTokens;
43
+ total.totalTokens += normalized.totalTokens;
44
+ }
45
+
46
+ function hasUsage(usage) {
47
+ return Object.values(usage).some(value => value > 0);
48
+ }
49
+
50
+ /**
51
+ * Wrap the shared adapter at the provider-call boundary. Every stream request
52
+ * reports once after its event stream finishes (or aborts after returning
53
+ * usage), and every non-streaming side call reports once after success.
54
+ *
55
+ * Parent VP engines, sub-agent engines, Dream, compact, reflection, AMS, and
56
+ * classifiers all reuse this adapter, so none need their own accounting hook.
57
+ */
58
+ export class UsageAccountingAdapter extends LLMAdapter {
59
+ #adapter;
60
+ #onUsage;
61
+
62
+ constructor(adapter, onUsage) {
63
+ super(adapter?.config || {});
64
+ this.#adapter = adapter;
65
+ this.#onUsage = onUsage;
66
+ }
67
+
68
+ #report(usage) {
69
+ if (!hasUsage(usage)) return;
70
+ try {
71
+ this.#onUsage(usage);
72
+ } catch (error) {
73
+ console.warn(`[llm-usage] accounting callback failed: ${error?.message || error}`);
74
+ }
75
+ }
76
+
77
+ async *stream(params) {
78
+ const total = normalizeTokenUsage();
79
+ try {
80
+ for await (const event of this.#adapter.stream(params)) {
81
+ if (event?.type === 'usage') addUsage(total, event);
82
+ yield event;
83
+ }
84
+ } finally {
85
+ this.#report(total);
86
+ }
87
+ }
88
+
89
+ async call(params) {
90
+ const result = await this.#adapter.call(params);
91
+ this.#report(normalizeTokenUsage(result?.usage || {}));
92
+ return result;
93
+ }
94
+
95
+ refreshProviders(providers) {
96
+ return this.#adapter.refreshProviders?.(providers);
97
+ }
98
+
99
+ getProviderForModel(modelId) {
100
+ return this.#adapter.getProviderForModel?.(modelId) || null;
101
+ }
102
+
103
+ listAvailableModels() {
104
+ return this.#adapter.listAvailableModels?.() || [];
105
+ }
106
+ }
107
+
108
+ export function withUsageAccounting(adapter, onUsage) {
109
+ return typeof onUsage === 'function'
110
+ ? new UsageAccountingAdapter(adapter, onUsage)
111
+ : adapter;
112
+ }
package/yeaft/session.js CHANGED
@@ -17,6 +17,8 @@ import { initYeaftDir, DEFAULT_YEAFT_DIR, isWritable } from './init.js';
17
17
  import { loadConfig, loadMCPConfig } from './config.js';
18
18
  import { createTrace } from './debug-trace.js';
19
19
  import { createLLMAdapter } from './llm/adapter.js';
20
+ import { withUsageAccounting } from './llm/usage-accounting.js';
21
+ import { recordAgentTokenUsage } from '../metrics.js';
20
22
  import { ConversationStore, setDefaultRecentTurnsLimit } from './conversation/persist.js';
21
23
  import { SkillManager, createSkillManager } from './skills.js';
22
24
  import { MCPManager } from './mcp.js';
@@ -262,7 +264,10 @@ export async function loadSession(options = {}) {
262
264
  });
263
265
 
264
266
  // ─── 4. Create LLM adapter ────────────────────────────
265
- const adapter = await createLLMAdapter(config);
267
+ const adapter = withUsageAccounting(
268
+ await createLLMAdapter(config),
269
+ recordAgentTokenUsage
270
+ );
266
271
 
267
272
  // ─── 5. Create stores ──────────────────────────────────
268
273
  const conversationStore = new ConversationStore(yeaftDir);
@@ -72,7 +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
+ import { recordAgentSessionCreated, recordAgentTurn } from '../metrics.js';
76
76
 
77
77
  const LEGACY_SKILL_COMMAND_PREFIX = 'skill:';
78
78
  const YEAFT_SKILL_COMMAND_PREFIX = 'yeaft-skills:';
@@ -2212,9 +2212,6 @@ function mergedStatusForProjectRuntime(runtime) {
2212
2212
 
2213
2213
  /** Send a Yeaft Session metadata event over the legacy-compatible envelope. */
2214
2214
  function sendSessionEvent(event, { sessionId, chatId, vpId, turnId, threadId, perfTraceId } = {}) {
2215
- if (event?.type === 'loop') {
2216
- recordAgentTokenUsage(event.usage || {});
2217
- }
2218
2215
  sendToServer({
2219
2216
  type: 'yeaft_output',
2220
2217
  conversationId: yeaftConversationId,