Files
UHD_Media_Tool/apps/server/test/agent/spend-no-budget.test.ts
T
rootandClaude Opus 5.5 414ea7c890 feat(智能体): 不设硬性预算上限,花费改为可见
- 去掉每天次数、每天花费、单次花费、单次 token 四个上限的拦截;设置项登记为弃用,旧值兼容读取、不生效
- 保留单次工具调用次数与时长两个防跑飞限制,缺省调宽(120 次 / 1800 秒,后台调查 20 次)
- 新增 GET /agent/usage:今天与最近 7 天按会话类型(对话 / 巡检 / 后台调查 / 整理识别 / 子会话)的次数、token 与费用
- 设置 agent.autoIdentifyOrganize(整理识别不到时叫智能体识别,缺省开)一并加在智能体设置里
- runner 里加整理识别会话的能力白名单与调查会话去重键解析(investigationDecisionId)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-06 16:47:18 +08:00

111 lines
5.0 KiB
TypeScript

import { DEFAULT_SETTINGS, DEPRECATED_SETTINGS_FIELDS, type GlobalSettings } from "@umt/contracts";
import type { PiUpstreamRow } from "@umt/db";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { createAgentModule } from "../../src/modules/agent/index.ts";
import { agentSettings } from "../../src/modules/agent/runner.ts";
import { ORGANIZE_IDENTIFY_ACTOR } from "../../src/modules/agent/usage.ts";
import { createHarness, type Harness, pgAvailable } from "../auth-settings/harness.ts";
import { DEV_KEY, json } from "../helpers.ts";
// 智能体不设硬性预算上限(2026-10-06):每天次数 / 每天花费 / 单次花费 / 单次 token 四个上限不再拦截(旧值兼容读取、不生效),
// 只保留单次工具调用次数与单次最长时长两个防跑飞限制(缺省调宽);花费改为可见:/agent/usage 按会话类型统计今天与最近 7 天。
const upstream = { row: { provider: "fake", default_model: null, thinking_level: "off" } as unknown as PiUpstreamRow };
describe("预算上限移除,防跑飞限制保留", () => {
it("旧设置里的 token / 费用上限不进运行参数;工具调用次数与时长照用", () => {
const legacy = {
...DEFAULT_SETTINGS,
agent: { ...DEFAULT_SETTINGS.agent, maxTokensPerRun: 1000, maxCostUsdPerRun: 0.01 },
budget: { ...DEFAULT_SETTINGS.budget, agentMaxRunsPerDay: 0, agentMaxCostUsdPerDay: 0 },
} as GlobalSettings;
// biome-ignore lint/suspicious/noExplicitAny: 只取用到的上游字段
const s = agentSettings(legacy, {}, true, upstream as any);
expect(s).not.toHaveProperty("costBudget");
expect(s.maxToolCalls).toBe(DEFAULT_SETTINGS.agent.maxToolCalls);
expect(s.maxDurationMs).toBe(DEFAULT_SETTINGS.agent.maxDurationSec * 1000);
// 调用方显式划拨(子会话、巡检预算)仍然生效
// biome-ignore lint/suspicious/noExplicitAny: 同上
const sub = agentSettings(legacy, { maxToolCalls: 3, maxCostUsd: 0.5 }, true, upstream as any);
expect(sub.maxToolCalls).toBe(3);
expect(sub.costBudget).toEqual({ maxCostUsd: 0.5 });
});
it("缺省调宽;四个上限登记为弃用字段", () => {
expect(DEFAULT_SETTINGS.agent.maxToolCalls).toBeGreaterThanOrEqual(100);
expect(DEFAULT_SETTINGS.agent.maxDurationSec).toBeGreaterThanOrEqual(1800);
expect(DEFAULT_SETTINGS.agent.investigateMaxToolCalls).toBeGreaterThanOrEqual(20);
expect(DEFAULT_SETTINGS.agent.autoIdentifyOrganize).toBe(true);
expect(DEFAULT_SETTINGS.budget).not.toHaveProperty("agentMaxRunsPerDay");
expect(DEPRECATED_SETTINGS_FIELDS).toEqual(
expect.arrayContaining([
"budget.agentMaxRunsPerDay",
"budget.agentMaxCostUsdPerDay",
"agent.maxTokensPerRun",
"agent.maxCostUsdPerRun",
]),
);
});
});
const hasPg = await pgAvailable();
describe.skipIf(!hasPg)("花费统计 /agent/usage(真实 PG)", () => {
let h: Harness;
beforeAll(async () => {
h = await createHarness({ extraModules: [createAgentModule()] });
}, 120_000);
afterAll(async () => {
await h?.close();
});
it("今天与最近 7 天按会话类型统计次数、token 与费用;超过 7 天的不算", async () => {
const day = 86_400_000;
const now = Date.now();
const run = async (
trigger: string,
at: number,
usage: { totalTokens: number; costUsd: number },
extra: Record<string, unknown> = {},
) =>
(
await h.db
.insertInto("agent_run")
.values({
trigger,
goal: "g",
status: "succeeded",
mode: "execute",
usage: JSON.stringify(usage),
started_at: new Date(at),
created_at: new Date(at),
...extra,
} as never)
.returning("id")
.executeTakeFirstOrThrow()
).id;
const chat = await run("user", now, { totalTokens: 1000, costUsd: 0.1 });
await run("subagent", now, { totalTokens: 500, costUsd: 0.05 }, { parent_run_id: chat });
await run("event", now, { totalTokens: 200, costUsd: 0.02 }, { created_by: ORGANIZE_IDENTIFY_ACTOR });
await run("event", now - 3 * day, { totalTokens: 300, costUsd: 0.03 });
await run("schedule", now - 3 * day, { totalTokens: 400, costUsd: 0.04 });
await run("user", now - 10 * day, { totalTokens: 9999, costUsd: 9 });
const res = await h.app.request("/api/v1/agent/usage", { headers: { authorization: `Bearer ${DEV_KEY}` } });
expect(res.status).toBe(200);
const { today, last7d } = (await json(res)).data;
expect(today.runs).toBe(3);
expect(today.tokens).toBe(1700);
expect(today.costUsd).toBeCloseTo(0.17);
expect(today.byKind.map((k: { kind: string }) => k.kind)).toEqual(["chat", "organize_identify", "subagent"]);
expect(last7d.runs).toBe(5);
expect(last7d.costUsd).toBeCloseTo(0.24);
expect(last7d.byKind).toEqual(
expect.arrayContaining([
{ kind: "patrol", runs: 1, tokens: 400, costUsd: 0.04 },
{ kind: "investigate", runs: 1, tokens: 300, costUsd: 0.03 },
]),
);
});
});