@xcanwin/manyoyo 6.2.8 → 6.2.9

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.
@@ -2083,6 +2083,13 @@
2083
2083
  { label: '最近 resume', value: lastResumeText },
2084
2084
  { label: '最近结果', value: detail.lastResumeOk == null ? '暂无' : (detail.lastResumeOk ? '成功' : '失败'), tone: detail.lastResumeOk == null ? 'info' : (detail.lastResumeOk ? 'ok' : 'danger') }
2085
2085
  ]);
2086
+ renderKeyValueCard(detailSummary, '用量统计', detail.usageTotal ? [
2087
+ { label: '累计输入 tokens', value: String(detail.usageTotal.inputTokens) },
2088
+ { label: '累计输出 tokens', value: String(detail.usageTotal.outputTokens) },
2089
+ { label: '累计花费', value: typeof detail.usageTotal.costUsd === 'number' ? `$${detail.usageTotal.costUsd.toFixed(4)}` : '暂不支持' }
2090
+ ] : [
2091
+ { label: '状态', value: '暂无数据(当前 Agent 程序不支持用量统计,或还未执行过对话)', tone: 'info' }
2092
+ ]);
2086
2093
  renderKeyValueCard(detailSummary, '最近活动', [
2087
2094
  { label: '最近角色', value: latestRoleLabel },
2088
2095
  { label: '最近时间', value: latestTimestampText },
package/lib/web/server.js CHANGED
@@ -189,7 +189,25 @@ function createEmptyWebAgentSession(agentId, agentName) {
189
189
  lastResumeAt: null,
190
190
  lastResumeOk: null,
191
191
  lastResumeError: '',
192
- engineSessionId: ''
192
+ engineSessionId: '',
193
+ usageTotal: null
194
+ };
195
+ }
196
+
197
+ function normalizeWebAgentUsageTotal(value) {
198
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
199
+ return null;
200
+ }
201
+ if (typeof value.inputTokens !== 'number' || typeof value.outputTokens !== 'number') {
202
+ return null;
203
+ }
204
+ if (typeof value.costUsd !== 'number' && value.costUsd !== null) {
205
+ return null;
206
+ }
207
+ return {
208
+ inputTokens: value.inputTokens,
209
+ outputTokens: value.outputTokens,
210
+ costUsd: typeof value.costUsd === 'number' ? value.costUsd : null
193
211
  };
194
212
  }
195
213
 
@@ -218,7 +236,8 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
218
236
  lastResumeAt: typeof source.lastResumeAt === 'string' ? source.lastResumeAt : null,
219
237
  lastResumeOk: typeof source.lastResumeOk === 'boolean' ? source.lastResumeOk : null,
220
238
  lastResumeError: typeof source.lastResumeError === 'string' ? source.lastResumeError : '',
221
- engineSessionId: typeof source.engineSessionId === 'string' ? source.engineSessionId : ''
239
+ engineSessionId: typeof source.engineSessionId === 'string' ? source.engineSessionId : '',
240
+ usageTotal: normalizeWebAgentUsageTotal(source.usageTotal)
222
241
  };
223
242
  }
224
243
 
@@ -1039,6 +1058,86 @@ function extractEngineSessionId(agentProgram, text) {
1039
1058
  return '';
1040
1059
  }
1041
1060
 
1061
+ function extractClaudeTurnUsage(text) {
1062
+ let result = null;
1063
+ for (const rawLine of String(text || '').split('\n')) {
1064
+ const payload = parseJsonObjectLine(rawLine);
1065
+ if (!payload || payload.type !== 'result') {
1066
+ continue;
1067
+ }
1068
+ const usage = toPlainObject(payload.usage);
1069
+ result = {
1070
+ inputTokens: typeof usage.input_tokens === 'number' ? usage.input_tokens : null,
1071
+ outputTokens: typeof usage.output_tokens === 'number' ? usage.output_tokens : null,
1072
+ costUsd: typeof payload.total_cost_usd === 'number' ? payload.total_cost_usd : null
1073
+ };
1074
+ }
1075
+ return result;
1076
+ }
1077
+
1078
+ function extractCodexTurnUsage(text) {
1079
+ let result = null;
1080
+ for (const rawLine of String(text || '').split('\n')) {
1081
+ const payload = parseJsonObjectLine(rawLine);
1082
+ if (!payload || payload.type !== 'turn.completed') {
1083
+ continue;
1084
+ }
1085
+ const usage = toPlainObject(payload.usage);
1086
+ result = {
1087
+ inputTokens: typeof usage.input_tokens === 'number' ? usage.input_tokens : null,
1088
+ outputTokens: typeof usage.output_tokens === 'number' ? usage.output_tokens : null,
1089
+ costUsd: null
1090
+ };
1091
+ }
1092
+ return result;
1093
+ }
1094
+
1095
+ function extractOpenCodeTurnUsage(text) {
1096
+ let result = null;
1097
+ for (const rawLine of String(text || '').split('\n')) {
1098
+ const payload = parseJsonObjectLine(rawLine);
1099
+ if (!payload || payload.type !== 'step_finish') {
1100
+ continue;
1101
+ }
1102
+ const part = toPlainObject(payload.part);
1103
+ const tokens = toPlainObject(part.tokens);
1104
+ result = {
1105
+ inputTokens: typeof tokens.input === 'number' ? tokens.input : null,
1106
+ outputTokens: typeof tokens.output === 'number' ? tokens.output : null,
1107
+ costUsd: typeof part.cost === 'number' ? part.cost : null
1108
+ };
1109
+ }
1110
+ return result;
1111
+ }
1112
+
1113
+ // Gemini 的非交互 stream-json 输出目前没有已确认的 usage/token 字段可解析,故不返回数据(而非猜测字段名)。
1114
+ function extractTurnUsage(agentProgram, text) {
1115
+ if (agentProgram === 'claude') {
1116
+ return extractClaudeTurnUsage(text);
1117
+ }
1118
+ if (agentProgram === 'codex') {
1119
+ return extractCodexTurnUsage(text);
1120
+ }
1121
+ if (agentProgram === 'opencode') {
1122
+ return extractOpenCodeTurnUsage(text);
1123
+ }
1124
+ return null;
1125
+ }
1126
+
1127
+ function accumulateUsageTotal(baseline, turnUsage) {
1128
+ const base = baseline && typeof baseline === 'object'
1129
+ ? baseline
1130
+ : { inputTokens: 0, outputTokens: 0, costUsd: null };
1131
+ const baseCost = typeof base.costUsd === 'number' ? base.costUsd : null;
1132
+ const turnCost = typeof turnUsage.costUsd === 'number' ? turnUsage.costUsd : null;
1133
+ return {
1134
+ inputTokens: (base.inputTokens || 0) + (turnUsage.inputTokens || 0),
1135
+ outputTokens: (base.outputTokens || 0) + (turnUsage.outputTokens || 0),
1136
+ // 引擎从未上报过费用(如 Codex)时保持 null,不要伪装成"花费为 0"
1137
+ costUsd: baseCost === null && turnCost === null ? null : (baseCost || 0) + (turnCost || 0)
1138
+ };
1139
+ }
1140
+
1042
1141
  function getAgentRuntimeMeta(template) {
1043
1142
  const normalizedTemplate = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
1044
1143
  const agentProgram = resolveAgentProgram(normalizedTemplate);
@@ -1762,13 +1861,15 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
1762
1861
  }
1763
1862
 
1764
1863
  function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, meta, result) {
1864
+ const turnUsage = extractTurnUsage(agentMeta.agentProgram, result.stdout);
1765
1865
  appendWebSessionMessage(state.webHistoryDir, sessionRef, 'assistant', result.output, {
1766
1866
  exitCode: result.exitCode,
1767
1867
  mode: 'agent',
1768
1868
  contextMode: meta.contextMode,
1769
1869
  resumeAttempted: meta.resumeAttempted,
1770
1870
  resumeSucceeded: meta.resumeSucceeded,
1771
- interrupted: result.interrupted === true
1871
+ interrupted: result.interrupted === true,
1872
+ ...(turnUsage ? { usage: turnUsage } : {})
1772
1873
  });
1773
1874
  const patch = {
1774
1875
  lastResumeAt: meta.resumeAttempted ? new Date().toISOString() : (agentSession.lastResumeAt || null),
@@ -1779,6 +1880,9 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
1779
1880
  if (engineSessionId) {
1780
1881
  patch.engineSessionId = engineSessionId;
1781
1882
  }
1883
+ if (turnUsage) {
1884
+ patch.usageTotal = accumulateUsageTotal(agentSession.usageTotal, turnUsage);
1885
+ }
1782
1886
  patchWebAgentSessionState(state.webHistoryDir, sessionRef, patch);
1783
1887
  }
1784
1888
 
@@ -3626,6 +3730,7 @@ function buildSessionDetail(ctx, state, containerMap, name) {
3626
3730
  lastResumeAt: agentSession.lastResumeAt || null,
3627
3731
  lastResumeOk: typeof agentSession.lastResumeOk === 'boolean' ? agentSession.lastResumeOk : null,
3628
3732
  lastResumeError: agentSession.lastResumeError || '',
3733
+ usageTotal: agentSession.usageTotal || null,
3629
3734
  applied
3630
3735
  };
3631
3736
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "6.2.8",
3
+ "version": "6.2.9",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.18",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",