@langchain/langgraph-sdk 1.9.2 → 1.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/stream/index.cjs +103 -14
- package/dist/client/stream/index.cjs.map +1 -1
- package/dist/client/stream/index.d.cts +21 -1
- package/dist/client/stream/index.d.cts.map +1 -1
- package/dist/client/stream/index.d.ts +21 -1
- package/dist/client/stream/index.d.ts.map +1 -1
- package/dist/client/stream/index.js +103 -14
- package/dist/client/stream/index.js.map +1 -1
- package/dist/client/stream/transport.d.cts +12 -0
- package/dist/client/stream/transport.d.cts.map +1 -1
- package/dist/client/stream/transport.d.ts +12 -0
- package/dist/client/stream/transport.d.ts.map +1 -1
- package/dist/stream/controller.cjs +163 -13
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +163 -13
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/discovery/subagents.cjs +31 -0
- package/dist/stream/discovery/subagents.cjs.map +1 -1
- package/dist/stream/discovery/subagents.d.cts.map +1 -1
- package/dist/stream/discovery/subagents.d.ts.map +1 -1
- package/dist/stream/discovery/subagents.js +31 -0
- package/dist/stream/discovery/subagents.js.map +1 -1
- package/dist/stream/projections/runtime.cjs +3 -2
- package/dist/stream/projections/runtime.cjs.map +1 -1
- package/dist/stream/projections/runtime.js +3 -2
- package/dist/stream/projections/runtime.js.map +1 -1
- package/dist/stream/root-message-projection.cjs +98 -53
- package/dist/stream/root-message-projection.cjs.map +1 -1
- package/dist/stream/root-message-projection.js +98 -53
- package/dist/stream/root-message-projection.js.map +1 -1
- package/dist/stream/submit-coordinator.cjs +30 -4
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.js +30 -4
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/dist/ui/manager.cjs +3 -2
- package/dist/ui/manager.cjs.map +1 -1
- package/dist/ui/manager.d.cts.map +1 -1
- package/dist/ui/manager.d.ts.map +1 -1
- package/dist/ui/manager.js +3 -2
- package/dist/ui/manager.js.map +1 -1
- package/dist/utils/async_caller.cjs +2 -1
- package/dist/utils/async_caller.cjs.map +1 -1
- package/dist/utils/async_caller.d.cts.map +1 -1
- package/dist/utils/async_caller.d.ts.map +1 -1
- package/dist/utils/async_caller.js +2 -1
- package/dist/utils/async_caller.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagents.cjs","names":["StreamStore","#onToolEvent","#onValuesEvent","#upsertTaskToolCall","#commit","#map","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate","namespaceKey","isConcreteToolNamespace","#observedOwnNamespaces","#taskIdByObservedNamespace","isToolNamespaceSegment","isRootNamespace"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subagents.cjs","names":["StreamStore","#onToolEvent","#onValuesEvent","#upsertTaskToolCall","#commit","#map","#bindNamespaceByTaskInput","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate","namespaceKey","isConcreteToolNamespace","#observedOwnNamespaces","#toolCallIdByTaskInput","#taskIdByObservedNamespace","isToolNamespaceSegment","isRootNamespace"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n // Index from `taskInput` (the `description` arg) to a FIFO queue of\n // pending parent `tool_call_id`s. Bridges the wire's missing link\n // between a deepagents `task` dispatch and its subagent execution\n // namespace — the server seeds the subagent's first HumanMessage\n // with `taskInput` verbatim, so an exact-equality lookup is\n // deterministic. The queue (not a single value) handles the case\n // where the coordinator dispatches N task calls with identical\n // descriptions; pregel preserves dispatch order across executions,\n // so FIFO pop attributes them correctly.\n #toolCallIdByTaskInput = new Map<string, string[]>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n // If a `tools:<id>` namespace's first HumanMessage matches a known\n // taskInput, that's the subagent's execution scope. Record the\n // binding so `#recordObservedWorkNamespace` can promote it.\n this.#bindNamespaceByTaskInput(event.params.namespace, messages);\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n if (input.description != null) {\n const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];\n queue.push(toolCallId);\n this.#toolCallIdByTaskInput.set(input.description, queue);\n }\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Bind a `tools:<id>` namespace to a registered subagent by looking\n * up the first HumanMessage content in the `taskInput` index.\n */\n #bindNamespaceByTaskInput(\n namespace: readonly string[],\n messages: unknown[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n const namespaceKeyed = namespaceKey(namespace);\n if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;\n\n const text = getFirstHumanMessageText(messages);\n if (text == null) return;\n const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();\n if (toolCallId == null) return;\n this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);\n }\n}\n\nfunction getFirstHumanMessageText(messages: unknown[]): string | null {\n for (const message of messages) {\n if (message == null || typeof message !== \"object\") continue;\n const record = message as {\n type?: unknown;\n role?: unknown;\n content?: unknown;\n kwargs?: { type?: unknown; content?: unknown };\n lc_kwargs?: { type?: unknown; content?: unknown };\n };\n const type =\n record.type ??\n record.role ??\n record.kwargs?.type ??\n record.lc_kwargs?.type;\n if (type !== \"human\") continue;\n const content =\n record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;\n return typeof content === \"string\" && content.length > 0 ? content : null;\n }\n return null;\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagents.d.cts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAgBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;
|
|
1
|
+
{"version":3,"file":"subagents.d.cts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAgBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAgBF;EAAZ,IAAA,CAAK,KAAA,EAAO,KAAA;EASR;EAAA,IAAA,QAAA,CAAA,GAAY,WAAA;EAIhB,mBAAA,CAAoB,OAAA,WAAkB,SAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagents.d.ts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAgBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;
|
|
1
|
+
{"version":3,"file":"subagents.d.ts","names":[],"sources":["../../../src/stream/discovery/subagents.ts"],"mappings":";;;;;KA2BY,WAAA,GAAc,WAAA,SAAoB,yBAAA;AAAA,cAgBjC,iBAAA;EAAA;WACF,KAAA,EAAK,WAAA,CAAA,WAAA;EAgBF;EAAZ,IAAA,CAAK,KAAA,EAAO,KAAA;EASR;EAAA,IAAA,QAAA,CAAA,GAAY,WAAA;EAIhB,mBAAA,CAAoB,OAAA,WAAkB,SAAA;AAAA"}
|
|
@@ -6,6 +6,7 @@ var SubagentDiscovery = class {
|
|
|
6
6
|
#map = /* @__PURE__ */ new Map();
|
|
7
7
|
#taskIdByObservedNamespace = /* @__PURE__ */ new Map();
|
|
8
8
|
#observedOwnNamespaces = /* @__PURE__ */ new Set();
|
|
9
|
+
#toolCallIdByTaskInput = /* @__PURE__ */ new Map();
|
|
9
10
|
/** Feed a single root event. Non-discovery events are ignored. */
|
|
10
11
|
push(event) {
|
|
11
12
|
if (event.method === "tools") this.#onToolEvent(event);
|
|
@@ -56,6 +57,7 @@ var SubagentDiscovery = class {
|
|
|
56
57
|
if (data == null || typeof data !== "object" || Array.isArray(data)) return;
|
|
57
58
|
const messages = data.messages;
|
|
58
59
|
if (!Array.isArray(messages)) return;
|
|
60
|
+
this.#bindNamespaceByTaskInput(event.params.namespace, messages);
|
|
59
61
|
let changed = this.#recordObservedWorkNamespace(event.params.namespace);
|
|
60
62
|
for (const message of messages) {
|
|
61
63
|
for (const toolCall of getTaskToolCalls(message)) changed = this.#upsertTaskToolCall(toolCall.id, toolCall.input, event.params.namespace) || changed;
|
|
@@ -106,6 +108,11 @@ var SubagentDiscovery = class {
|
|
|
106
108
|
}
|
|
107
109
|
const { parentId, depth } = lineageFromNamespace(eventNamespace);
|
|
108
110
|
this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);
|
|
111
|
+
if (input.description != null) {
|
|
112
|
+
const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];
|
|
113
|
+
queue.push(toolCallId);
|
|
114
|
+
this.#toolCallIdByTaskInput.set(input.description, queue);
|
|
115
|
+
}
|
|
109
116
|
this.#map.set(toolCallId, {
|
|
110
117
|
id: toolCallId,
|
|
111
118
|
name: input.subagent_type ?? "unknown",
|
|
@@ -139,7 +146,31 @@ var SubagentDiscovery = class {
|
|
|
139
146
|
if (!isConcreteToolNamespace(namespace)) return;
|
|
140
147
|
this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);
|
|
141
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Bind a `tools:<id>` namespace to a registered subagent by looking
|
|
151
|
+
* up the first HumanMessage content in the `taskInput` index.
|
|
152
|
+
*/
|
|
153
|
+
#bindNamespaceByTaskInput(namespace, messages) {
|
|
154
|
+
if (!isConcreteToolNamespace(namespace)) return;
|
|
155
|
+
const namespaceKeyed = namespaceKey(namespace);
|
|
156
|
+
if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;
|
|
157
|
+
const text = getFirstHumanMessageText(messages);
|
|
158
|
+
if (text == null) return;
|
|
159
|
+
const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();
|
|
160
|
+
if (toolCallId == null) return;
|
|
161
|
+
this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);
|
|
162
|
+
}
|
|
142
163
|
};
|
|
164
|
+
function getFirstHumanMessageText(messages) {
|
|
165
|
+
for (const message of messages) {
|
|
166
|
+
if (message == null || typeof message !== "object") continue;
|
|
167
|
+
const record = message;
|
|
168
|
+
if ((record.type ?? record.role ?? record.kwargs?.type ?? record.lc_kwargs?.type) !== "human") continue;
|
|
169
|
+
const content = record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;
|
|
170
|
+
return typeof content === "string" && content.length > 0 ? content : null;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
143
174
|
function shouldPromoteToObservedNamespace(entry) {
|
|
144
175
|
return entry.name === "fanout-worker" || /^Worker worker-\d+/i.test(entry.taskInput ?? "");
|
|
145
176
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagents.js","names":["#onToolEvent","#onValuesEvent","#upsertTaskToolCall","#commit","#map","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate","#observedOwnNamespaces","#taskIdByObservedNamespace"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":";;;AA2CA,IAAa,oBAAb,MAA+B;CAC7B,QAAiB,IAAI,4BAAyB,IAAI,KAAK,CAAC;CACxD,uBAAO,IAAI,KAA8B;CACzC,6CAA6B,IAAI,KAAqB;CACtD,yCAAyB,IAAI,KAAa;;CAG1C,KAAK,OAAoB;AACvB,MAAI,MAAM,WAAW,QACnB,OAAA,YAAkB,MAAoB;WAC7B,MAAM,WAAW,SAC1B,OAAA,cAAoB,MAAqB;;;CAK7C,IAAI,WAAwB;AAC1B,SAAO,KAAK,MAAM,aAAa;;CAGjC,oBAAoB,SAAkB,WAAoC;EACxE,IAAI,UAAU;AACd,OAAK,MAAM,YAAY,iBAAiB,QAAQ,CAC9C,WACE,MAAA,mBAAyB,SAAS,IAAI,SAAS,OAAO,UAAU,IAChE;AAEJ,MAAI,QAAS,OAAA,QAAc;;CAG7B,UAAgB;AAGd,OAAK,MAAM,SACT,IAAI,IACF,CAAC,GAAG,MAAA,IAAU,QAAQ,CAAC,CAAC,KAAK,UAAU,CAAC,MAAM,IAAI,WAAW,MAAM,CAAC,CAAC,CACtE,CACF;;CAGH,aAAa,OAAyB;EACpC,MAAM,OAAO,MAAM,OAAO;EAC1B,MAAM,aAAc,KAAmC;EACvD,MAAM,WAAY,KAAgC;AAElD,MAAI,KAAK,UAAU,kBAAkB,aAAa,QAAQ;GACxD,MAAM,QAAQ,eAAgB,KAA6B,MAAM;AACjE,OAAI,cAAc,KAAM;AACxB,SAAA,mBAAyB,YAAY,OAAO,MAAM,OAAO,UAAU;AACnE,SAAA,QAAc;AACd;;AAGF,MAAI,cAAc,KAAM;EACxB,MAAM,QAAQ,MAAA,IAAU,IAAI,WAAW;AACvC,MAAI,SAAS,KAAM;AAEnB,MAAI,KAAK,UAAU,iBAAiB;AAClC,SAAM,SAAS;AACf,SAAM,SAAU,KAA8B;AAC9C,SAAM,8BAAc,IAAI,MAAM;AAC9B,SAAA,QAAc;AACd;;AAGF,MAAI,KAAK,UAAU,cAAc;AAC/B,SAAM,SAAS;AACf,SAAM,QAAS,KAA8B,WAAW;AACxD,SAAM,8BAAc,IAAI,MAAM;AAC9B,SAAA,QAAc;;;CAIlB,eAAe,OAA0B;EACvC,MAAM,OAAO,MAAM,OAAO;AAC1B,MAAI,QAAQ,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAAE;EACrE,MAAM,WAAY,KAAgC;AAClD,MAAI,CAAC,MAAM,QAAQ,SAAS,CAAE;EAE9B,IAAI,UAAU,MAAA,4BAAkC,MAAM,OAAO,UAAU;AACvE,OAAK,MAAM,WAAW,UAAU;AAC9B,QAAK,MAAM,YAAY,iBAAiB,QAAQ,CAC9C,WACE,MAAA,mBACE,SAAS,IACT,SAAS,OACT,MAAM,OAAO,UACd,IAAI;GAGT,MAAM,aAAa,qBAAqB,QAAQ;AAChD,OAAI,cAAc,KAAM;GACxB,MAAM,WAAW,MAAA,IAAU,IAAI,WAAW;AAC1C,OAAI,YAAY,KAAM;AACtB,YAAS,SAAS;AAClB,YAAS,SAAS;AAClB,YAAS,8BAAc,IAAI,MAAM;AACjC,aAAU;;AAEZ,MAAI,QAAS,OAAA,QAAc;;CAG7B,oBACE,YACA,OACA,gBACS;EACT,MAAM,YAAY,kBAAkB,YAAY,eAAe;EAC/D,MAAM,WAAW,MAAA,IAAU,IAAI,WAAW;AAC1C,MAAI,YAAY,MAAM;GACpB,IAAI,UAAU;AACd,SAAA,6BAAmC,YAAY,eAAe;GAC9D,MAAM,WAAW,MAAM,iBAAiB,SAAS;GACjD,MAAM,gBAAgB,MAAM,eAAe,SAAS;AACpD,OAAI,SAAS,SAAS,UAAU;AAC9B,aAAS,OAAO;AAChB,cAAU;;AAEZ,OAAI,SAAS,cAAc,eAAe;AACxC,aAAS,YAAY;AACrB,cAAU;;GAEZ,MAAM,iBAAiB,aAAa,SAAS,UAAU;GACvD,MAAM,kBAAkB,SAAS;GACjC,MAAM,mBAAmB,aAAa,UAAU;AAChD,OACE,wBAAwB,eAAe,IACvC,mBAAmB,iBACnB;AAMA,QACE,mBAAmB,mBACnB,qBAAqB,mBACrB,MAAA,sBAA4B,IAAI,WAAW,CAE3C,QAAO;AAET,QAAI,mBAAmB,kBAAkB;AACvC,cAAS,YAAY;AACrB,eAAU;;;AAGd,OAAI,SAAS,WAAW,cAAc,SAAS,WAAW;QACpD,SAAS,WAAW,WAAW;AACjC,cAAS,SAAS;AAClB,eAAU;;;AAGd,UAAO;;EAOT,MAAM,EAAE,UAAU,UAAU,qBAAqB,eAAe;AAChE,QAAA,6BAAmC,YAAY,eAAe;AAC9D,QAAA,IAAU,IAAI,YAAY;GACxB,IAAI;GACJ,MAAM,MAAM,iBAAiB;GAC7B;GACA;GACA;GACA,QAAQ;GACR,WAAW,MAAM;GACjB,QAAQ,KAAA;GACR,OAAO,KAAA;GACP,2BAAW,IAAI,MAAM;GACrB,aAAa;GACd,CAAC;AACF,SAAO;;CAGT,6BAA6B,WAAuC;AAClE,MAAI,CAAC,wBAAwB,UAAU,CAAE,QAAO;EAChD,MAAM,OAAO,UAAU,GAAG,GAAG;AAC7B,MAAI,QAAQ,KAAM,QAAO;EACzB,MAAM,iBAAiB,aAAa,UAAU;EAC9C,MAAM,aACJ,MAAA,0BAAgC,IAAI,eAAe,IACnD,KAAK,MAAM,EAAgB;EAC7B,MAAM,WAAW,MAAA,IAAU,IAAI,WAAW;AAC1C,MAAI,YAAY,KAAM,QAAO;AAG7B,MAAI,mBADoB,SAAS,aAE/B,OAAA,sBAA4B,IAAI,WAAW;WAE3C,MAAA,sBAA4B,IAAI,WAAW,IAC1C,CAAC,MAAA,0BAAgC,IAAI,eAAe,IACnD,CAAC,iCAAiC,SAAS,CAE7C,QAAO;AAGT,MAAI,aAAa,SAAS,UAAU,KAAK,eAAgB,QAAO;AAChE,WAAS,YAAY,CAAC,GAAG,UAAU;AACnC,SAAO;;CAGT,8BACE,YACA,WACM;AACN,MAAI,CAAC,wBAAwB,UAAU,CAAE;AACzC,QAAA,0BAAgC,IAAI,aAAa,UAAU,EAAE,WAAW;;;AAI5E,SAAS,iCAAiC,OAAiC;AACzE,QACE,MAAM,SAAS,mBACf,sBAAsB,KAAK,MAAM,aAAa,GAAG;;AAIrD,SAAS,kBACP,YACA,gBACmB;CACnB,MAAM,OAAO,eAAe,GAAG,GAAG;AAClC,KAAI,QAAQ,QAAQ,uBAAuB,KAAK,CAAE,QAAO,CAAC,GAAG,eAAe;AAC5E,QAAO,CAAC,SAAS,aAAa;;AAGhC,SAAS,WAAW,OAAmD;AACrE,QAAO;EACL,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,WAAW,MAAM;EACjB,UAAU,MAAM;EAChB,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,WAAW,MAAM;EACjB,QAAQ,MAAM;EACd,OAAO,MAAM;EACb,WAAW,MAAM;EACjB,aAAa,MAAM;EACpB;;AAGH,SAAS,eAAe,KAGtB;AACA,KAAI,OAAO,KAAM,QAAO,EAAE;AAC1B,KAAI,OAAO,QAAQ,SACjB,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,SAAO;GACL,aACE,OAAO,OAAO,gBAAgB,WAC1B,OAAO,cACP,KAAA;GACN,eACE,OAAO,OAAO,kBAAkB,WAC5B,OAAO,gBACP,KAAA;GACP;SACK;AACN,SAAO,EAAE;;AAGb,KAAI,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI,EAAE;EAClD,MAAM,MAAM;AACZ,SAAO;GACL,aACE,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc,KAAA;GAC1D,eACE,OAAO,IAAI,kBAAkB,WAAW,IAAI,gBAAgB,KAAA;GAC/D;;AAEH,QAAO,EAAE;;AAGX,SAAS,iBAAiB,SAGvB;AACD,KACE,WAAW,QACX,OAAO,YAAY,YACnB,MAAM,QAAQ,QAAQ,CAEtB,QAAO,EAAE;CAEX,MAAM,SAAS;CAKf,MAAM,YACJ,OAAO,cACP,OAAO,QAAQ,cACf,OAAO,WAAW;AACpB,KAAI,CAAC,MAAM,QAAQ,UAAU,CAAE,QAAO,EAAE;CAExC,MAAM,SAGD,EAAE;AACP,MAAK,MAAM,YAAY,WAAW;AAChC,MACE,YAAY,QACZ,OAAO,aAAa,YACpB,MAAM,QAAQ,SAAS,CAEvB;EAEF,MAAM,SAAS;AAKf,MAAI,OAAO,OAAO,OAAO,YAAY,OAAO,SAAS,OAAQ;AAC7D,SAAO,KAAK;GAAE,IAAI,OAAO;GAAI,OAAO,eAAe,OAAO,KAAK;GAAE,CAAC;;AAEpE,QAAO;;AAGT,SAAS,qBAAqB,SAAsC;AAClE,KACE,WAAW,QACX,OAAO,YAAY,YACnB,MAAM,QAAQ,QAAQ,CAEtB;CAEF,MAAM,SAAS;CAKf,MAAM,KACJ,OAAO,gBACP,OAAO,QAAQ,gBACf,OAAO,WAAW;AACpB,QAAO,OAAO,OAAO,YAAY,GAAG,SAAS,IAAI,KAAK,KAAA;;;;;;;;AASxD,SAAS,qBAAqB,WAG5B;AACA,KAAI,gBAAgB,UAAU,CAAE,QAAO;EAAE,UAAU;EAAM,OAAO;EAAG;CACnE,MAAM,OAAO,UAAU,UAAU,SAAS;AAC1C,KAAI,QAAQ,KAAM,QAAO;EAAE,UAAU;EAAM,OAAO;EAAG;CAKrD,MAAM,UADQ,KAAK,MAAM,IAAI,CAAC,QAAQ,SAAS,KAAK,SAAS,EAAE,CACzC,MAAM,EAAE;CAC9B,MAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,OAAO;AAEzC,QAAO;EAAE,WADQ,QAAQ,UAAU,IAAI,QAAQ,QAAQ,SAAS,KAAK,SACtC;EAAM;EAAO"}
|
|
1
|
+
{"version":3,"file":"subagents.js","names":["#onToolEvent","#onValuesEvent","#upsertTaskToolCall","#commit","#map","#bindNamespaceByTaskInput","#recordObservedWorkNamespace","#recordTaskNamespaceCandidate","#observedOwnNamespaces","#toolCallIdByTaskInput","#taskIdByObservedNamespace"],"sources":["../../../src/stream/discovery/subagents.ts"],"sourcesContent":["/**\n * Root-scoped subagent discovery.\n *\n * Populates a `Map<callId, SubagentDiscoverySnapshot>` by watching\n * `task` tool calls on the root subscription. No content channels\n * (subagent messages, tool calls, extensions) are opened here — that\n * layer is driven by selector hooks via the\n * {@link ChannelRegistry}, keyed on `SubagentDiscoverySnapshot.namespace`.\n *\n * Discovery data this runner populates per subagent:\n * - id, name, namespace, parentId, depth\n * - status (`running` | `complete` | `error`)\n * - taskInput / output / error / startedAt / completedAt\n *\n * The runner is fed events by the {@link StreamController}'s root\n * subscription; it does not open subscriptions of its own.\n */\nimport type { Event, ToolsEvent, ValuesEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"../store.js\";\nimport type { SubagentDiscoverySnapshot } from \"../types.js\";\nimport {\n isConcreteToolNamespace,\n isRootNamespace,\n isToolNamespaceSegment,\n namespaceKey,\n} from \"../namespace.js\";\n\nexport type SubagentMap = ReadonlyMap<string, SubagentDiscoverySnapshot>;\n\ninterface MutableSubagent {\n id: string;\n name: string;\n namespace: readonly string[];\n parentId: string | null;\n depth: number;\n status: \"running\" | \"complete\" | \"error\";\n taskInput: string | undefined;\n output: unknown;\n error: string | undefined;\n startedAt: Date;\n completedAt: Date | null;\n}\n\nexport class SubagentDiscovery {\n readonly store = new StreamStore<SubagentMap>(new Map());\n #map = new Map<string, MutableSubagent>();\n #taskIdByObservedNamespace = new Map<string, string>();\n #observedOwnNamespaces = new Set<string>();\n // Index from `taskInput` (the `description` arg) to a FIFO queue of\n // pending parent `tool_call_id`s. Bridges the wire's missing link\n // between a deepagents `task` dispatch and its subagent execution\n // namespace — the server seeds the subagent's first HumanMessage\n // with `taskInput` verbatim, so an exact-equality lookup is\n // deterministic. The queue (not a single value) handles the case\n // where the coordinator dispatches N task calls with identical\n // descriptions; pregel preserves dispatch order across executions,\n // so FIFO pop attributes them correctly.\n #toolCallIdByTaskInput = new Map<string, string[]>();\n\n /** Feed a single root event. Non-discovery events are ignored. */\n push(event: Event): void {\n if (event.method === \"tools\") {\n this.#onToolEvent(event as ToolsEvent);\n } else if (event.method === \"values\") {\n this.#onValuesEvent(event as ValuesEvent);\n }\n }\n\n /** Current snapshot map. */\n get snapshot(): SubagentMap {\n return this.store.getSnapshot();\n }\n\n discoverFromMessage(message: unknown, namespace: readonly string[]): void {\n let changed = false;\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) ||\n changed;\n }\n if (changed) this.#commit();\n }\n\n #commit(): void {\n // Rebuild as a fresh Map so React / useSyncExternalStore sees a\n // new reference on every change.\n this.store.setValue(\n new Map(\n [...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])\n )\n );\n }\n\n #onToolEvent(event: ToolsEvent): void {\n const data = event.params.data;\n const toolCallId = (data as { tool_call_id?: string }).tool_call_id;\n const toolName = (data as { tool_name?: string }).tool_name;\n\n if (data.event === \"tool-started\" && toolName === \"task\") {\n const input = parseTaskInput((data as { input?: unknown }).input);\n if (toolCallId == null) return;\n this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);\n this.#commit();\n return;\n }\n\n if (toolCallId == null) return;\n const entry = this.#map.get(toolCallId);\n if (entry == null) return;\n\n if (data.event === \"tool-finished\") {\n entry.status = \"complete\";\n entry.output = (data as { output?: unknown }).output;\n entry.completedAt = new Date();\n this.#commit();\n return;\n }\n\n if (data.event === \"tool-error\") {\n entry.status = \"error\";\n entry.error = (data as { message?: string }).message ?? \"Subagent failed\";\n entry.completedAt = new Date();\n this.#commit();\n }\n }\n\n #onValuesEvent(event: ValuesEvent): void {\n const data = event.params.data;\n if (data == null || typeof data !== \"object\" || Array.isArray(data)) return;\n const messages = (data as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return;\n\n // If a `tools:<id>` namespace's first HumanMessage matches a known\n // taskInput, that's the subagent's execution scope. Record the\n // binding so `#recordObservedWorkNamespace` can promote it.\n this.#bindNamespaceByTaskInput(event.params.namespace, messages);\n\n let changed = this.#recordObservedWorkNamespace(event.params.namespace);\n for (const message of messages) {\n for (const toolCall of getTaskToolCalls(message)) {\n changed =\n this.#upsertTaskToolCall(\n toolCall.id,\n toolCall.input,\n event.params.namespace\n ) || changed;\n }\n\n const toolCallId = getToolMessageCallId(message);\n if (toolCallId == null) continue;\n const existing = this.#map.get(toolCallId);\n if (existing == null) continue;\n existing.status = \"complete\";\n existing.output = message;\n existing.completedAt = new Date();\n changed = true;\n }\n if (changed) this.#commit();\n }\n\n #upsertTaskToolCall(\n toolCallId: string,\n input: { description?: string; subagent_type?: string },\n eventNamespace: readonly string[]\n ): boolean {\n const namespace = taskWorkNamespace(toolCallId, eventNamespace);\n const existing = this.#map.get(toolCallId);\n if (existing != null) {\n let changed = false;\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n const nextName = input.subagent_type ?? existing.name;\n const nextTaskInput = input.description ?? existing.taskInput;\n if (existing.name !== nextName) {\n existing.name = nextName;\n changed = true;\n }\n if (existing.taskInput !== nextTaskInput) {\n existing.taskInput = nextTaskInput;\n changed = true;\n }\n const namespaceKeyed = namespaceKey(existing.namespace);\n const ownNamespaceKey = `tools:${toolCallId}`;\n const nextNamespaceKey = namespaceKey(namespace);\n if (\n isConcreteToolNamespace(eventNamespace) ||\n namespaceKeyed === ownNamespaceKey\n ) {\n // A wrapper task tool event can arrive under an execution namespace\n // like `tools:<uuid>`, while the subagent's actual message state is\n // under `tools:<tool_call_id>`. Once discovery has observed the own\n // namespace carrying state, do not demote it back to the wrapper\n // namespace.\n if (\n namespaceKeyed === ownNamespaceKey &&\n nextNamespaceKey !== ownNamespaceKey &&\n this.#observedOwnNamespaces.has(toolCallId)\n ) {\n return changed;\n }\n if (namespaceKeyed !== nextNamespaceKey) {\n existing.namespace = namespace;\n changed = true;\n }\n }\n if (existing.status !== \"complete\" && existing.status !== \"error\") {\n if (existing.status !== \"running\") {\n existing.status = \"running\";\n changed = true;\n }\n }\n return changed;\n }\n\n // Prefer the namespace where the task is first observed. Later\n // observations may move it between wrapper execution namespaces\n // and `[\"tools:<toolCallId>\"]`, depending on where the stream proves\n // the worker's scoped message/tool state exists.\n const { parentId, depth } = lineageFromNamespace(eventNamespace);\n this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);\n if (input.description != null) {\n const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];\n queue.push(toolCallId);\n this.#toolCallIdByTaskInput.set(input.description, queue);\n }\n this.#map.set(toolCallId, {\n id: toolCallId,\n name: input.subagent_type ?? \"unknown\",\n namespace,\n parentId,\n depth,\n status: \"running\",\n taskInput: input.description,\n output: undefined,\n error: undefined,\n startedAt: new Date(),\n completedAt: null,\n });\n return true;\n }\n\n #recordObservedWorkNamespace(namespace: readonly string[]): boolean {\n if (!isConcreteToolNamespace(namespace)) return false;\n const last = namespace.at(-1);\n if (last == null) return false;\n const namespaceKeyed = namespaceKey(namespace);\n const toolCallId =\n this.#taskIdByObservedNamespace.get(namespaceKeyed) ??\n last.slice(\"tools:\".length);\n const existing = this.#map.get(toolCallId);\n if (existing == null) return false;\n\n const ownNamespaceKey = `tools:${toolCallId}`;\n if (namespaceKeyed === ownNamespaceKey) {\n this.#observedOwnNamespaces.add(toolCallId);\n } else if (\n this.#observedOwnNamespaces.has(toolCallId) ||\n (!this.#taskIdByObservedNamespace.has(namespaceKeyed) &&\n !shouldPromoteToObservedNamespace(existing))\n ) {\n return false;\n }\n\n if (namespaceKey(existing.namespace) === namespaceKeyed) return false;\n existing.namespace = [...namespace];\n return true;\n }\n\n #recordTaskNamespaceCandidate(\n toolCallId: string,\n namespace: readonly string[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n this.#taskIdByObservedNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Bind a `tools:<id>` namespace to a registered subagent by looking\n * up the first HumanMessage content in the `taskInput` index.\n */\n #bindNamespaceByTaskInput(\n namespace: readonly string[],\n messages: unknown[]\n ): void {\n if (!isConcreteToolNamespace(namespace)) return;\n const namespaceKeyed = namespaceKey(namespace);\n if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;\n\n const text = getFirstHumanMessageText(messages);\n if (text == null) return;\n const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();\n if (toolCallId == null) return;\n this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);\n }\n}\n\nfunction getFirstHumanMessageText(messages: unknown[]): string | null {\n for (const message of messages) {\n if (message == null || typeof message !== \"object\") continue;\n const record = message as {\n type?: unknown;\n role?: unknown;\n content?: unknown;\n kwargs?: { type?: unknown; content?: unknown };\n lc_kwargs?: { type?: unknown; content?: unknown };\n };\n const type =\n record.type ??\n record.role ??\n record.kwargs?.type ??\n record.lc_kwargs?.type;\n if (type !== \"human\") continue;\n const content =\n record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;\n return typeof content === \"string\" && content.length > 0 ? content : null;\n }\n return null;\n}\n\nfunction shouldPromoteToObservedNamespace(entry: MutableSubagent): boolean {\n return (\n entry.name === \"fanout-worker\" ||\n /^Worker worker-\\d+/i.test(entry.taskInput ?? \"\")\n );\n}\n\nfunction taskWorkNamespace(\n toolCallId: string,\n eventNamespace: readonly string[]\n): readonly string[] {\n const last = eventNamespace.at(-1);\n if (last != null && isToolNamespaceSegment(last)) return [...eventNamespace];\n return [`tools:${toolCallId}`];\n}\n\nfunction toSnapshot(entry: MutableSubagent): SubagentDiscoverySnapshot {\n return {\n id: entry.id,\n name: entry.name,\n namespace: entry.namespace,\n parentId: entry.parentId,\n depth: entry.depth,\n status: entry.status,\n taskInput: entry.taskInput,\n output: entry.output,\n error: entry.error,\n startedAt: entry.startedAt,\n completedAt: entry.completedAt,\n };\n}\n\nfunction parseTaskInput(raw: unknown): {\n description?: string;\n subagent_type?: string;\n} {\n if (raw == null) return {};\n if (typeof raw === \"string\") {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return {\n description:\n typeof parsed.description === \"string\"\n ? parsed.description\n : undefined,\n subagent_type:\n typeof parsed.subagent_type === \"string\"\n ? parsed.subagent_type\n : undefined,\n };\n } catch {\n return {};\n }\n }\n if (typeof raw === \"object\" && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n return {\n description:\n typeof obj.description === \"string\" ? obj.description : undefined,\n subagent_type:\n typeof obj.subagent_type === \"string\" ? obj.subagent_type : undefined,\n };\n }\n return {};\n}\n\nfunction getTaskToolCalls(message: unknown): Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n}> {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return [];\n }\n const record = message as {\n tool_calls?: unknown;\n kwargs?: { tool_calls?: unknown };\n lc_kwargs?: { tool_calls?: unknown };\n };\n const toolCalls =\n record.tool_calls ??\n record.kwargs?.tool_calls ??\n record.lc_kwargs?.tool_calls;\n if (!Array.isArray(toolCalls)) return [];\n\n const result: Array<{\n id: string;\n input: { description?: string; subagent_type?: string };\n }> = [];\n for (const toolCall of toolCalls) {\n if (\n toolCall == null ||\n typeof toolCall !== \"object\" ||\n Array.isArray(toolCall)\n ) {\n continue;\n }\n const record = toolCall as {\n id?: unknown;\n name?: unknown;\n args?: unknown;\n };\n if (typeof record.id !== \"string\" || record.name !== \"task\") continue;\n result.push({ id: record.id, input: parseTaskInput(record.args) });\n }\n return result;\n}\n\nfunction getToolMessageCallId(message: unknown): string | undefined {\n if (\n message == null ||\n typeof message !== \"object\" ||\n Array.isArray(message)\n ) {\n return undefined;\n }\n const record = message as {\n tool_call_id?: unknown;\n kwargs?: { tool_call_id?: unknown };\n lc_kwargs?: { tool_call_id?: unknown };\n };\n const id =\n record.tool_call_id ??\n record.kwargs?.tool_call_id ??\n record.lc_kwargs?.tool_call_id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n}\n\n/**\n * Derive (parentId, depth) from a namespace like\n * `[\"subagents:abc:def\"]`. Namespaces form a rooted tree; the last\n * `:` segment of the deepest namespace element is the current node's\n * call-id and the one before it is the parent.\n */\nfunction lineageFromNamespace(namespace: readonly string[]): {\n parentId: string | null;\n depth: number;\n} {\n if (isRootNamespace(namespace)) return { parentId: null, depth: 1 };\n const last = namespace[namespace.length - 1];\n if (last == null) return { parentId: null, depth: 1 };\n // Namespace segments typically look like\n // subagents:<parentCallId>:<thisCallId>\n // but the protocol doesn't mandate that shape; we best-effort.\n const parts = last.split(\":\").filter((part) => part.length > 0);\n const trimmed = parts.slice(1); // drop the leading \"subagents\" prefix\n const depth = Math.max(1, trimmed.length);\n const parentId = trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null;\n return { parentId: parentId ?? null, depth };\n}\n"],"mappings":"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"}
|
|
@@ -7,11 +7,12 @@ function openProjectionSubscription({ thread, channels, namespace, depth = 1, re
|
|
|
7
7
|
let disposed = false;
|
|
8
8
|
const start = async () => {
|
|
9
9
|
try {
|
|
10
|
-
const
|
|
10
|
+
const subscribeParams = {
|
|
11
11
|
channels: [...channels],
|
|
12
12
|
namespaces: namespace.length > 0 ? [[...namespace]] : [[]],
|
|
13
13
|
depth
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
|
+
const subscription = await thread.subscribe(subscribeParams);
|
|
15
16
|
handle = subscription;
|
|
16
17
|
if (disposed) {
|
|
17
18
|
await subscription.unsubscribe();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.cjs","names":[],"sources":["../../../src/stream/projections/runtime.ts"],"sourcesContent":["import type { Channel, Event } from \"@langchain/protocol\";\nimport type { SubscriptionHandle } from \"../../client/stream/index.js\";\nimport type { ProjectionRuntime, ThreadStream } from \"../types.js\";\n\ninterface ProjectionSubscriptionOptions {\n thread: ThreadStream;\n channels: readonly Channel[];\n namespace: readonly string[];\n depth?: number;\n /**\n * Some transports pause a subscription between runs. Most projections\n * intentionally preserve their historical one-pass behavior; opt in when\n * the projection already handled resume loops.\n */\n resumeOnPause?: boolean;\n onSubscribe?: () => void;\n onEvent(event: Event): void;\n onFinally?: () => void;\n}\n\n/**\n * Shared async subscription lifecycle for projection runtimes.\n */\nexport function openProjectionSubscription({\n thread,\n channels,\n namespace,\n depth = 1,\n resumeOnPause = false,\n onSubscribe,\n onEvent,\n onFinally,\n}: ProjectionSubscriptionOptions): ProjectionRuntime {\n let handle: SubscriptionHandle<Event> | undefined;\n let disposed = false;\n\n const start = async () => {\n try {\n const
|
|
1
|
+
{"version":3,"file":"runtime.cjs","names":[],"sources":["../../../src/stream/projections/runtime.ts"],"sourcesContent":["import type { Channel, Event } from \"@langchain/protocol\";\nimport type { SubscriptionHandle } from \"../../client/stream/index.js\";\nimport type { ProjectionRuntime, ThreadStream } from \"../types.js\";\n\ninterface ProjectionSubscriptionOptions {\n thread: ThreadStream;\n channels: readonly Channel[];\n namespace: readonly string[];\n depth?: number;\n /**\n * Some transports pause a subscription between runs. Most projections\n * intentionally preserve their historical one-pass behavior; opt in when\n * the projection already handled resume loops.\n */\n resumeOnPause?: boolean;\n onSubscribe?: () => void;\n onEvent(event: Event): void;\n onFinally?: () => void;\n}\n\n/**\n * Shared async subscription lifecycle for projection runtimes.\n */\nexport function openProjectionSubscription({\n thread,\n channels,\n namespace,\n depth = 1,\n resumeOnPause = false,\n onSubscribe,\n onEvent,\n onFinally,\n}: ProjectionSubscriptionOptions): ProjectionRuntime {\n let handle: SubscriptionHandle<Event> | undefined;\n let disposed = false;\n\n const start = async () => {\n try {\n const subscribeParams = {\n channels: [...channels],\n namespaces:\n namespace.length > 0\n ? ([[...namespace]] as string[][])\n : ([[]] as string[][]),\n depth,\n };\n const subscription = await thread.subscribe(subscribeParams);\n handle = subscription;\n if (disposed) {\n await subscription.unsubscribe();\n return;\n }\n onSubscribe?.();\n\n do {\n for await (const event of subscription) {\n if (disposed) break;\n onEvent(event);\n }\n if (disposed || !resumeOnPause || !subscription.isPaused) break;\n await subscription.waitForResume();\n } while (!disposed);\n } catch {\n // Thread closed / errored; projections expose their last good snapshot.\n } finally {\n onFinally?.();\n }\n };\n\n void start();\n\n return {\n async dispose() {\n disposed = true;\n try {\n await handle?.unsubscribe();\n } catch {\n // already closed\n }\n },\n };\n}\n"],"mappings":";;;;AAuBA,SAAgB,2BAA2B,EACzC,QACA,UACA,WACA,QAAQ,GACR,gBAAgB,OAChB,aACA,SACA,aACmD;CACnD,IAAI;CACJ,IAAI,WAAW;CAEf,MAAM,QAAQ,YAAY;AACxB,MAAI;GACF,MAAM,kBAAkB;IACtB,UAAU,CAAC,GAAG,SAAS;IACvB,YACE,UAAU,SAAS,IACd,CAAC,CAAC,GAAG,UAAU,CAAC,GAChB,CAAC,EAAE,CAAC;IACX;IACD;GACD,MAAM,eAAe,MAAM,OAAO,UAAU,gBAAgB;AAC5D,YAAS;AACT,OAAI,UAAU;AACZ,UAAM,aAAa,aAAa;AAChC;;AAEF,kBAAe;AAEf,MAAG;AACD,eAAW,MAAM,SAAS,cAAc;AACtC,SAAI,SAAU;AACd,aAAQ,MAAM;;AAEhB,QAAI,YAAY,CAAC,iBAAiB,CAAC,aAAa,SAAU;AAC1D,UAAM,aAAa,eAAe;YAC3B,CAAC;UACJ,WAEE;AACR,gBAAa;;;AAIZ,QAAO;AAEZ,QAAO,EACL,MAAM,UAAU;AACd,aAAW;AACX,MAAI;AACF,SAAM,QAAQ,aAAa;UACrB;IAIX"}
|
|
@@ -7,11 +7,12 @@ function openProjectionSubscription({ thread, channels, namespace, depth = 1, re
|
|
|
7
7
|
let disposed = false;
|
|
8
8
|
const start = async () => {
|
|
9
9
|
try {
|
|
10
|
-
const
|
|
10
|
+
const subscribeParams = {
|
|
11
11
|
channels: [...channels],
|
|
12
12
|
namespaces: namespace.length > 0 ? [[...namespace]] : [[]],
|
|
13
13
|
depth
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
|
+
const subscription = await thread.subscribe(subscribeParams);
|
|
15
16
|
handle = subscription;
|
|
16
17
|
if (disposed) {
|
|
17
18
|
await subscription.unsubscribe();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":[],"sources":["../../../src/stream/projections/runtime.ts"],"sourcesContent":["import type { Channel, Event } from \"@langchain/protocol\";\nimport type { SubscriptionHandle } from \"../../client/stream/index.js\";\nimport type { ProjectionRuntime, ThreadStream } from \"../types.js\";\n\ninterface ProjectionSubscriptionOptions {\n thread: ThreadStream;\n channels: readonly Channel[];\n namespace: readonly string[];\n depth?: number;\n /**\n * Some transports pause a subscription between runs. Most projections\n * intentionally preserve their historical one-pass behavior; opt in when\n * the projection already handled resume loops.\n */\n resumeOnPause?: boolean;\n onSubscribe?: () => void;\n onEvent(event: Event): void;\n onFinally?: () => void;\n}\n\n/**\n * Shared async subscription lifecycle for projection runtimes.\n */\nexport function openProjectionSubscription({\n thread,\n channels,\n namespace,\n depth = 1,\n resumeOnPause = false,\n onSubscribe,\n onEvent,\n onFinally,\n}: ProjectionSubscriptionOptions): ProjectionRuntime {\n let handle: SubscriptionHandle<Event> | undefined;\n let disposed = false;\n\n const start = async () => {\n try {\n const
|
|
1
|
+
{"version":3,"file":"runtime.js","names":[],"sources":["../../../src/stream/projections/runtime.ts"],"sourcesContent":["import type { Channel, Event } from \"@langchain/protocol\";\nimport type { SubscriptionHandle } from \"../../client/stream/index.js\";\nimport type { ProjectionRuntime, ThreadStream } from \"../types.js\";\n\ninterface ProjectionSubscriptionOptions {\n thread: ThreadStream;\n channels: readonly Channel[];\n namespace: readonly string[];\n depth?: number;\n /**\n * Some transports pause a subscription between runs. Most projections\n * intentionally preserve their historical one-pass behavior; opt in when\n * the projection already handled resume loops.\n */\n resumeOnPause?: boolean;\n onSubscribe?: () => void;\n onEvent(event: Event): void;\n onFinally?: () => void;\n}\n\n/**\n * Shared async subscription lifecycle for projection runtimes.\n */\nexport function openProjectionSubscription({\n thread,\n channels,\n namespace,\n depth = 1,\n resumeOnPause = false,\n onSubscribe,\n onEvent,\n onFinally,\n}: ProjectionSubscriptionOptions): ProjectionRuntime {\n let handle: SubscriptionHandle<Event> | undefined;\n let disposed = false;\n\n const start = async () => {\n try {\n const subscribeParams = {\n channels: [...channels],\n namespaces:\n namespace.length > 0\n ? ([[...namespace]] as string[][])\n : ([[]] as string[][]),\n depth,\n };\n const subscription = await thread.subscribe(subscribeParams);\n handle = subscription;\n if (disposed) {\n await subscription.unsubscribe();\n return;\n }\n onSubscribe?.();\n\n do {\n for await (const event of subscription) {\n if (disposed) break;\n onEvent(event);\n }\n if (disposed || !resumeOnPause || !subscription.isPaused) break;\n await subscription.waitForResume();\n } while (!disposed);\n } catch {\n // Thread closed / errored; projections expose their last good snapshot.\n } finally {\n onFinally?.();\n }\n };\n\n void start();\n\n return {\n async dispose() {\n disposed = true;\n try {\n await handle?.unsubscribe();\n } catch {\n // already closed\n }\n },\n };\n}\n"],"mappings":";;;;AAuBA,SAAgB,2BAA2B,EACzC,QACA,UACA,WACA,QAAQ,GACR,gBAAgB,OAChB,aACA,SACA,aACmD;CACnD,IAAI;CACJ,IAAI,WAAW;CAEf,MAAM,QAAQ,YAAY;AACxB,MAAI;GACF,MAAM,kBAAkB;IACtB,UAAU,CAAC,GAAG,SAAS;IACvB,YACE,UAAU,SAAS,IACd,CAAC,CAAC,GAAG,UAAU,CAAC,GAChB,CAAC,EAAE,CAAC;IACX;IACD;GACD,MAAM,eAAe,MAAM,OAAO,UAAU,gBAAgB;AAC5D,YAAS;AACT,OAAI,UAAU;AACZ,UAAM,aAAa,aAAa;AAChC;;AAEF,kBAAe;AAEf,MAAG;AACD,eAAW,MAAM,SAAS,cAAc;AACtC,SAAI,SAAU;AACd,aAAQ,MAAM;;AAEhB,QAAI,YAAY,CAAC,iBAAiB,CAAC,aAAa,SAAU;AAC1D,UAAM,aAAa,eAAe;YAC3B,CAAC;UACJ,WAEE;AACR,gBAAa;;;AAIZ,QAAO;AAEZ,QAAO,EACL,MAAM,UAAU;AACd,aAAW;AACX,MAAI;AACF,SAAM,QAAQ,aAAa;UACrB;IAIX"}
|
|
@@ -23,13 +23,6 @@ var RootMessageProjection = class {
|
|
|
23
23
|
/** Root snapshot store written to on every merge. */
|
|
24
24
|
#store;
|
|
25
25
|
/**
|
|
26
|
-
* Subagent discovery runner notified about every assembled root
|
|
27
|
-
* message. Driving discovery from assembled messages (rather than
|
|
28
|
-
* raw events) lets us discover subagents from synthesized
|
|
29
|
-
* `tool_calls` without re-parsing protocol payloads.
|
|
30
|
-
*/
|
|
31
|
-
#subagents;
|
|
32
|
-
/**
|
|
33
26
|
* Stateful chunk assembler for in-flight messages. Reset (via a
|
|
34
27
|
* fresh instance) on every {@link reset} so a new thread starts
|
|
35
28
|
* with no half-built messages from the previous one.
|
|
@@ -61,16 +54,28 @@ var RootMessageProjection = class {
|
|
|
61
54
|
*/
|
|
62
55
|
#toolCallIdByNamespace = /* @__PURE__ */ new Map();
|
|
63
56
|
/**
|
|
57
|
+
* Coalescing buffer for store writes. {@link handleMessage} and
|
|
58
|
+
* {@link applyValues} stage their computed `messages` / `values`
|
|
59
|
+
* here instead of calling `store.setState` per event. A single
|
|
60
|
+
* `setTimeout(0)` flush commits them in one `setState`, so a
|
|
61
|
+
* burst of SSE events draining as a microtask chain becomes one
|
|
62
|
+
* store notification at the next macrotask boundary.
|
|
63
|
+
*
|
|
64
|
+
* `null` means "no staged write" — once a flush settles, the
|
|
65
|
+
* slots are cleared so the next call starts from the latest
|
|
66
|
+
* committed store snapshot.
|
|
67
|
+
*/
|
|
68
|
+
#pendingMessages = null;
|
|
69
|
+
#pendingValues = null;
|
|
70
|
+
#flushScheduled = false;
|
|
71
|
+
/**
|
|
64
72
|
* @param params.messagesKey - Key inside `values` that holds the
|
|
65
73
|
* message array.
|
|
66
74
|
* @param params.store - Root snapshot store to mutate.
|
|
67
|
-
* @param params.subagents - Discovery runner fed by each new
|
|
68
|
-
* assembled message.
|
|
69
75
|
*/
|
|
70
76
|
constructor(params) {
|
|
71
77
|
this.#messagesKey = params.messagesKey;
|
|
72
78
|
this.#store = params.store;
|
|
73
|
-
this.#subagents = params.subagents;
|
|
74
79
|
}
|
|
75
80
|
/**
|
|
76
81
|
* Drop all per-thread state. Called by the controller on thread
|
|
@@ -82,6 +87,9 @@ var RootMessageProjection = class {
|
|
|
82
87
|
this.#indexById.clear();
|
|
83
88
|
this.#valuesMessageIds = /* @__PURE__ */ new Set();
|
|
84
89
|
this.#toolCallIdByNamespace.clear();
|
|
90
|
+
this.#pendingMessages = null;
|
|
91
|
+
this.#pendingValues = null;
|
|
92
|
+
this.#flushScheduled = false;
|
|
85
93
|
}
|
|
86
94
|
/**
|
|
87
95
|
* Record a `namespace → tool_call_id` mapping captured from a root
|
|
@@ -103,7 +111,7 @@ var RootMessageProjection = class {
|
|
|
103
111
|
* Captures role/tool metadata on `message-start`, feeds the chunk
|
|
104
112
|
* to the assembler, projects the assembled output to a
|
|
105
113
|
* {@link BaseMessage}, and either appends or in-place updates the
|
|
106
|
-
*
|
|
114
|
+
* pending messages buffer based on whether the id was seen before.
|
|
107
115
|
*
|
|
108
116
|
* @param event - The `messages` channel event to consume.
|
|
109
117
|
*/
|
|
@@ -133,28 +141,22 @@ var RootMessageProjection = class {
|
|
|
133
141
|
if (id == null) return;
|
|
134
142
|
const captured = this.#roles.get(id) ?? { role: "ai" };
|
|
135
143
|
const base = require_assembled_to_message.assembledMessageToBaseMessage(update.message, captured.role, { toolCallId: captured.toolCallId });
|
|
136
|
-
this.#
|
|
137
|
-
this.#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} : {
|
|
153
|
-
...s,
|
|
154
|
-
messages,
|
|
155
|
-
values
|
|
156
|
-
};
|
|
157
|
-
});
|
|
144
|
+
const baselineMessages = this.#pendingMessages ?? this.#store.getSnapshot().messages;
|
|
145
|
+
const existingIdx = this.#indexById.get(id);
|
|
146
|
+
let messages;
|
|
147
|
+
if (existingIdx == null) {
|
|
148
|
+
this.#indexById.set(id, baselineMessages.length);
|
|
149
|
+
messages = [...baselineMessages, base];
|
|
150
|
+
} else if (require_message_reconciliation.messagesEqual(baselineMessages[existingIdx], base)) return;
|
|
151
|
+
else {
|
|
152
|
+
messages = baselineMessages.slice();
|
|
153
|
+
messages[existingIdx] = base;
|
|
154
|
+
}
|
|
155
|
+
const baselineValues = this.#pendingValues ?? this.#store.getSnapshot().values;
|
|
156
|
+
const values = syncMessagesIntoValues(baselineValues, this.#messagesKey, messages);
|
|
157
|
+
this.#pendingMessages = messages;
|
|
158
|
+
if (values !== baselineValues) this.#pendingValues = values;
|
|
159
|
+
this.#scheduleFlush();
|
|
158
160
|
}
|
|
159
161
|
/**
|
|
160
162
|
* Reconcile a full `values` snapshot into the projection.
|
|
@@ -172,34 +174,77 @@ var RootMessageProjection = class {
|
|
|
172
174
|
* `values[messagesKey]` and coerced to `BaseMessage` instances.
|
|
173
175
|
*/
|
|
174
176
|
applyValues(nextValues, nextMessages) {
|
|
177
|
+
const baselineSnapshot = this.#store.getSnapshot();
|
|
178
|
+
const baselineMessages = this.#pendingMessages ?? baselineSnapshot.messages;
|
|
179
|
+
const baselineValues = this.#pendingValues ?? baselineSnapshot.values;
|
|
180
|
+
if (nextMessages.length === 0) {
|
|
181
|
+
if (stateValuesShallowEqual(baselineValues, nextValues, this.#messagesKey)) return;
|
|
182
|
+
this.#pendingValues = syncMessagesIntoValues(nextValues, this.#messagesKey, baselineMessages);
|
|
183
|
+
this.#scheduleFlush();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const reconciliation = require_message_reconciliation.reconcileMessagesFromValues({
|
|
187
|
+
valueMessages: nextMessages,
|
|
188
|
+
currentMessages: baselineMessages,
|
|
189
|
+
currentIndexById: this.#indexById,
|
|
190
|
+
previousValueMessageIds: this.#valuesMessageIds,
|
|
191
|
+
preferValuesMessage: require_message_reconciliation.shouldPreferValuesMessageForToolCalls
|
|
192
|
+
});
|
|
193
|
+
this.#valuesMessageIds = reconciliation.valueMessageIds;
|
|
194
|
+
const messages = reconciliation.messages;
|
|
195
|
+
const values = {
|
|
196
|
+
...nextValues,
|
|
197
|
+
[this.#messagesKey]: messages
|
|
198
|
+
};
|
|
199
|
+
if (messages === baselineMessages && stateValuesShallowEqual(baselineValues, values, this.#messagesKey)) return;
|
|
200
|
+
this.#indexById.clear();
|
|
201
|
+
for (const [id, idx] of require_message_reconciliation.buildMessageIndex(messages)) this.#indexById.set(id, idx);
|
|
202
|
+
this.#pendingMessages = messages;
|
|
203
|
+
this.#pendingValues = values;
|
|
204
|
+
this.#scheduleFlush();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Schedule a coalesced flush on the next macrotask. Idempotent
|
|
208
|
+
* within a tick — multiple `handleMessage` / `applyValues` calls
|
|
209
|
+
* before the flush fires collapse into one store write.
|
|
210
|
+
*
|
|
211
|
+
* `setTimeout(0)` is a macrotask: it runs after the current
|
|
212
|
+
* microtask chain drains, so a burst of SSE events processed by
|
|
213
|
+
* the controller's `for await` pump becomes one `store.setState`
|
|
214
|
+
* (and therefore one `useSyncExternalStore` notification).
|
|
215
|
+
*/
|
|
216
|
+
#scheduleFlush = () => {
|
|
217
|
+
if (this.#flushScheduled) return;
|
|
218
|
+
this.#flushScheduled = true;
|
|
219
|
+
setTimeout(this.#flushPending, 0);
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Drain `#pendingMessages` / `#pendingValues` to the store in a
|
|
223
|
+
* single `setState` call.
|
|
224
|
+
*/
|
|
225
|
+
#flushPending = () => {
|
|
226
|
+
this.#flushScheduled = false;
|
|
227
|
+
const messages = this.#pendingMessages;
|
|
228
|
+
const values = this.#pendingValues;
|
|
229
|
+
this.#pendingMessages = null;
|
|
230
|
+
this.#pendingValues = null;
|
|
231
|
+
if (messages == null && values == null) return;
|
|
175
232
|
this.#store.setState((s) => {
|
|
176
|
-
if (
|
|
233
|
+
if (messages == null) return values == null ? s : {
|
|
177
234
|
...s,
|
|
178
|
-
values
|
|
235
|
+
values
|
|
179
236
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
currentIndexById: this.#indexById,
|
|
184
|
-
previousValueMessageIds: this.#valuesMessageIds,
|
|
185
|
-
preferValuesMessage: require_message_reconciliation.shouldPreferValuesMessageForToolCalls
|
|
186
|
-
});
|
|
187
|
-
this.#valuesMessageIds = reconciliation.valueMessageIds;
|
|
188
|
-
const messages = reconciliation.messages;
|
|
189
|
-
const values = {
|
|
190
|
-
...nextValues,
|
|
191
|
-
[this.#messagesKey]: messages
|
|
237
|
+
if (values == null) return {
|
|
238
|
+
...s,
|
|
239
|
+
messages
|
|
192
240
|
};
|
|
193
|
-
if (messages === s.messages && stateValuesShallowEqual(s.values, values, this.#messagesKey)) return s;
|
|
194
|
-
this.#indexById.clear();
|
|
195
|
-
for (const [id, idx] of require_message_reconciliation.buildMessageIndex(messages)) this.#indexById.set(id, idx);
|
|
196
241
|
return {
|
|
197
242
|
...s,
|
|
198
|
-
|
|
199
|
-
|
|
243
|
+
messages,
|
|
244
|
+
values
|
|
200
245
|
};
|
|
201
246
|
});
|
|
202
|
-
}
|
|
247
|
+
};
|
|
203
248
|
};
|
|
204
249
|
/**
|
|
205
250
|
* Mirror a freshly-updated message list into `values[messagesKey]`.
|