Skip to content

Commit f4444ff

Browse files
committed
fix(orchestration): address PR review feedback for parser and sidebar
1 parent bdc6cc8 commit f4444ff

File tree

2 files changed

+164
-58
lines changed

2 files changed

+164
-58
lines changed

src/components/sessions/session-sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function SessionSidebar({ sessions, isLoading }: SessionSidebarProps) {
5656
);
5757
}
5858
// Sort by most recent
59-
return list.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
59+
return [...list].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
6060
}, [sessions, search]);
6161

6262
return (

src/lib/normalize-content.ts

Lines changed: 163 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,183 @@ export interface ApprovalPayload {
4040
resolution?: "approved" | "rejected";
4141
}
4242

43+
function isRecord(value: unknown): value is Record<string, unknown> {
44+
return typeof value === "object" && value !== null && !Array.isArray(value);
45+
}
46+
47+
function parseToolArguments(value: unknown): Record<string, unknown> {
48+
if (isRecord(value)) return value;
49+
if (typeof value !== "string") return {};
50+
51+
try {
52+
const parsed = JSON.parse(value);
53+
return isRecord(parsed) ? parsed : { raw: parsed };
54+
} catch {
55+
return { raw: value };
56+
}
57+
}
58+
59+
function parseDelegationStatus(value: unknown): DelegationPayload["status"] {
60+
const status = typeof value === "string" ? value.toLowerCase() : "";
61+
if (status.includes("complete") || status.includes("done") || status.includes("finish")) {
62+
return "completed";
63+
}
64+
if (status.includes("spin") || status.includes("pending") || status.includes("start")) {
65+
return "spinning_up";
66+
}
67+
return "active";
68+
}
69+
70+
function parseApprovalResolution(value: unknown): ApprovalPayload["resolution"] {
71+
const normalized = typeof value === "string" ? value.toLowerCase() : "";
72+
if (["approved", "approve", "accepted", "allow", "allowed", "ok"].includes(normalized)) {
73+
return "approved";
74+
}
75+
if (["rejected", "reject", "denied", "deny", "blocked"].includes(normalized)) {
76+
return "rejected";
77+
}
78+
return undefined;
79+
}
80+
4381
/**
4482
* Parses raw message content into an array of structured ParsedEvents.
4583
*/
4684
export function parseEvents(content: unknown, fallbackId: string = Math.random().toString(36).slice(2)): ParsedEvent[] {
4785
const events: ParsedEvent[] = [];
86+
let textEventCounter = 0;
87+
88+
const parseObjectEvent = (obj: Record<string, unknown>, idx: number) => {
89+
if (obj.type === "tool_use") {
90+
events.push({
91+
id: (obj.id as string) || `${fallbackId}-tool-${idx}`,
92+
type: "tool_call",
93+
payload: {
94+
toolName: obj.name as string,
95+
arguments: (obj.input as Record<string, unknown>) || {},
96+
} as ToolCallPayload
97+
});
98+
return;
99+
}
100+
101+
if (obj.type === "tool_result") {
102+
events.push({
103+
id: (obj.tool_use_id as string) || `${fallbackId}-result-${idx}`,
104+
type: "tool_result",
105+
payload: {
106+
result: obj.content || obj,
107+
} as ToolCallPayload
108+
});
109+
return;
110+
}
111+
112+
if ("text" in obj) {
113+
parseTextWithThoughts(String(obj.text));
114+
return;
115+
}
116+
117+
if ("tool_calls" in obj && Array.isArray(obj.tool_calls)) {
118+
(obj.tool_calls as Array<Record<string, unknown>>).forEach((tc: Record<string, unknown>, tcIdx: number) => {
119+
const fn = tc.function as Record<string, unknown> | undefined;
120+
events.push({
121+
id: (tc.id as string) || `${fallbackId}-tool-${idx}-${tcIdx}`,
122+
type: "tool_call",
123+
payload: {
124+
toolName: (fn?.name as string) || "unknown",
125+
arguments: parseToolArguments(fn?.arguments)
126+
} as ToolCallPayload
127+
});
128+
});
129+
return;
130+
}
131+
132+
const maybeDelegationType = typeof obj.type === "string"
133+
&& ["delegation", "delegate", "sub_agent_delegation", "agent_delegation"].includes(obj.type.toLowerCase());
134+
const hasDelegationFields = "agentId" in obj || "agent_id" in obj || "roleDescription" in obj || "role" in obj;
135+
136+
if (maybeDelegationType || hasDelegationFields) {
137+
const nestedSource = Array.isArray(obj.nestedEvents)
138+
? obj.nestedEvents
139+
: Array.isArray(obj.events)
140+
? obj.events
141+
: undefined;
142+
143+
const nestedEvents = nestedSource ? parseEvents(nestedSource, `${fallbackId}-delegation-${idx}`) : undefined;
144+
const delegationPayload: DelegationPayload = {
145+
agentId: String(obj.agentId ?? obj.agent_id ?? obj.agent ?? "unknown-agent"),
146+
roleDescription: String(obj.roleDescription ?? obj.role_description ?? obj.role ?? ""),
147+
status: parseDelegationStatus(obj.status),
148+
handoffSummary: obj.handoffSummary != null
149+
? String(obj.handoffSummary)
150+
: obj.handoff_summary != null
151+
? String(obj.handoff_summary)
152+
: obj.summary != null
153+
? String(obj.summary)
154+
: undefined,
155+
nestedEvents,
156+
};
157+
158+
events.push({
159+
id: (obj.id as string) || `${fallbackId}-delegation-${idx}`,
160+
type: "delegation",
161+
payload: delegationPayload
162+
});
163+
return;
164+
}
165+
166+
const maybeApprovalType = typeof obj.type === "string"
167+
&& [
168+
"approval",
169+
"approval_req",
170+
"approval_request",
171+
"exec.approval.requested",
172+
"exec.approval.resolved"
173+
].includes(obj.type.toLowerCase());
174+
const hasApprovalFields = "actionDescription" in obj || "action" in obj || "resolution" in obj || "approval" in obj;
175+
176+
if (maybeApprovalType || hasApprovalFields) {
177+
const resolution = parseApprovalResolution(obj.resolution ?? obj.status ?? obj.decision ?? obj.result);
178+
const approvalPayload: ApprovalPayload = {
179+
actionDescription: String(
180+
obj.actionDescription
181+
?? obj.action_description
182+
?? obj.action
183+
?? obj.description
184+
?? "Approval required"
185+
),
186+
resolution,
187+
};
188+
189+
events.push({
190+
id: (obj.id as string) || `${fallbackId}-approval-${idx}`,
191+
type: "approval",
192+
payload: approvalPayload
193+
});
194+
return;
195+
}
196+
197+
events.push({
198+
id: `${fallbackId}-unknown-${idx}`,
199+
type: "unknown",
200+
payload: obj
201+
});
202+
};
48203

49204
const parseTextWithThoughts = (text: string) => {
50205
const parts = text.split(/(<think>[\s\S]*?<\/think>)/g);
51-
for (let i = 0; i < parts.length; i++) {
52-
const part = parts[i];
206+
for (const part of parts) {
53207
if (!part) continue;
208+
const currentIndex = textEventCounter++;
54209
if (part.startsWith("<think>") && part.endsWith("</think>")) {
55210
// Push the thought
56211
const thoughtText = part.slice(7, -8).trim();
57212
events.push({
58-
id: `${fallbackId}-thought-${i}`,
213+
id: `${fallbackId}-thought-${currentIndex}`,
59214
type: "thought",
60215
payload: { rawText: thoughtText } as ThoughtPayload
61216
});
62217
} else if (part.trim()) {
63218
events.push({
64-
id: `${fallbackId}-text-${i}`,
219+
id: `${fallbackId}-text-${currentIndex}`,
65220
type: "text",
66221
payload: part
67222
});
@@ -80,64 +235,15 @@ export function parseEvents(content: unknown, fallbackId: string = Math.random()
80235
content.forEach((block, idx) => {
81236
if (typeof block === "string") {
82237
parseTextWithThoughts(block);
83-
} else if (block && typeof block === "object") {
84-
const obj = block as Record<string, unknown>;
85-
if (obj.type === "tool_use") {
86-
events.push({
87-
id: (obj.id as string) || `${fallbackId}-tool-${idx}`,
88-
type: "tool_call",
89-
payload: {
90-
toolName: obj.name as string,
91-
arguments: (obj.input as Record<string, unknown>) || {},
92-
} as ToolCallPayload
93-
});
94-
} else if (obj.type === "tool_result") {
95-
events.push({
96-
id: (obj.tool_use_id as string) || `${fallbackId}-result-${idx}`,
97-
type: "tool_result",
98-
payload: {
99-
result: obj.content || obj,
100-
} as ToolCallPayload
101-
});
102-
} else if ("text" in obj) {
103-
parseTextWithThoughts(String(obj.text));
104-
} else if ("tool_calls" in obj && Array.isArray(obj.tool_calls)) {
105-
(obj.tool_calls as Array<Record<string, unknown>>).forEach((tc: Record<string, unknown>, tcIdx: number) => {
106-
const fn = tc.function as Record<string, unknown> | undefined;
107-
events.push({
108-
id: (tc.id as string) || `${fallbackId}-tool-${idx}-${tcIdx}`,
109-
type: "tool_call",
110-
payload: {
111-
toolName: (fn?.name as string) || "unknown",
112-
arguments: typeof fn?.arguments === 'string'
113-
? JSON.parse(fn.arguments)
114-
: (fn?.arguments as Record<string, unknown>) || {}
115-
} as ToolCallPayload
116-
});
117-
});
118-
} else {
119-
events.push({
120-
id: `${fallbackId}-unknown-${idx}`,
121-
type: "unknown",
122-
payload: block
123-
});
124-
}
238+
} else if (isRecord(block)) {
239+
parseObjectEvent(block, idx);
125240
}
126241
});
127242
return events;
128243
}
129244

130-
if (typeof content === "object") {
131-
const obj = content as Record<string, unknown>;
132-
if ("text" in obj) {
133-
parseTextWithThoughts(String(obj.text));
134-
return events;
135-
}
136-
events.push({
137-
id: `${fallbackId}-unknown`,
138-
type: "unknown",
139-
payload: content
140-
});
245+
if (isRecord(content)) {
246+
parseObjectEvent(content, 0);
141247
return events;
142248
}
143249

0 commit comments

Comments
 (0)