mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-10 14:58:46 +00:00
* feat(subagents): show runtime metadata on task cards * fix(subagents): stop task-card render loop and dedupe model fetches Address code review on the runtime-metadata cards: - P1 render loop: the terminal ToolMessage is re-parsed on every MessageList render and always carries modelName/usage, so the presence-based setTasks condition fired a fresh state object each render -> "Maximum update depth exceeded". computeNextSubtask now returns a value-compared `changed` flag and a pure subtaskNotification() routes terminal transitions through the deferred after-render path while skipping no-op re-parses. - Per-card useModels refetch: add staleTime: Infinity to the ["models"] query so every subtask card shares one /api/models fetch instead of refetching on each mount. * make format * refactor(subagents): dedupe token-usage validators + tidy event narrowing Address PR review follow-ups: - DRY: extract one shared token-usage validator per side. Backend status_contract.normalize_token_usage() now backs both the terminal ToolMessage metadata and the subagent.step/.end run events (step_events.py), and frontend messages/usage.normalizeTokenUsage() backs both the live task_running event (lifecycle.ts) and the terminal ToolMessage metadata (subtask-result.ts). Prevents the input/output/ total_tokens validation from drifting across the four former copies. - Nit: onCustomEvent narrows event.type once instead of re-checking the object shape per branch; the redundant task_started early-return (already validated by taskEventToSubtaskUpdate) is dropped.
66 lines
1.6 KiB
TypeScript
66 lines
1.6 KiB
TypeScript
import { normalizeTokenUsage } from "../messages/usage";
|
|
|
|
import type { Subtask } from "./types";
|
|
|
|
type TaskStartedEvent = {
|
|
type: "task_started";
|
|
task_id: string;
|
|
model_name?: unknown;
|
|
};
|
|
|
|
type TaskRunningEvent = {
|
|
type: "task_running";
|
|
task_id: string;
|
|
model_name?: unknown;
|
|
usage?: unknown;
|
|
};
|
|
|
|
/** Convert an additive task lifecycle event into a task-state update. */
|
|
export function taskEventToSubtaskUpdate(
|
|
event: unknown,
|
|
): (Partial<Subtask> & { id: string }) | null {
|
|
if (!isRecord(event)) {
|
|
return null;
|
|
}
|
|
|
|
const taskId = event.task_id;
|
|
if (typeof taskId !== "string" || !taskId.trim()) {
|
|
return null;
|
|
}
|
|
|
|
if (event.type === "task_started") {
|
|
const started = event as TaskStartedEvent;
|
|
const modelName =
|
|
typeof started.model_name === "string" && started.model_name.trim()
|
|
? started.model_name.trim()
|
|
: undefined;
|
|
return {
|
|
id: taskId,
|
|
...(modelName ? { modelName } : {}),
|
|
};
|
|
}
|
|
|
|
if (event.type === "task_running") {
|
|
const running = event as TaskRunningEvent;
|
|
const usage = normalizeTokenUsage(running.usage);
|
|
const modelName = normalizeModelName(running.model_name);
|
|
return usage || modelName
|
|
? {
|
|
id: taskId,
|
|
...(modelName ? { modelName } : {}),
|
|
...(usage ? { usage } : {}),
|
|
}
|
|
: null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
|
|
function normalizeModelName(value: unknown): string | undefined {
|
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
}
|