forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathactive-subagent.ts
More file actions
91 lines (83 loc) · 3.04 KB
/
Copy pathactive-subagent.ts
File metadata and controls
91 lines (83 loc) · 3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import { Delegation, SubAgentName } from "./delegation-log";
// Inspect the live message stream to find the most recent supervisor
// tool call to a sub-agent that has not yet produced a ToolMessage
// reply. Falls back to `null` when there is no in-flight delegation.
//
// This is intentionally defensive — the v2 message shape can vary
// across runtime versions, so we structurally probe instead of
// relying on a specific TS type.
type RawToolCall = {
id?: string | null;
function?: { name?: string | null; arguments?: string | null } | null;
};
type RawMessage = {
role?: string;
toolCalls?: RawToolCall[] | null;
toolCallId?: string | null;
content?: unknown;
};
export function inferActiveSubAgent(
delegations: Delegation[],
messages: unknown,
): { subAgent: SubAgentName; task: string } | null {
const msgs: RawMessage[] = Array.isArray(messages)
? (messages as RawMessage[])
: [];
// Collect every tool_call_id that already has a matching tool reply.
const repliedIds = new Set<string>();
for (const m of msgs) {
if (m.role === "tool" && typeof m.toolCallId === "string") {
repliedIds.add(m.toolCallId);
}
}
const SUB_AGENT_NAMES: ReadonlySet<SubAgentName> = new Set<SubAgentName>([
"research_agent",
"writing_agent",
"critique_agent",
]);
// Walk newest → oldest and return the most recent unanswered call.
for (let i = msgs.length - 1; i >= 0; i--) {
const m = msgs[i];
if (!m || m.role !== "assistant" || !Array.isArray(m.toolCalls)) continue;
for (let j = m.toolCalls.length - 1; j >= 0; j--) {
const call = m.toolCalls[j];
const name = call?.function?.name;
if (typeof name !== "string") continue;
if (!SUB_AGENT_NAMES.has(name as SubAgentName)) continue;
if (typeof call?.id === "string" && repliedIds.has(call.id)) continue;
const task = extractTask(call?.function?.arguments);
return { subAgent: name as SubAgentName, task };
}
}
// Fall back: if delegations is non-empty but the supervisor is still
// running, surface the most recent delegation as a soft signal.
if (delegations.length > 0) {
const last = delegations[delegations.length - 1];
if (last && last.status !== "completed") {
return { subAgent: last.sub_agent, task: last.task };
}
}
return null;
}
function extractTask(rawArgs: string | null | undefined): string {
if (!rawArgs) return "(preparing task…)";
// Streaming tool args may be a partial JSON string. Try a strict
// parse first, then fall back to a regex sniff for `"task": "..."`.
try {
const parsed = JSON.parse(rawArgs);
if (parsed && typeof parsed === "object" && "task" in parsed) {
const t = (parsed as { task?: unknown }).task;
if (typeof t === "string" && t.trim().length > 0) return t;
}
} catch {
const match = rawArgs.match(/"task"\s*:\s*"((?:[^"\\]|\\.)*)/);
if (match && match[1].length > 0) {
try {
return JSON.parse(`"${match[1]}"`);
} catch {
return match[1];
}
}
}
return "(preparing task…)";
}