Files
tutti/packages/agent/gui/agentSideConversationProjection.ts
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

299 lines
9.6 KiB
TypeScript

import type {
AgentActivityEphemeralConversationEvent,
AgentActivityEphemeralInteractionPatch,
AgentActivityEphemeralStatePatch,
AgentActivityEphemeralTurnPatch,
AgentActivityMessage,
AgentActivityMessageDeltaEvent
} from "@tutti-os/agent-activity-core";
import type { AgentSideUpdatedPayloadV1 } from "@tutti-os/event-protocol";
export function normalizeAgentSideConversationEvent(
event: AgentSideUpdatedPayloadV1
): AgentActivityEphemeralConversationEvent {
const identity = {
workspaceId: event.workspaceId,
agentSessionId: event.sideAgentSessionId,
sourceAgentSessionId: event.sourceAgentSessionId,
sequence: event.sequence
};
switch (event.eventType) {
case "message_delta":
return {
...identity,
change: {
kind: "message_delta",
data: normalizeMessageDelta(event)
}
};
case "message_update":
return {
...identity,
change: {
kind: "message_update",
message: normalizeMessageUpdate(event)
}
};
case "state_patch":
return {
...identity,
change: {
kind: "state_patch",
patch: normalizeStatePatch(event.data)
}
};
case "available_commands_update":
case "config_options_update":
case "session_audit":
return { ...identity, change: { kind: "noop" } };
}
}
function normalizeMessageDelta(
event: Extract<AgentSideUpdatedPayloadV1, { eventType: "message_delta" }>
): AgentActivityMessageDeltaEvent["data"] {
const data = record(event.data);
const content = record(data.content);
const toolOutput = record(data.toolOutput);
const operation = text(content.operation);
const contentValue =
operation === "append_text"
? rawText(content.text) || rawText(content.value)
: cloneValue(content.value ?? content.text ?? "");
const normalizedContent: AgentActivityMessageDeltaEvent["data"]["content"] =
operation === "append_text"
? { operation: "append_text", text: String(contentValue) }
: operation === "set"
? { operation: "set", value: contentValue }
: undefined;
const toolOperation = text(toolOutput.operation);
const normalizedToolOutput: AgentActivityMessageDeltaEvent["data"]["toolOutput"] =
toolOperation === "append_text"
? {
operation: "append_text",
text: rawText(toolOutput.text),
offsetBytes: positiveInteger(toolOutput.offsetBytes) ?? 0
}
: toolOperation === "set"
? { operation: "set", text: rawText(toolOutput.text) }
: undefined;
return {
workspaceId: event.workspaceId,
agentSessionId: event.sideAgentSessionId,
messageId: text(data.messageId),
turnId: text(data.turnId),
role: normalizeRole(data.role),
kind: normalizeMessageKind(data.kind, data.role),
occurredAtUnixMs: positiveInteger(data.occurredAtUnixMs) ?? Date.now(),
...(normalizedContent ? { content: normalizedContent } : {}),
...(normalizedToolOutput ? { toolOutput: normalizedToolOutput } : {}),
...(recordOrNull(data.payloadSet)
? { payloadSet: record(data.payloadSet) }
: {}),
...(Array.isArray(data.payloadUnset)
? {
payloadUnset: data.payloadUnset.filter(
(value): value is string => typeof value === "string"
)
}
: {}),
...(text(data.status) ? { status: text(data.status) } : {}),
...(recordOrNull(data.semantics)
? {
semantics: record(data.semantics)
}
: {}),
...(positiveInteger(data.startedAtUnixMs)
? { startedAtUnixMs: positiveInteger(data.startedAtUnixMs)! }
: {}),
...(positiveInteger(data.completedAtUnixMs)
? { completedAtUnixMs: positiveInteger(data.completedAtUnixMs)! }
: {})
};
}
function normalizeMessageUpdate(
event: Extract<AgentSideUpdatedPayloadV1, { eventType: "message_update" }>
): AgentActivityMessage {
const data = record(event.data);
const payload = record(data.payload);
const contentDelta = rawText(data.contentDelta);
if (contentDelta && typeof payload.text !== "string") {
payload.text = contentDelta;
payload.content = contentDelta;
}
const callId = text(data.callId);
const parentCallId = text(data.parentCallId);
const rootCallId = text(data.rootCallId);
const title = text(data.title);
if (callId && typeof payload.callId !== "string") payload.callId = callId;
if (parentCallId && typeof payload.parentCallId !== "string") {
payload.parentCallId = parentCallId;
}
if (rootCallId && typeof payload.rootCallId !== "string") {
payload.rootCallId = rootCallId;
}
if (title && typeof payload.title !== "string") payload.title = title;
return {
workspaceId: event.workspaceId,
agentSessionId: event.sideAgentSessionId,
messageId: text(data.messageId),
version: positiveInteger(data.seq) ?? event.sequence,
turnId: text(data.turnId) || null,
role: normalizeRole(data.role),
kind: normalizeMessageKind(data.kind, data.role),
...(text(data.status) ? { status: text(data.status) } : {}),
...(recordOrNull(data.semantics)
? {
semantics: record(data.semantics)
}
: {}),
payload,
occurredAtUnixMs: positiveInteger(data.occurredAtUnixMs) ?? Date.now(),
...(positiveInteger(data.startedAtUnixMs)
? { startedAtUnixMs: positiveInteger(data.startedAtUnixMs)! }
: {}),
...(positiveInteger(data.completedAtUnixMs)
? { completedAtUnixMs: positiveInteger(data.completedAtUnixMs)! }
: {})
};
}
function normalizeStatePatch(
dataInput: unknown
): AgentActivityEphemeralStatePatch {
const data = record(dataInput);
const turnLifecycle = record(data.turnLifecycle);
const turn = normalizeTurnPatch(data.turn);
const interaction = normalizeInteractionPatch(data.interactionTransition);
return {
provider: text(data.provider) || null,
cwd: text(data.cwd) || null,
title: text(data.title) || null,
lifecycleStatus: text(data.lifecycleStatus) || null,
currentPhase: text(data.currentPhase) || null,
...(Object.prototype.hasOwnProperty.call(turnLifecycle, "activeTurnId")
? {
activeTurnId:
typeof turnLifecycle.activeTurnId === "string"
? turnLifecycle.activeTurnId
: null
}
: {}),
turn,
interaction,
occurredAtUnixMs: positiveInteger(data.occurredAtUnixMs)
};
}
function normalizeTurnPatch(
value: unknown
): AgentActivityEphemeralTurnPatch | null {
const turn = recordOrNull(value);
const turnId = text(turn?.turnId);
if (!turn || !turnId) return null;
return {
turnId,
...(Object.prototype.hasOwnProperty.call(turn, "activeTurnId")
? {
activeTurnId:
typeof turn.activeTurnId === "string" ? turn.activeTurnId : null
}
: {}),
phase: text(turn.phase) || null,
outcome: text(turn.outcome) || null,
origin: text(turn.origin) || null,
error: normalizeTurnError(turn.error),
fileChanges: recordOrNull(turn.fileChanges),
startedAtUnixMs: positiveInteger(turn.startedAtUnixMs),
completedAtUnixMs: positiveInteger(turn.completedAtUnixMs),
updatedAtUnixMs:
positiveInteger(turn.updatedAtUnixMs) ??
positiveInteger(turn.completedAtUnixMs) ??
positiveInteger(turn.startedAtUnixMs)
};
}
function normalizeInteractionPatch(
value: unknown
): AgentActivityEphemeralInteractionPatch | null {
const interaction = recordOrNull(value);
const requestId = text(interaction?.requestId);
const turnId = text(interaction?.turnId);
if (!interaction || !requestId || !turnId) return null;
const rawKind = text(interaction.kind);
const kind: AgentActivityEphemeralInteractionPatch["kind"] =
rawKind === "question"
? "question"
: rawKind === "plan"
? "plan"
: "approval";
return {
requestId,
turnId,
kind,
status: text(interaction.status) || "pending",
toolName: text(interaction.toolName) || null,
input: recordOrNull(interaction.input),
metadata: recordOrNull(interaction.metadata),
output: recordOrNull(interaction.output),
occurredAtUnixMs: positiveInteger(interaction.occurredAtUnixMs)
};
}
function normalizeTurnError(
value: unknown
): AgentActivityEphemeralTurnPatch["error"] {
const error = recordOrNull(value);
if (!error) return null;
const message = text(error.message) || text(error.detail);
if (!message) return null;
return {
...(text(error.code) ? { code: text(error.code) } : {}),
message
};
}
function normalizeRole(value: unknown): string {
const role = text(value).toLowerCase();
if (role === "assistant_thinking") return "assistant";
return role || "assistant";
}
function normalizeMessageKind(kindValue: unknown, roleValue: unknown): string {
const kind = text(kindValue).toLowerCase();
if (kind) return kind;
return text(roleValue).toLowerCase() === "assistant_thinking"
? "reasoning"
: "text";
}
function record(value: unknown): Record<string, unknown> {
return recordOrNull(value) ?? {};
}
function recordOrNull(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object"
? { ...(value as Record<string, unknown>) }
: null;
}
function text(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}
function rawText(value: unknown): string {
return typeof value === "string" ? value : "";
}
function positiveInteger(value: unknown): number | null {
return typeof value === "number" && Number.isSafeInteger(value) && value > 0
? value
: null;
}
function cloneValue<T>(value: T): T {
if (typeof structuredClone === "function") return structuredClone(value);
return JSON.parse(JSON.stringify(value)) as T;
}