Files
UHD_Media_Tool/packages/agent/src/prompt/layers/environment.ts
T

114 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
accountName,
downloadStatusName,
healthName,
humanNow,
humanRelative,
kindName,
subscriptionStatusName,
} from "../format.ts";
import type { EnvironmentFacts, PromptSection } from "../types.ts";
// 第 2 层:环境事实。会话开始时服务端从业务表读出的摘要,让模型知道"有什么"再去查"怎么样":
// 反例(docs/design/agent-report-sample-bad.md)里模型只看了站点列表,就汇报"系统里没有下载器"。
// 只写名称、类型、状态和计数,不写地址、ID 与凭证;数字以会话开始时为准,细节仍以工具结果为准。
const MAX_NAMED = 6;
function names<T>(items: readonly T[], render: (x: T) => string): string {
const shown = items.slice(0, MAX_NAMED).map(render);
const more = items.length - shown.length;
return more > 0 ? `${shown.join(";")};另有 ${more} 个` : shown.join(";");
}
/** 计数表 → "追更中 3、已取消 2";全 0 返回空串 */
function counts(m: Record<string, number>, label: (k: string) => string): string {
return Object.entries(m)
.filter(([, n]) => n > 0)
.sort((a, b) => b[1] - a[1])
.map(([k, n]) => `${label(k)} ${n}`)
.join("、");
}
const total = (m: Record<string, number>) => Object.values(m).reduce((a, b) => a + b, 0);
export function renderEnvironment(f: EnvironmentFacts): string[] {
const now = new Date(f.now);
const tz = f.timeZone;
const rel = (iso: string | null) => humanRelative(iso, now, tz);
const lines: string[] = [`现在:${humanNow(now, tz)}。`];
const dl = f.downloaders.filter((d) => d.enabled);
if (dl.length === 0)
lines.push(
f.downloaders.length > 0
? `下载器:${f.downloaders.length} 个都已停用。`
: "下载器:还没有接入(设置 → 下载器)。",
);
else
lines.push(
`下载器 ${dl.length} 个:${names(dl, (d) => {
const seen = rel(d.lastSeenAt);
const t = d.tasks;
const tasks =
t.total > 0
? `,本工具管理 ${t.total} 个任务(进行中 ${t.active}、失败 ${t.failed})`
: ",没有本工具管理的任务";
return `「${d.name}」${kindName(d.kind)},${healthName(d.health)}${seen ? `(${seen})` : ""}${tasks}`;
})}。`,
);
const ms = f.mediaServers.filter((m) => m.enabled);
lines.push(
ms.length === 0
? "媒体服务器:还没有接入。"
: `媒体服务器 ${ms.length} 个:${names(ms, (m) => {
const seen = rel(m.lastSeenAt);
return `「${m.name}」${kindName(m.kind)},${healthName(m.health)}${seen ? `(${seen})` : ""}`;
})}。`,
);
const sites = f.sites.filter((s) => s.enabled);
lines.push(
sites.length === 0
? "站点:还没有启用的站点。"
: `站点 ${sites.length} 个:${names(sites, (s) => {
const at = rel(s.lastHealthAt);
const health =
s.health === "unknown" ? "还没检查过" : `上次检查${healthName(s.health)}${at ? `(${at})` : ""}`;
return `「${s.name}」${kindName(s.kind)},${health}${s.account ? `,${accountName(s.account)}` : ""}`;
})}。`,
);
const roots = f.libraryRoots.filter((r) => r.enabled);
lines.push(
roots.length === 0
? "媒体库目录:还没有登记。"
: `媒体库目录 ${roots.length} 个:${names(roots, (r) => `「${r.name}」${healthName(r.health)}`)}。`,
);
const subs = total(f.subscriptions);
lines.push(subs === 0 ? "订阅:没有。" : `订阅 ${subs} 个:${counts(f.subscriptions, subscriptionStatusName)}。`);
const dls = total(f.downloads);
lines.push(
dls === 0
? "下载任务(本工具管理):没有。"
: `下载任务(本工具管理)${dls} 个:${counts(f.downloads, downloadStatusName)}。`,
);
lines.push(f.openDecisions === 0 ? "待处理事项:没有。" : `待处理事项:${f.openDecisions} 条未关闭。`);
if (f.webSearch !== undefined) lines.push(f.webSearch ? "网页搜索:已开启。" : "网页搜索:未开启。");
return lines;
}
export function environmentLayer(f: EnvironmentFacts | null | undefined): PromptSection {
if (!f) return { layer: "environment", title: "当前环境", lines: [] };
return {
layer: "environment",
title: "当前环境",
lines: [
"会话开始时的快照,只有名称、状态和计数;要细节或最新状态用下面的能力查。这里写了「还没有接入」的就是真的没有,不用再向用户确认。",
...renderEnvironment(f).map((l) => `- ${l}`),
],
};
}