refactor(agent): Pi 会话运行时 - 接入原生 Agent 与 Skills 工具链
This commit is contained in:
1 parent
f9b8c5a43e
commit
387d3562ad
69 files changed
+11460
-207
No files matched your search
@@ -691,7 +691,6 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, se
|
||||
const result = submission.parentRunId ? await sendAgentMessage(submission.parentRunId, request) : await createAgentRun(request);
|
||||
accepted = true;
|
||||
if (currentScope.current === scope) {
|
||||
setContextUsage(emptyAgentContextUsage(result.run.id));
|
||||
setRun(result.run);
|
||||
}
|
||||
await clearCloudAgentPendingSubmission(canvasId, activeConversationId);
|
||||
@@ -1248,7 +1247,7 @@ function AgentContextRing({ view }: { view: AgentContextUsageView }) {
|
||||
getPopupContainer={(trigger) => trigger.closest<HTMLElement>(".canvas-agent-panel") ?? document.body}
|
||||
content={
|
||||
<div className="agent-context-panel" data-phase={view.phase}>
|
||||
<span className="agent-context-eyebrow">下一次请求</span>
|
||||
<span className="agent-context-eyebrow">下一次请求 · 上下文窗口占用</span>
|
||||
<div className="agent-context-panel-head">
|
||||
<strong>{usageHeading}</strong>
|
||||
{view.phase !== "ok" ? <span className={`agent-context-phase is-${view.phase}`}>{phaseLabel}</span> : null}
|
||||
@@ -1268,7 +1267,7 @@ function AgentContextRing({ view }: { view: AgentContextUsageView }) {
|
||||
)}
|
||||
</div>
|
||||
<div className="agent-context-progress-head">
|
||||
<span>输入预算占用</span>
|
||||
<span>上下文窗口占用</span>
|
||||
<strong>{percent}</strong>
|
||||
</div>
|
||||
<div className="agent-context-progress" role="progressbar" aria-label={`上下文已用 ${percent}`} aria-valuemin={0} aria-valuemax={100} aria-valuenow={view.ratio === undefined ? undefined : Math.round(view.ratio * 100)}>
|
||||
|
||||
@@ -87,15 +87,26 @@ export function mergeAgentCanvasEditor(previous: CanvasProject, incoming: Canvas
|
||||
const changes = <T extends { id: string }>(before: T[], after: T[]): Change<T>[] => {
|
||||
const byId = new Map(before.map((item) => [item.id, item]));
|
||||
const afterIds = new Set(after.map((item) => item.id));
|
||||
return [
|
||||
...after.filter((item) => !equal(item, byId.get(item.id))).map((item) => ({ before: byId.get(item.id) ?? null, after: item })),
|
||||
...before.filter((item) => !afterIds.has(item.id)).map((item) => ({ before: item, after: null })),
|
||||
];
|
||||
return [...after.filter((item) => !equal(item, byId.get(item.id))).map((item) => ({ before: byId.get(item.id) ?? null, after: item })), ...before.filter((item) => !afterIds.has(item.id)).map((item) => ({ before: item, after: null }))];
|
||||
};
|
||||
return applyAgentCanvasPatch({ ...previous, nodes, connections }, {
|
||||
canvasId: previous.id,
|
||||
updatedAt: incoming.updatedAt,
|
||||
nodes: changes(previous.nodes, incoming.nodes),
|
||||
connections: changes(previous.connections, incoming.connections),
|
||||
});
|
||||
const projected = applyAgentCanvasPatch(
|
||||
{ ...previous, nodes, connections },
|
||||
{
|
||||
canvasId: previous.id,
|
||||
updatedAt: incoming.updatedAt,
|
||||
nodes: changes(previous.nodes, incoming.nodes),
|
||||
connections: changes(previous.connections, incoming.connections),
|
||||
},
|
||||
);
|
||||
const merged = { ...projected } as CanvasProject & Record<string, unknown>;
|
||||
const before = previous as CanvasProject & Record<string, unknown>;
|
||||
const after = incoming as CanvasProject & Record<string, unknown>;
|
||||
const editor = { ...previous, nodes, connections } as CanvasProject & Record<string, unknown>;
|
||||
for (const key of new Set([...Object.keys(before), ...Object.keys(after), ...Object.keys(editor)])) {
|
||||
if (["id", "revision", "updatedAt", "remoteContentHash", "viewport", "nodes", "connections"].includes(key)) continue;
|
||||
const value = mergeValue(editor[key], before[key], after[key]);
|
||||
if (value === undefined) delete merged[key];
|
||||
else merged[key] = value;
|
||||
}
|
||||
return { ...merged, revision: incoming.revision, updatedAt: incoming.updatedAt, viewport: editor.viewport } as CanvasProject;
|
||||
}
|
||||
@@ -27,11 +27,11 @@ export type AgentContextBreakdownItem = {
|
||||
|
||||
export type AgentContextUsageView = {
|
||||
phase: AgentContextPhase;
|
||||
/** Share of the input budget, 0–1 when the model window is known. */
|
||||
/** Share of the model context window, 0–1 when the window is known. */
|
||||
ratio?: number;
|
||||
/** Where semantic compaction starts, as a share of the same input budget. */
|
||||
/** Compaction threshold as a share of the model context window. */
|
||||
compactRatio?: number;
|
||||
/** Ring fill. 1 means "at or past the compaction line", not "window is full". */
|
||||
/** Ring fill against the full context window. */
|
||||
ring: number;
|
||||
label: string;
|
||||
detail: string;
|
||||
@@ -74,8 +74,8 @@ export function finiteContextNumber(value: unknown): number | undefined {
|
||||
/** Returns a ratio only when the backend has a trustworthy configured model budget. */
|
||||
export function contextPressureRatio(reading: Record<string, unknown> | null): number | undefined {
|
||||
if (!reading || reading.modelLimitConfigured !== true) return undefined;
|
||||
const usable = finiteContextNumber(reading.usableInputTokens);
|
||||
if (!usable || usable <= 0) return undefined;
|
||||
const contextWindow = finiteContextNumber(reading.contextWindowTokens);
|
||||
if (!contextWindow || contextWindow <= 0) return undefined;
|
||||
const ratio = reading.tokenSource === "provider"
|
||||
? finiteContextNumber(reading.projectedPressureRatio)
|
||||
: finiteContextNumber(reading.pressureRatio);
|
||||
@@ -128,15 +128,14 @@ function formatContextTokens(tokens: number | undefined): string {
|
||||
|
||||
/**
|
||||
* One view of the compaction mechanism.
|
||||
* The ring fills against compactAtTokens (85% of the input budget): full means
|
||||
* the next model call is about to pause and compact, not that the window is 100% used.
|
||||
* All visual scales use the model context window; the marker shows the shared 80% compaction line.
|
||||
*/
|
||||
export function presentAgentContextUsage(usage: AgentContextUsage): AgentContextUsageView {
|
||||
const reading = usage.reading;
|
||||
const inputTokens = contextInputTokens(reading);
|
||||
const usableTokens = finiteContextNumber(reading?.usableInputTokens);
|
||||
const compactAtTokens = finiteContextNumber(reading?.compactAtTokens);
|
||||
const contextWindowTokens = finiteContextNumber(reading?.contextWindowTokens);
|
||||
const usableTokens = contextWindowTokens;
|
||||
const compactAtTokens = finiteContextNumber(reading?.compactAtTokens);
|
||||
const ratio = contextPressureRatio(reading);
|
||||
const compactRatio = usableTokens && compactAtTokens ? Math.min(1, compactAtTokens / usableTokens) : undefined;
|
||||
const tokenSource = reading?.tokenSource === "provider" ? "provider" : reading ? "estimate" : undefined;
|
||||
@@ -168,21 +167,21 @@ export function presentAgentContextUsage(usage: AgentContextUsage): AgentContext
|
||||
}
|
||||
if (!reading) return base;
|
||||
if (usage.readingStale) {
|
||||
return { ...base, phase: "stale", ring: ratio === undefined ? 0 : Math.min(1, ratio / (compactRatio || 0.85)), label: "刚压缩", detail: "上一份读数是压缩前的;下一次模型调用会给出压缩后的占用。" };
|
||||
return { ...base, phase: "stale", ring: ratio === undefined ? 0 : Math.min(1, ratio), label: "刚压缩", detail: "上一份读数是压缩前的;下一次模型调用会给出压缩后的占用。" };
|
||||
}
|
||||
if (ratio === undefined || usableTokens === undefined || usableTokens <= 0) {
|
||||
const measured = inputTokens === undefined ? "窗口未知" : `约 ${formatContextTokens(inputTokens)} Token`;
|
||||
return { ...base, phase: "unknown", label: measured, detail: "这个模型没有配置可确认的上下文窗口,不能给出占用百分比;对话过长时仍会按条数和体积压缩。" };
|
||||
}
|
||||
const line = compactRatio && compactRatio > 0 ? compactRatio : 0.85;
|
||||
const ring = Math.max(0, Math.min(1, ratio / line));
|
||||
const line = compactRatio && compactRatio > 0 ? compactRatio : 0.8;
|
||||
const ring = Math.max(0, Math.min(1, ratio));
|
||||
const percent = Math.round(ratio * 100);
|
||||
const source = estimate ? "本地估算" : "模型实测校准";
|
||||
if (ratio >= line) {
|
||||
return { ...base, phase: "compress", ring: 1, label: `${percent}%`, detail: `已到压缩线(输入预算的 ${Math.round(line * 100)}%)。下一次调用前会暂停,把历史收成检查点后再继续。当前 ${formatContextTokens(inputTokens)} / ${formatContextTokens(usableTokens)}(${source})。` };
|
||||
return { ...base, phase: "compress", ring, label: `${percent}%`, detail: `已到压缩线(模型窗口的 ${Math.round(line * 100)}%)。下一次调用前会暂停,把历史收成检查点后再继续。当前 ${formatContextTokens(inputTokens)} / ${formatContextTokens(usableTokens)}(${source})。` };
|
||||
}
|
||||
if (ring >= 0.72) {
|
||||
return { ...base, phase: "watch", ring, label: `${percent}%`, detail: `接近压缩。当前 ${formatContextTokens(inputTokens)} / ${formatContextTokens(usableTokens)}(${source}),到 ${formatContextTokens(compactAtTokens)} 时开始压缩。` };
|
||||
}
|
||||
return { ...base, phase: "ok", ring, label: `${percent}%`, detail: `当前 ${formatContextTokens(inputTokens)} / ${formatContextTokens(usableTokens)}(${source})。满格表示到达压缩线,不是窗口已经 100% 用完。` };
|
||||
return { ...base, phase: "ok", ring, label: `${percent}%`, detail: `当前 ${formatContextTokens(inputTokens)} / ${formatContextTokens(usableTokens)}(${source})。标记线为自动压缩阈值。` };
|
||||
}
|
||||
Reference in new issue
Block a user