@langchain/langgraph-sdk 1.9.1 → 1.9.3-rc.0
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/crons/index.cjs +8 -2
- package/dist/client/crons/index.cjs.map +1 -1
- package/dist/client/crons/index.d.cts +7 -1
- package/dist/client/crons/index.d.cts.map +1 -1
- package/dist/client/crons/index.d.ts +7 -1
- package/dist/client/crons/index.d.ts.map +1 -1
- package/dist/client/crons/index.js +8 -2
- package/dist/client/crons/index.js.map +1 -1
- 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/messages.cjs +50 -10
- package/dist/client/stream/messages.cjs.map +1 -1
- package/dist/client/stream/messages.d.cts +4 -0
- package/dist/client/stream/messages.d.cts.map +1 -1
- package/dist/client/stream/messages.d.ts +4 -0
- package/dist/client/stream/messages.d.ts.map +1 -1
- package/dist/client/stream/messages.js +50 -10
- package/dist/client/stream/messages.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/assembled-to-message.cjs +10 -6
- package/dist/stream/assembled-to-message.cjs.map +1 -1
- package/dist/stream/assembled-to-message.d.cts.map +1 -1
- package/dist/stream/assembled-to-message.d.ts.map +1 -1
- package/dist/stream/assembled-to-message.js +10 -6
- package/dist/stream/assembled-to-message.js.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/message-reconciliation.cjs +11 -1
- package/dist/stream/message-reconciliation.cjs.map +1 -1
- package/dist/stream/message-reconciliation.js +11 -1
- package/dist/stream/message-reconciliation.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/package.json +3 -3
|
@@ -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":"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"}
|
|
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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"}
|
|
@@ -57,10 +57,20 @@ function shouldPreferValuesMessageForToolCalls(valuesMessage, streamedMessage) {
|
|
|
57
57
|
const streamedToolCalls = getMessageToolCalls(streamedMessage);
|
|
58
58
|
if (streamedToolCalls.length < valuesToolCalls.length) return true;
|
|
59
59
|
const streamedIds = new Set(streamedToolCalls.map((toolCall) => toolCall.id).filter((id) => typeof id === "string" && id.length > 0));
|
|
60
|
-
|
|
60
|
+
if (valuesToolCalls.some((toolCall) => {
|
|
61
61
|
return typeof toolCall.id === "string" && !streamedIds.has(toolCall.id);
|
|
62
|
+
})) return true;
|
|
63
|
+
return valuesToolCalls.some((valuesToolCall) => {
|
|
64
|
+
const streamedToolCall = streamedToolCalls.find((candidate) => typeof valuesToolCall.id === "string" && candidate.id === valuesToolCall.id);
|
|
65
|
+
return streamedToolCall != null && hasMeaningfulArgs(valuesToolCall.args) && !jsonishEqual(valuesToolCall.args, streamedToolCall.args);
|
|
62
66
|
});
|
|
63
67
|
}
|
|
68
|
+
function hasMeaningfulArgs(args) {
|
|
69
|
+
if (args == null) return false;
|
|
70
|
+
if (typeof args === "string") return args.length > 0;
|
|
71
|
+
if (typeof args === "object") return Object.keys(args).length > 0;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
64
74
|
function messagesEqualList(previous, next) {
|
|
65
75
|
if (previous === next) return true;
|
|
66
76
|
if (previous.length !== next.length) return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-reconciliation.cjs","names":[],"sources":["../../src/stream/message-reconciliation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\n\nexport interface ReconcileMessagesFromValuesOptions {\n /**\n * Messages from the authoritative `values.messages` snapshot.\n */\n readonly valueMessages: readonly BaseMessage[];\n /**\n * Current message projection, including stream-assembled in-flight messages.\n */\n readonly currentMessages: readonly BaseMessage[];\n /**\n * Index from message id to current message position.\n */\n readonly currentIndexById: ReadonlyMap<string, number>;\n /**\n * Ids observed in the most recent previous `values.messages` snapshot.\n * If one of these ids is missing from the next snapshot, it is treated as\n * an explicit server-side removal.\n */\n readonly previousValueMessageIds: ReadonlySet<string>;\n /**\n * Optional stream-id filter. When supplied, only these current ids are\n * eligible to override the values snapshot. When omitted, any id present in\n * `currentIndexById` is eligible, preserving the root controller's historic\n * behavior.\n */\n readonly streamedMessageIds?: ReadonlySet<string>;\n /**\n * Allows callers to keep a values message even when a streamed message with\n * the same id exists. Used by the root controller when the values message\n * carries finalized tool-call data missing from the streamed message.\n */\n readonly preferValuesMessage?: (\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n ) => boolean;\n}\n\nexport interface ReconciledMessages {\n readonly messages: readonly BaseMessage[];\n readonly valueMessageIds: Set<string>;\n}\n\n/**\n * Merge an authoritative `values.messages` snapshot with the current streamed\n * message projection.\n *\n * Values remain authoritative for ordering and removals. Streamed messages\n * remain authoritative for in-flight content until the server echoes them in a\n * values snapshot, and stream-only messages are preserved until they either\n * appear in values or are known to have been removed.\n */\nexport function reconcileMessagesFromValues({\n valueMessages,\n currentMessages,\n currentIndexById,\n previousValueMessageIds,\n streamedMessageIds,\n preferValuesMessage,\n}: ReconcileMessagesFromValuesOptions): ReconciledMessages {\n const valueMessageIds = new Set<string>();\n const merged: BaseMessage[] = [];\n\n for (const valuesMessage of valueMessages) {\n const id = normalizedMessageId(valuesMessage);\n if (id == null) {\n merged.push(valuesMessage);\n continue;\n }\n\n valueMessageIds.add(id);\n const streamIdx = currentIndexById.get(id);\n const canUseStreamed =\n streamIdx != null &&\n (streamedMessageIds == null || streamedMessageIds.has(id));\n const streamedMessage = canUseStreamed\n ? currentMessages[streamIdx]\n : undefined;\n\n if (\n streamedMessage != null &&\n preferValuesMessage?.(valuesMessage, streamedMessage) !== true\n ) {\n merged.push(streamedMessage);\n } else {\n merged.push(valuesMessage);\n }\n }\n\n for (const existing of currentMessages) {\n const id = normalizedMessageId(existing);\n if (id == null) continue;\n if (valueMessageIds.has(id)) continue;\n if (previousValueMessageIds.has(id)) continue;\n if (streamedMessageIds != null && !streamedMessageIds.has(id)) continue;\n merged.push(existing);\n }\n\n return {\n messages: messagesEqualList(currentMessages, merged)\n ? currentMessages\n : merged,\n valueMessageIds,\n };\n}\n\n/**\n * Build a position index for keyed messages.\n */\nexport function buildMessageIndex(\n messages: readonly BaseMessage[]\n): Map<string, number> {\n const index = new Map<string, number>();\n messages.forEach((message, idx) => {\n const id = normalizedMessageId(message);\n if (id != null) index.set(id, idx);\n });\n return index;\n}\n\n/**\n * Decide whether a values message carries tool-call data missing from the\n * streamed message.\n */\nexport function shouldPreferValuesMessageForToolCalls(\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n): boolean {\n const valuesToolCalls = getMessageToolCalls(valuesMessage);\n if (valuesToolCalls.length === 0) return false;\n\n const streamedToolCalls = getMessageToolCalls(streamedMessage);\n if (streamedToolCalls.length < valuesToolCalls.length) return true;\n\n const streamedIds = new Set(\n streamedToolCalls\n .map((toolCall) => toolCall.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n );\n return valuesToolCalls.some((toolCall) => {\n return typeof toolCall.id === \"string\" && !streamedIds.has(toolCall.id);\n });\n}\n\nexport function messagesEqualList(\n previous: readonly BaseMessage[],\n next: readonly BaseMessage[]\n): boolean {\n if (previous === next) return true;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!messagesEqual(previous[i], next[i])) return false;\n }\n return true;\n}\n\nexport function messagesEqual(\n previous: BaseMessage | undefined,\n next: BaseMessage | undefined\n): boolean {\n if (previous === next) return true;\n if (previous == null || next == null) return false;\n const previousRecord = previous as unknown as Record<string, unknown>;\n const nextRecord = next as unknown as Record<string, unknown>;\n const previousType =\n typeof previous.getType === \"function\"\n ? previous.getType()\n : previousRecord.type;\n const nextType =\n typeof next.getType === \"function\" ? next.getType() : nextRecord.type;\n\n return (\n previous.id === next.id &&\n previousType === nextType &&\n jsonishEqual(previous.content, next.content) &&\n previousRecord.tool_call_id === nextRecord.tool_call_id &&\n previousRecord.status === nextRecord.status &&\n jsonishEqual(\n previousRecord.additional_kwargs,\n nextRecord.additional_kwargs\n ) &&\n jsonishEqual(\n previousRecord.response_metadata,\n nextRecord.response_metadata\n ) &&\n jsonishEqual(previousRecord.tool_calls, nextRecord.tool_calls) &&\n jsonishEqual(\n previousRecord.tool_call_chunks,\n nextRecord.tool_call_chunks\n ) &&\n jsonishEqual(previousRecord.usage_metadata, nextRecord.usage_metadata)\n );\n}\n\nfunction normalizedMessageId(message: BaseMessage): string | undefined {\n return typeof message.id === \"string\" && message.id.length > 0\n ? message.id\n : undefined;\n}\n\nfunction getMessageToolCalls(\n message: BaseMessage\n): Array<{ id?: string; name?: string }> {\n const raw = (message as unknown as { tool_calls?: unknown }).tool_calls;\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (toolCall): toolCall is { id?: string; name?: string } =>\n toolCall != null && typeof toolCall === \"object\"\n );\n}\n\nfunction jsonishEqual(previous: unknown, next: unknown): boolean {\n return jsonishEqualAtDepth(previous, next, 0);\n}\n\nfunction jsonishEqualAtDepth(\n previous: unknown,\n next: unknown,\n depth: number\n): boolean {\n if (Object.is(previous, next)) return true;\n if (previous == null || next == null) return false;\n if (typeof previous !== \"object\" || typeof next !== \"object\") return false;\n if (depth >= 4) return false;\n\n if (Array.isArray(previous) || Array.isArray(next)) {\n if (!Array.isArray(previous) || !Array.isArray(next)) return false;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!jsonishEqualAtDepth(previous[i], next[i], depth + 1)) return false;\n }\n return true;\n }\n\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord).filter(\n (key) => typeof previousRecord[key] !== \"function\"\n );\n const nextKeys = Object.keys(nextRecord).filter(\n (key) => typeof nextRecord[key] !== \"function\"\n );\n if (previousKeys.length !== nextKeys.length) return false;\n\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n if (!jsonishEqualAtDepth(previousRecord[key], nextRecord[key], depth + 1)) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message-reconciliation.cjs","names":[],"sources":["../../src/stream/message-reconciliation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\n\nexport interface ReconcileMessagesFromValuesOptions {\n /**\n * Messages from the authoritative `values.messages` snapshot.\n */\n readonly valueMessages: readonly BaseMessage[];\n /**\n * Current message projection, including stream-assembled in-flight messages.\n */\n readonly currentMessages: readonly BaseMessage[];\n /**\n * Index from message id to current message position.\n */\n readonly currentIndexById: ReadonlyMap<string, number>;\n /**\n * Ids observed in the most recent previous `values.messages` snapshot.\n * If one of these ids is missing from the next snapshot, it is treated as\n * an explicit server-side removal.\n */\n readonly previousValueMessageIds: ReadonlySet<string>;\n /**\n * Optional stream-id filter. When supplied, only these current ids are\n * eligible to override the values snapshot. When omitted, any id present in\n * `currentIndexById` is eligible, preserving the root controller's historic\n * behavior.\n */\n readonly streamedMessageIds?: ReadonlySet<string>;\n /**\n * Allows callers to keep a values message even when a streamed message with\n * the same id exists. Used by the root controller when the values message\n * carries finalized tool-call data missing from the streamed message.\n */\n readonly preferValuesMessage?: (\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n ) => boolean;\n}\n\nexport interface ReconciledMessages {\n readonly messages: readonly BaseMessage[];\n readonly valueMessageIds: Set<string>;\n}\n\n/**\n * Merge an authoritative `values.messages` snapshot with the current streamed\n * message projection.\n *\n * Values remain authoritative for ordering and removals. Streamed messages\n * remain authoritative for in-flight content until the server echoes them in a\n * values snapshot, and stream-only messages are preserved until they either\n * appear in values or are known to have been removed.\n */\nexport function reconcileMessagesFromValues({\n valueMessages,\n currentMessages,\n currentIndexById,\n previousValueMessageIds,\n streamedMessageIds,\n preferValuesMessage,\n}: ReconcileMessagesFromValuesOptions): ReconciledMessages {\n const valueMessageIds = new Set<string>();\n const merged: BaseMessage[] = [];\n\n for (const valuesMessage of valueMessages) {\n const id = normalizedMessageId(valuesMessage);\n if (id == null) {\n merged.push(valuesMessage);\n continue;\n }\n\n valueMessageIds.add(id);\n const streamIdx = currentIndexById.get(id);\n const canUseStreamed =\n streamIdx != null &&\n (streamedMessageIds == null || streamedMessageIds.has(id));\n const streamedMessage = canUseStreamed\n ? currentMessages[streamIdx]\n : undefined;\n\n if (\n streamedMessage != null &&\n preferValuesMessage?.(valuesMessage, streamedMessage) !== true\n ) {\n merged.push(streamedMessage);\n } else {\n merged.push(valuesMessage);\n }\n }\n\n for (const existing of currentMessages) {\n const id = normalizedMessageId(existing);\n if (id == null) continue;\n if (valueMessageIds.has(id)) continue;\n if (previousValueMessageIds.has(id)) continue;\n if (streamedMessageIds != null && !streamedMessageIds.has(id)) continue;\n merged.push(existing);\n }\n\n return {\n messages: messagesEqualList(currentMessages, merged)\n ? currentMessages\n : merged,\n valueMessageIds,\n };\n}\n\n/**\n * Build a position index for keyed messages.\n */\nexport function buildMessageIndex(\n messages: readonly BaseMessage[]\n): Map<string, number> {\n const index = new Map<string, number>();\n messages.forEach((message, idx) => {\n const id = normalizedMessageId(message);\n if (id != null) index.set(id, idx);\n });\n return index;\n}\n\n/**\n * Decide whether a values message carries tool-call data missing from the\n * streamed message.\n */\nexport function shouldPreferValuesMessageForToolCalls(\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n): boolean {\n const valuesToolCalls = getMessageToolCalls(valuesMessage);\n if (valuesToolCalls.length === 0) return false;\n\n const streamedToolCalls = getMessageToolCalls(streamedMessage);\n if (streamedToolCalls.length < valuesToolCalls.length) return true;\n\n const streamedIds = new Set(\n streamedToolCalls\n .map((toolCall) => toolCall.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n );\n if (\n valuesToolCalls.some((toolCall) => {\n return typeof toolCall.id === \"string\" && !streamedIds.has(toolCall.id);\n })\n ) {\n return true;\n }\n\n // Values snapshots carry the finalized tool-call args. Prefer them only when\n // they add meaningful data, so empty placeholder args do not replace an\n // otherwise useful streamed message.\n return valuesToolCalls.some((valuesToolCall) => {\n const streamedToolCall = streamedToolCalls.find(\n (candidate) =>\n typeof valuesToolCall.id === \"string\" &&\n candidate.id === valuesToolCall.id\n );\n return (\n streamedToolCall != null &&\n hasMeaningfulArgs(valuesToolCall.args) &&\n !jsonishEqual(valuesToolCall.args, streamedToolCall.args)\n );\n });\n}\n\nfunction hasMeaningfulArgs(args: unknown): boolean {\n if (args == null) return false;\n if (typeof args === \"string\") return args.length > 0;\n if (typeof args === \"object\") return Object.keys(args).length > 0;\n return true;\n}\n\nexport function messagesEqualList(\n previous: readonly BaseMessage[],\n next: readonly BaseMessage[]\n): boolean {\n if (previous === next) return true;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!messagesEqual(previous[i], next[i])) return false;\n }\n return true;\n}\n\nexport function messagesEqual(\n previous: BaseMessage | undefined,\n next: BaseMessage | undefined\n): boolean {\n if (previous === next) return true;\n if (previous == null || next == null) return false;\n const previousRecord = previous as unknown as Record<string, unknown>;\n const nextRecord = next as unknown as Record<string, unknown>;\n const previousType =\n typeof previous.getType === \"function\"\n ? previous.getType()\n : previousRecord.type;\n const nextType =\n typeof next.getType === \"function\" ? next.getType() : nextRecord.type;\n\n return (\n previous.id === next.id &&\n previousType === nextType &&\n jsonishEqual(previous.content, next.content) &&\n previousRecord.tool_call_id === nextRecord.tool_call_id &&\n previousRecord.status === nextRecord.status &&\n jsonishEqual(\n previousRecord.additional_kwargs,\n nextRecord.additional_kwargs\n ) &&\n jsonishEqual(\n previousRecord.response_metadata,\n nextRecord.response_metadata\n ) &&\n jsonishEqual(previousRecord.tool_calls, nextRecord.tool_calls) &&\n jsonishEqual(\n previousRecord.tool_call_chunks,\n nextRecord.tool_call_chunks\n ) &&\n jsonishEqual(previousRecord.usage_metadata, nextRecord.usage_metadata)\n );\n}\n\nfunction normalizedMessageId(message: BaseMessage): string | undefined {\n return typeof message.id === \"string\" && message.id.length > 0\n ? message.id\n : undefined;\n}\n\nfunction getMessageToolCalls(\n message: BaseMessage\n): Array<{ id?: string; name?: string; args?: unknown }> {\n const raw = (message as unknown as { tool_calls?: unknown }).tool_calls;\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (toolCall): toolCall is { id?: string; name?: string; args?: unknown } =>\n toolCall != null && typeof toolCall === \"object\"\n );\n}\n\nfunction jsonishEqual(previous: unknown, next: unknown): boolean {\n return jsonishEqualAtDepth(previous, next, 0);\n}\n\nfunction jsonishEqualAtDepth(\n previous: unknown,\n next: unknown,\n depth: number\n): boolean {\n if (Object.is(previous, next)) return true;\n if (previous == null || next == null) return false;\n if (typeof previous !== \"object\" || typeof next !== \"object\") return false;\n if (depth >= 4) return false;\n\n if (Array.isArray(previous) || Array.isArray(next)) {\n if (!Array.isArray(previous) || !Array.isArray(next)) return false;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!jsonishEqualAtDepth(previous[i], next[i], depth + 1)) return false;\n }\n return true;\n }\n\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord).filter(\n (key) => typeof previousRecord[key] !== \"function\"\n );\n const nextKeys = Object.keys(nextRecord).filter(\n (key) => typeof nextRecord[key] !== \"function\"\n );\n if (previousKeys.length !== nextKeys.length) return false;\n\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n if (!jsonishEqualAtDepth(previousRecord[key], nextRecord[key], depth + 1)) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -57,10 +57,20 @@ function shouldPreferValuesMessageForToolCalls(valuesMessage, streamedMessage) {
|
|
|
57
57
|
const streamedToolCalls = getMessageToolCalls(streamedMessage);
|
|
58
58
|
if (streamedToolCalls.length < valuesToolCalls.length) return true;
|
|
59
59
|
const streamedIds = new Set(streamedToolCalls.map((toolCall) => toolCall.id).filter((id) => typeof id === "string" && id.length > 0));
|
|
60
|
-
|
|
60
|
+
if (valuesToolCalls.some((toolCall) => {
|
|
61
61
|
return typeof toolCall.id === "string" && !streamedIds.has(toolCall.id);
|
|
62
|
+
})) return true;
|
|
63
|
+
return valuesToolCalls.some((valuesToolCall) => {
|
|
64
|
+
const streamedToolCall = streamedToolCalls.find((candidate) => typeof valuesToolCall.id === "string" && candidate.id === valuesToolCall.id);
|
|
65
|
+
return streamedToolCall != null && hasMeaningfulArgs(valuesToolCall.args) && !jsonishEqual(valuesToolCall.args, streamedToolCall.args);
|
|
62
66
|
});
|
|
63
67
|
}
|
|
68
|
+
function hasMeaningfulArgs(args) {
|
|
69
|
+
if (args == null) return false;
|
|
70
|
+
if (typeof args === "string") return args.length > 0;
|
|
71
|
+
if (typeof args === "object") return Object.keys(args).length > 0;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
64
74
|
function messagesEqualList(previous, next) {
|
|
65
75
|
if (previous === next) return true;
|
|
66
76
|
if (previous.length !== next.length) return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-reconciliation.js","names":[],"sources":["../../src/stream/message-reconciliation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\n\nexport interface ReconcileMessagesFromValuesOptions {\n /**\n * Messages from the authoritative `values.messages` snapshot.\n */\n readonly valueMessages: readonly BaseMessage[];\n /**\n * Current message projection, including stream-assembled in-flight messages.\n */\n readonly currentMessages: readonly BaseMessage[];\n /**\n * Index from message id to current message position.\n */\n readonly currentIndexById: ReadonlyMap<string, number>;\n /**\n * Ids observed in the most recent previous `values.messages` snapshot.\n * If one of these ids is missing from the next snapshot, it is treated as\n * an explicit server-side removal.\n */\n readonly previousValueMessageIds: ReadonlySet<string>;\n /**\n * Optional stream-id filter. When supplied, only these current ids are\n * eligible to override the values snapshot. When omitted, any id present in\n * `currentIndexById` is eligible, preserving the root controller's historic\n * behavior.\n */\n readonly streamedMessageIds?: ReadonlySet<string>;\n /**\n * Allows callers to keep a values message even when a streamed message with\n * the same id exists. Used by the root controller when the values message\n * carries finalized tool-call data missing from the streamed message.\n */\n readonly preferValuesMessage?: (\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n ) => boolean;\n}\n\nexport interface ReconciledMessages {\n readonly messages: readonly BaseMessage[];\n readonly valueMessageIds: Set<string>;\n}\n\n/**\n * Merge an authoritative `values.messages` snapshot with the current streamed\n * message projection.\n *\n * Values remain authoritative for ordering and removals. Streamed messages\n * remain authoritative for in-flight content until the server echoes them in a\n * values snapshot, and stream-only messages are preserved until they either\n * appear in values or are known to have been removed.\n */\nexport function reconcileMessagesFromValues({\n valueMessages,\n currentMessages,\n currentIndexById,\n previousValueMessageIds,\n streamedMessageIds,\n preferValuesMessage,\n}: ReconcileMessagesFromValuesOptions): ReconciledMessages {\n const valueMessageIds = new Set<string>();\n const merged: BaseMessage[] = [];\n\n for (const valuesMessage of valueMessages) {\n const id = normalizedMessageId(valuesMessage);\n if (id == null) {\n merged.push(valuesMessage);\n continue;\n }\n\n valueMessageIds.add(id);\n const streamIdx = currentIndexById.get(id);\n const canUseStreamed =\n streamIdx != null &&\n (streamedMessageIds == null || streamedMessageIds.has(id));\n const streamedMessage = canUseStreamed\n ? currentMessages[streamIdx]\n : undefined;\n\n if (\n streamedMessage != null &&\n preferValuesMessage?.(valuesMessage, streamedMessage) !== true\n ) {\n merged.push(streamedMessage);\n } else {\n merged.push(valuesMessage);\n }\n }\n\n for (const existing of currentMessages) {\n const id = normalizedMessageId(existing);\n if (id == null) continue;\n if (valueMessageIds.has(id)) continue;\n if (previousValueMessageIds.has(id)) continue;\n if (streamedMessageIds != null && !streamedMessageIds.has(id)) continue;\n merged.push(existing);\n }\n\n return {\n messages: messagesEqualList(currentMessages, merged)\n ? currentMessages\n : merged,\n valueMessageIds,\n };\n}\n\n/**\n * Build a position index for keyed messages.\n */\nexport function buildMessageIndex(\n messages: readonly BaseMessage[]\n): Map<string, number> {\n const index = new Map<string, number>();\n messages.forEach((message, idx) => {\n const id = normalizedMessageId(message);\n if (id != null) index.set(id, idx);\n });\n return index;\n}\n\n/**\n * Decide whether a values message carries tool-call data missing from the\n * streamed message.\n */\nexport function shouldPreferValuesMessageForToolCalls(\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n): boolean {\n const valuesToolCalls = getMessageToolCalls(valuesMessage);\n if (valuesToolCalls.length === 0) return false;\n\n const streamedToolCalls = getMessageToolCalls(streamedMessage);\n if (streamedToolCalls.length < valuesToolCalls.length) return true;\n\n const streamedIds = new Set(\n streamedToolCalls\n .map((toolCall) => toolCall.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n );\n return valuesToolCalls.some((toolCall) => {\n return typeof toolCall.id === \"string\" && !streamedIds.has(toolCall.id);\n });\n}\n\nexport function messagesEqualList(\n previous: readonly BaseMessage[],\n next: readonly BaseMessage[]\n): boolean {\n if (previous === next) return true;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!messagesEqual(previous[i], next[i])) return false;\n }\n return true;\n}\n\nexport function messagesEqual(\n previous: BaseMessage | undefined,\n next: BaseMessage | undefined\n): boolean {\n if (previous === next) return true;\n if (previous == null || next == null) return false;\n const previousRecord = previous as unknown as Record<string, unknown>;\n const nextRecord = next as unknown as Record<string, unknown>;\n const previousType =\n typeof previous.getType === \"function\"\n ? previous.getType()\n : previousRecord.type;\n const nextType =\n typeof next.getType === \"function\" ? next.getType() : nextRecord.type;\n\n return (\n previous.id === next.id &&\n previousType === nextType &&\n jsonishEqual(previous.content, next.content) &&\n previousRecord.tool_call_id === nextRecord.tool_call_id &&\n previousRecord.status === nextRecord.status &&\n jsonishEqual(\n previousRecord.additional_kwargs,\n nextRecord.additional_kwargs\n ) &&\n jsonishEqual(\n previousRecord.response_metadata,\n nextRecord.response_metadata\n ) &&\n jsonishEqual(previousRecord.tool_calls, nextRecord.tool_calls) &&\n jsonishEqual(\n previousRecord.tool_call_chunks,\n nextRecord.tool_call_chunks\n ) &&\n jsonishEqual(previousRecord.usage_metadata, nextRecord.usage_metadata)\n );\n}\n\nfunction normalizedMessageId(message: BaseMessage): string | undefined {\n return typeof message.id === \"string\" && message.id.length > 0\n ? message.id\n : undefined;\n}\n\nfunction getMessageToolCalls(\n message: BaseMessage\n): Array<{ id?: string; name?: string }> {\n const raw = (message as unknown as { tool_calls?: unknown }).tool_calls;\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (toolCall): toolCall is { id?: string; name?: string } =>\n toolCall != null && typeof toolCall === \"object\"\n );\n}\n\nfunction jsonishEqual(previous: unknown, next: unknown): boolean {\n return jsonishEqualAtDepth(previous, next, 0);\n}\n\nfunction jsonishEqualAtDepth(\n previous: unknown,\n next: unknown,\n depth: number\n): boolean {\n if (Object.is(previous, next)) return true;\n if (previous == null || next == null) return false;\n if (typeof previous !== \"object\" || typeof next !== \"object\") return false;\n if (depth >= 4) return false;\n\n if (Array.isArray(previous) || Array.isArray(next)) {\n if (!Array.isArray(previous) || !Array.isArray(next)) return false;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!jsonishEqualAtDepth(previous[i], next[i], depth + 1)) return false;\n }\n return true;\n }\n\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord).filter(\n (key) => typeof previousRecord[key] !== \"function\"\n );\n const nextKeys = Object.keys(nextRecord).filter(\n (key) => typeof nextRecord[key] !== \"function\"\n );\n if (previousKeys.length !== nextKeys.length) return false;\n\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n if (!jsonishEqualAtDepth(previousRecord[key], nextRecord[key], depth + 1)) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message-reconciliation.js","names":[],"sources":["../../src/stream/message-reconciliation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\n\nexport interface ReconcileMessagesFromValuesOptions {\n /**\n * Messages from the authoritative `values.messages` snapshot.\n */\n readonly valueMessages: readonly BaseMessage[];\n /**\n * Current message projection, including stream-assembled in-flight messages.\n */\n readonly currentMessages: readonly BaseMessage[];\n /**\n * Index from message id to current message position.\n */\n readonly currentIndexById: ReadonlyMap<string, number>;\n /**\n * Ids observed in the most recent previous `values.messages` snapshot.\n * If one of these ids is missing from the next snapshot, it is treated as\n * an explicit server-side removal.\n */\n readonly previousValueMessageIds: ReadonlySet<string>;\n /**\n * Optional stream-id filter. When supplied, only these current ids are\n * eligible to override the values snapshot. When omitted, any id present in\n * `currentIndexById` is eligible, preserving the root controller's historic\n * behavior.\n */\n readonly streamedMessageIds?: ReadonlySet<string>;\n /**\n * Allows callers to keep a values message even when a streamed message with\n * the same id exists. Used by the root controller when the values message\n * carries finalized tool-call data missing from the streamed message.\n */\n readonly preferValuesMessage?: (\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n ) => boolean;\n}\n\nexport interface ReconciledMessages {\n readonly messages: readonly BaseMessage[];\n readonly valueMessageIds: Set<string>;\n}\n\n/**\n * Merge an authoritative `values.messages` snapshot with the current streamed\n * message projection.\n *\n * Values remain authoritative for ordering and removals. Streamed messages\n * remain authoritative for in-flight content until the server echoes them in a\n * values snapshot, and stream-only messages are preserved until they either\n * appear in values or are known to have been removed.\n */\nexport function reconcileMessagesFromValues({\n valueMessages,\n currentMessages,\n currentIndexById,\n previousValueMessageIds,\n streamedMessageIds,\n preferValuesMessage,\n}: ReconcileMessagesFromValuesOptions): ReconciledMessages {\n const valueMessageIds = new Set<string>();\n const merged: BaseMessage[] = [];\n\n for (const valuesMessage of valueMessages) {\n const id = normalizedMessageId(valuesMessage);\n if (id == null) {\n merged.push(valuesMessage);\n continue;\n }\n\n valueMessageIds.add(id);\n const streamIdx = currentIndexById.get(id);\n const canUseStreamed =\n streamIdx != null &&\n (streamedMessageIds == null || streamedMessageIds.has(id));\n const streamedMessage = canUseStreamed\n ? currentMessages[streamIdx]\n : undefined;\n\n if (\n streamedMessage != null &&\n preferValuesMessage?.(valuesMessage, streamedMessage) !== true\n ) {\n merged.push(streamedMessage);\n } else {\n merged.push(valuesMessage);\n }\n }\n\n for (const existing of currentMessages) {\n const id = normalizedMessageId(existing);\n if (id == null) continue;\n if (valueMessageIds.has(id)) continue;\n if (previousValueMessageIds.has(id)) continue;\n if (streamedMessageIds != null && !streamedMessageIds.has(id)) continue;\n merged.push(existing);\n }\n\n return {\n messages: messagesEqualList(currentMessages, merged)\n ? currentMessages\n : merged,\n valueMessageIds,\n };\n}\n\n/**\n * Build a position index for keyed messages.\n */\nexport function buildMessageIndex(\n messages: readonly BaseMessage[]\n): Map<string, number> {\n const index = new Map<string, number>();\n messages.forEach((message, idx) => {\n const id = normalizedMessageId(message);\n if (id != null) index.set(id, idx);\n });\n return index;\n}\n\n/**\n * Decide whether a values message carries tool-call data missing from the\n * streamed message.\n */\nexport function shouldPreferValuesMessageForToolCalls(\n valuesMessage: BaseMessage,\n streamedMessage: BaseMessage\n): boolean {\n const valuesToolCalls = getMessageToolCalls(valuesMessage);\n if (valuesToolCalls.length === 0) return false;\n\n const streamedToolCalls = getMessageToolCalls(streamedMessage);\n if (streamedToolCalls.length < valuesToolCalls.length) return true;\n\n const streamedIds = new Set(\n streamedToolCalls\n .map((toolCall) => toolCall.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n );\n if (\n valuesToolCalls.some((toolCall) => {\n return typeof toolCall.id === \"string\" && !streamedIds.has(toolCall.id);\n })\n ) {\n return true;\n }\n\n // Values snapshots carry the finalized tool-call args. Prefer them only when\n // they add meaningful data, so empty placeholder args do not replace an\n // otherwise useful streamed message.\n return valuesToolCalls.some((valuesToolCall) => {\n const streamedToolCall = streamedToolCalls.find(\n (candidate) =>\n typeof valuesToolCall.id === \"string\" &&\n candidate.id === valuesToolCall.id\n );\n return (\n streamedToolCall != null &&\n hasMeaningfulArgs(valuesToolCall.args) &&\n !jsonishEqual(valuesToolCall.args, streamedToolCall.args)\n );\n });\n}\n\nfunction hasMeaningfulArgs(args: unknown): boolean {\n if (args == null) return false;\n if (typeof args === \"string\") return args.length > 0;\n if (typeof args === \"object\") return Object.keys(args).length > 0;\n return true;\n}\n\nexport function messagesEqualList(\n previous: readonly BaseMessage[],\n next: readonly BaseMessage[]\n): boolean {\n if (previous === next) return true;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!messagesEqual(previous[i], next[i])) return false;\n }\n return true;\n}\n\nexport function messagesEqual(\n previous: BaseMessage | undefined,\n next: BaseMessage | undefined\n): boolean {\n if (previous === next) return true;\n if (previous == null || next == null) return false;\n const previousRecord = previous as unknown as Record<string, unknown>;\n const nextRecord = next as unknown as Record<string, unknown>;\n const previousType =\n typeof previous.getType === \"function\"\n ? previous.getType()\n : previousRecord.type;\n const nextType =\n typeof next.getType === \"function\" ? next.getType() : nextRecord.type;\n\n return (\n previous.id === next.id &&\n previousType === nextType &&\n jsonishEqual(previous.content, next.content) &&\n previousRecord.tool_call_id === nextRecord.tool_call_id &&\n previousRecord.status === nextRecord.status &&\n jsonishEqual(\n previousRecord.additional_kwargs,\n nextRecord.additional_kwargs\n ) &&\n jsonishEqual(\n previousRecord.response_metadata,\n nextRecord.response_metadata\n ) &&\n jsonishEqual(previousRecord.tool_calls, nextRecord.tool_calls) &&\n jsonishEqual(\n previousRecord.tool_call_chunks,\n nextRecord.tool_call_chunks\n ) &&\n jsonishEqual(previousRecord.usage_metadata, nextRecord.usage_metadata)\n );\n}\n\nfunction normalizedMessageId(message: BaseMessage): string | undefined {\n return typeof message.id === \"string\" && message.id.length > 0\n ? message.id\n : undefined;\n}\n\nfunction getMessageToolCalls(\n message: BaseMessage\n): Array<{ id?: string; name?: string; args?: unknown }> {\n const raw = (message as unknown as { tool_calls?: unknown }).tool_calls;\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (toolCall): toolCall is { id?: string; name?: string; args?: unknown } =>\n toolCall != null && typeof toolCall === \"object\"\n );\n}\n\nfunction jsonishEqual(previous: unknown, next: unknown): boolean {\n return jsonishEqualAtDepth(previous, next, 0);\n}\n\nfunction jsonishEqualAtDepth(\n previous: unknown,\n next: unknown,\n depth: number\n): boolean {\n if (Object.is(previous, next)) return true;\n if (previous == null || next == null) return false;\n if (typeof previous !== \"object\" || typeof next !== \"object\") return false;\n if (depth >= 4) return false;\n\n if (Array.isArray(previous) || Array.isArray(next)) {\n if (!Array.isArray(previous) || !Array.isArray(next)) return false;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!jsonishEqualAtDepth(previous[i], next[i], depth + 1)) return false;\n }\n return true;\n }\n\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord).filter(\n (key) => typeof previousRecord[key] !== \"function\"\n );\n const nextKeys = Object.keys(nextRecord).filter(\n (key) => typeof nextRecord[key] !== \"function\"\n );\n if (previousKeys.length !== nextKeys.length) return false;\n\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n if (!jsonishEqualAtDepth(previousRecord[key], nextRecord[key], depth + 1)) {\n return false;\n }\n }\n return true;\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"}
|