@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.
- package/lib/web/frontend/app.js +7 -0
- package/lib/web/server.js +108 -3
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -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
|
}
|