@langchain/langgraph-sdk 1.8.9 → 1.9.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/README.md +90 -43
- package/dist/client/assistants/index.cjs +180 -0
- package/dist/client/assistants/index.cjs.map +1 -0
- package/dist/client/assistants/index.d.cts +155 -0
- package/dist/client/assistants/index.d.cts.map +1 -0
- package/dist/client/assistants/index.d.ts +155 -0
- package/dist/client/assistants/index.d.ts.map +1 -0
- package/dist/client/assistants/index.js +180 -0
- package/dist/client/assistants/index.js.map +1 -0
- package/dist/client/base.cjs +190 -0
- package/dist/client/base.cjs.map +1 -0
- package/dist/client/base.d.cts +84 -0
- package/dist/client/base.d.cts.map +1 -0
- package/dist/client/base.d.ts +84 -0
- package/dist/client/base.d.ts.map +1 -0
- package/dist/client/base.js +188 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/crons/index.cjs +159 -0
- package/dist/client/crons/index.cjs.map +1 -0
- package/dist/client/crons/index.d.cts +71 -0
- package/dist/client/crons/index.d.cts.map +1 -0
- package/dist/client/crons/index.d.ts +71 -0
- package/dist/client/crons/index.d.ts.map +1 -0
- package/dist/client/crons/index.js +159 -0
- package/dist/client/crons/index.js.map +1 -0
- package/dist/client/index.cjs +84 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +63 -0
- package/dist/client/index.d.cts.map +1 -0
- package/dist/client/index.d.ts +63 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +83 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/runs/index.cjs +275 -0
- package/dist/client/runs/index.cjs.map +1 -0
- package/dist/client/runs/index.d.cts +123 -0
- package/dist/client/runs/index.d.cts.map +1 -0
- package/dist/client/runs/index.d.ts +123 -0
- package/dist/client/runs/index.d.ts.map +1 -0
- package/dist/client/runs/index.js +275 -0
- package/dist/client/runs/index.js.map +1 -0
- package/dist/client/store/index.cjs +128 -0
- package/dist/client/store/index.cjs.map +1 -0
- package/dist/client/store/index.d.cts +75 -0
- package/dist/client/store/index.d.cts.map +1 -0
- package/dist/client/store/index.d.ts +75 -0
- package/dist/client/store/index.d.ts.map +1 -0
- package/dist/client/store/index.js +128 -0
- package/dist/client/store/index.js.map +1 -0
- package/dist/client/stream/error.cjs +18 -0
- package/dist/client/stream/error.cjs.map +1 -0
- package/dist/client/stream/error.d.cts +14 -0
- package/dist/client/stream/error.d.cts.map +1 -0
- package/dist/client/stream/error.d.ts +14 -0
- package/dist/client/stream/error.d.ts.map +1 -0
- package/dist/client/stream/error.js +18 -0
- package/dist/client/stream/error.js.map +1 -0
- package/dist/client/stream/handles/index.cjs +3 -0
- package/dist/client/stream/handles/index.d.ts +3 -0
- package/dist/client/stream/handles/index.js +4 -0
- package/dist/client/stream/handles/subagents.cjs +263 -0
- package/dist/client/stream/handles/subagents.cjs.map +1 -0
- package/dist/client/stream/handles/subagents.d.cts +45 -0
- package/dist/client/stream/handles/subagents.d.cts.map +1 -0
- package/dist/client/stream/handles/subagents.d.ts +45 -0
- package/dist/client/stream/handles/subagents.d.ts.map +1 -0
- package/dist/client/stream/handles/subagents.js +262 -0
- package/dist/client/stream/handles/subagents.js.map +1 -0
- package/dist/client/stream/handles/subgraphs.cjs +352 -0
- package/dist/client/stream/handles/subgraphs.cjs.map +1 -0
- package/dist/client/stream/handles/subgraphs.d.cts +82 -0
- package/dist/client/stream/handles/subgraphs.d.cts.map +1 -0
- package/dist/client/stream/handles/subgraphs.d.ts +82 -0
- package/dist/client/stream/handles/subgraphs.d.ts.map +1 -0
- package/dist/client/stream/handles/subgraphs.js +351 -0
- package/dist/client/stream/handles/subgraphs.js.map +1 -0
- package/dist/client/stream/handles/tools.cjs +92 -0
- package/dist/client/stream/handles/tools.cjs.map +1 -0
- package/dist/client/stream/handles/tools.d.cts +26 -0
- package/dist/client/stream/handles/tools.d.cts.map +1 -0
- package/dist/client/stream/handles/tools.d.ts +26 -0
- package/dist/client/stream/handles/tools.d.ts.map +1 -0
- package/dist/client/stream/handles/tools.js +92 -0
- package/dist/client/stream/handles/tools.js.map +1 -0
- package/dist/client/stream/index.cjs +1368 -0
- package/dist/client/stream/index.cjs.map +1 -0
- package/dist/client/stream/index.d.cts +238 -0
- package/dist/client/stream/index.d.cts.map +1 -0
- package/dist/client/stream/index.d.ts +238 -0
- package/dist/client/stream/index.d.ts.map +1 -0
- package/dist/client/stream/index.js +1367 -0
- package/dist/client/stream/index.js.map +1 -0
- package/dist/client/stream/media.cjs +506 -0
- package/dist/client/stream/media.cjs.map +1 -0
- package/dist/client/stream/media.d.cts +164 -0
- package/dist/client/stream/media.d.cts.map +1 -0
- package/dist/client/stream/media.d.ts +164 -0
- package/dist/client/stream/media.d.ts.map +1 -0
- package/dist/client/stream/media.js +505 -0
- package/dist/client/stream/media.js.map +1 -0
- package/dist/client/stream/messages.cjs +635 -0
- package/dist/client/stream/messages.cjs.map +1 -0
- package/dist/client/stream/messages.d.cts +139 -0
- package/dist/client/stream/messages.d.cts.map +1 -0
- package/dist/client/stream/messages.d.ts +139 -0
- package/dist/client/stream/messages.d.ts.map +1 -0
- package/dist/client/stream/messages.js +631 -0
- package/dist/client/stream/messages.js.map +1 -0
- package/dist/client/stream/multi-cursor-buffer.cjs +55 -0
- package/dist/client/stream/multi-cursor-buffer.cjs.map +1 -0
- package/dist/client/stream/multi-cursor-buffer.js +55 -0
- package/dist/client/stream/multi-cursor-buffer.js.map +1 -0
- package/dist/client/stream/subscription.cjs +85 -0
- package/dist/client/stream/subscription.cjs.map +1 -0
- package/dist/client/stream/subscription.d.cts +22 -0
- package/dist/client/stream/subscription.d.cts.map +1 -0
- package/dist/client/stream/subscription.d.ts +22 -0
- package/dist/client/stream/subscription.d.ts.map +1 -0
- package/dist/client/stream/subscription.js +84 -0
- package/dist/client/stream/subscription.js.map +1 -0
- package/dist/client/stream/transport/agent-server.cjs +45 -0
- package/dist/client/stream/transport/agent-server.cjs.map +1 -0
- package/dist/client/stream/transport/agent-server.d.cts +39 -0
- package/dist/client/stream/transport/agent-server.d.cts.map +1 -0
- package/dist/client/stream/transport/agent-server.d.ts +39 -0
- package/dist/client/stream/transport/agent-server.d.ts.map +1 -0
- package/dist/client/stream/transport/agent-server.js +45 -0
- package/dist/client/stream/transport/agent-server.js.map +1 -0
- package/dist/client/stream/transport/constants.cjs +10 -0
- package/dist/client/stream/transport/constants.cjs.map +1 -0
- package/dist/client/stream/transport/constants.js +10 -0
- package/dist/client/stream/transport/constants.js.map +1 -0
- package/dist/client/stream/transport/decoder.cjs +115 -0
- package/dist/client/stream/transport/decoder.cjs.map +1 -0
- package/dist/client/stream/transport/decoder.js +114 -0
- package/dist/client/stream/transport/decoder.js.map +1 -0
- package/dist/client/stream/transport/http.cjs +183 -0
- package/dist/client/stream/transport/http.cjs.map +1 -0
- package/dist/client/stream/transport/http.d.cts +45 -0
- package/dist/client/stream/transport/http.d.cts.map +1 -0
- package/dist/client/stream/transport/http.d.ts +45 -0
- package/dist/client/stream/transport/http.d.ts.map +1 -0
- package/dist/client/stream/transport/http.js +183 -0
- package/dist/client/stream/transport/http.js.map +1 -0
- package/dist/client/stream/transport/index.cjs +3 -0
- package/dist/client/stream/transport/index.js +4 -0
- package/dist/client/stream/transport/queue.cjs +55 -0
- package/dist/client/stream/transport/queue.cjs.map +1 -0
- package/dist/client/stream/transport/queue.js +55 -0
- package/dist/client/stream/transport/queue.js.map +1 -0
- package/dist/client/stream/transport/stream.cjs +79 -0
- package/dist/client/stream/transport/stream.cjs.map +1 -0
- package/dist/client/stream/transport/stream.js +79 -0
- package/dist/client/stream/transport/stream.js.map +1 -0
- package/dist/client/stream/transport/types.d.cts +29 -0
- package/dist/client/stream/transport/types.d.cts.map +1 -0
- package/dist/client/stream/transport/types.d.ts +29 -0
- package/dist/client/stream/transport/types.d.ts.map +1 -0
- package/dist/client/stream/transport/utils.cjs +45 -0
- package/dist/client/stream/transport/utils.cjs.map +1 -0
- package/dist/client/stream/transport/utils.js +39 -0
- package/dist/client/stream/transport/utils.js.map +1 -0
- package/dist/client/stream/transport/websocket.cjs +155 -0
- package/dist/client/stream/transport/websocket.cjs.map +1 -0
- package/dist/client/stream/transport/websocket.d.cts +36 -0
- package/dist/client/stream/transport/websocket.d.cts.map +1 -0
- package/dist/client/stream/transport/websocket.d.ts +36 -0
- package/dist/client/stream/transport/websocket.d.ts.map +1 -0
- package/dist/client/stream/transport/websocket.js +155 -0
- package/dist/client/stream/transport/websocket.js.map +1 -0
- package/dist/client/stream/transport.d.cts +104 -0
- package/dist/client/stream/transport.d.cts.map +1 -0
- package/dist/client/stream/transport.d.ts +104 -0
- package/dist/client/stream/transport.d.ts.map +1 -0
- package/dist/client/stream/types.d.cts +208 -0
- package/dist/client/stream/types.d.cts.map +1 -0
- package/dist/client/stream/types.d.ts +208 -0
- package/dist/client/stream/types.d.ts.map +1 -0
- package/dist/client/threads/index.cjs +271 -0
- package/dist/client/threads/index.cjs.map +1 -0
- package/dist/client/threads/index.d.cts +235 -0
- package/dist/client/threads/index.d.cts.map +1 -0
- package/dist/client/threads/index.d.ts +235 -0
- package/dist/client/threads/index.d.ts.map +1 -0
- package/dist/client/threads/index.js +270 -0
- package/dist/client/threads/index.js.map +1 -0
- package/dist/client/ui-internal/index.cjs +29 -0
- package/dist/client/ui-internal/index.cjs.map +1 -0
- package/dist/client/ui-internal/index.d.cts +11 -0
- package/dist/client/ui-internal/index.d.cts.map +1 -0
- package/dist/client/ui-internal/index.d.ts +11 -0
- package/dist/client/ui-internal/index.d.ts.map +1 -0
- package/dist/client/ui-internal/index.js +29 -0
- package/dist/client/ui-internal/index.js.map +1 -0
- package/dist/client.cjs +35 -1308
- package/dist/client.d.cts +19 -857
- package/dist/client.d.ts +19 -857
- package/dist/client.js +16 -1301
- package/dist/index.cjs +25 -4
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +14 -3
- package/dist/react/stream.cjs.map +1 -1
- package/dist/react/stream.custom.cjs +1 -1
- package/dist/react/stream.custom.js +1 -1
- package/dist/react/stream.d.cts +2 -1
- package/dist/react/stream.d.cts.map +1 -1
- package/dist/react/stream.d.ts +2 -1
- package/dist/react/stream.d.ts.map +1 -1
- package/dist/react/stream.js.map +1 -1
- package/dist/react/stream.lgp.cjs +8 -6
- package/dist/react/stream.lgp.cjs.map +1 -1
- package/dist/react/stream.lgp.js +6 -4
- package/dist/react/stream.lgp.js.map +1 -1
- package/dist/react/types.d.cts +1 -1
- package/dist/react/types.d.ts +1 -1
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react-ui/server/server.cjs +1 -1
- package/dist/react-ui/server/server.cjs.map +1 -1
- package/dist/react-ui/server/server.js +1 -1
- package/dist/react-ui/server/server.js.map +1 -1
- package/dist/react-ui/types.cjs.map +1 -1
- package/dist/react-ui/types.d.cts +1 -1
- package/dist/react-ui/types.d.cts.map +1 -1
- package/dist/react-ui/types.d.ts +1 -1
- package/dist/react-ui/types.d.ts.map +1 -1
- package/dist/react-ui/types.js.map +1 -1
- package/dist/stream/assembled-to-message.cjs +121 -0
- package/dist/stream/assembled-to-message.cjs.map +1 -0
- package/dist/stream/assembled-to-message.d.cts +35 -0
- package/dist/stream/assembled-to-message.d.cts.map +1 -0
- package/dist/stream/assembled-to-message.d.ts +35 -0
- package/dist/stream/assembled-to-message.d.ts.map +1 -0
- package/dist/stream/assembled-to-message.js +119 -0
- package/dist/stream/assembled-to-message.js.map +1 -0
- package/dist/stream/channel-registry.cjs +224 -0
- package/dist/stream/channel-registry.cjs.map +1 -0
- package/dist/stream/channel-registry.d.cts +102 -0
- package/dist/stream/channel-registry.d.cts.map +1 -0
- package/dist/stream/channel-registry.d.ts +102 -0
- package/dist/stream/channel-registry.d.ts.map +1 -0
- package/dist/stream/channel-registry.js +224 -0
- package/dist/stream/channel-registry.js.map +1 -0
- package/dist/stream/constants.cjs +11 -0
- package/dist/stream/constants.cjs.map +1 -0
- package/dist/stream/constants.d.cts +10 -0
- package/dist/stream/constants.d.cts.map +1 -0
- package/dist/stream/constants.d.ts +10 -0
- package/dist/stream/constants.d.ts.map +1 -0
- package/dist/stream/constants.js +11 -0
- package/dist/stream/constants.js.map +1 -0
- package/dist/stream/controller.cjs +933 -0
- package/dist/stream/controller.cjs.map +1 -0
- package/dist/stream/controller.d.cts +135 -0
- package/dist/stream/controller.d.cts.map +1 -0
- package/dist/stream/controller.d.ts +135 -0
- package/dist/stream/controller.d.ts.map +1 -0
- package/dist/stream/controller.js +910 -0
- package/dist/stream/controller.js.map +1 -0
- package/dist/stream/discovery/index.d.ts +2 -0
- package/dist/stream/discovery/subagents.cjs +235 -0
- package/dist/stream/discovery/subagents.cjs.map +1 -0
- package/dist/stream/discovery/subagents.d.cts +18 -0
- package/dist/stream/discovery/subagents.d.cts.map +1 -0
- package/dist/stream/discovery/subagents.d.ts +18 -0
- package/dist/stream/discovery/subagents.d.ts.map +1 -0
- package/dist/stream/discovery/subagents.js +235 -0
- package/dist/stream/discovery/subagents.js.map +1 -0
- package/dist/stream/discovery/subgraphs.cjs +153 -0
- package/dist/stream/discovery/subgraphs.cjs.map +1 -0
- package/dist/stream/discovery/subgraphs.d.cts +19 -0
- package/dist/stream/discovery/subgraphs.d.cts.map +1 -0
- package/dist/stream/discovery/subgraphs.d.ts +19 -0
- package/dist/stream/discovery/subgraphs.d.ts.map +1 -0
- package/dist/stream/discovery/subgraphs.js +153 -0
- package/dist/stream/discovery/subgraphs.js.map +1 -0
- package/dist/stream/index.cjs +36 -0
- package/dist/stream/index.d.cts +25 -0
- package/dist/stream/index.d.ts +25 -0
- package/dist/stream/index.js +16 -0
- package/dist/stream/lifecycle-loading-tracker.cjs +83 -0
- package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -0
- package/dist/stream/lifecycle-loading-tracker.js +83 -0
- package/dist/stream/lifecycle-loading-tracker.js.map +1 -0
- package/dist/stream/message-metadata-tracker.cjs +165 -0
- package/dist/stream/message-metadata-tracker.cjs.map +1 -0
- package/dist/stream/message-metadata-tracker.d.cts +24 -0
- package/dist/stream/message-metadata-tracker.d.cts.map +1 -0
- package/dist/stream/message-metadata-tracker.d.ts +24 -0
- package/dist/stream/message-metadata-tracker.d.ts.map +1 -0
- package/dist/stream/message-metadata-tracker.js +165 -0
- package/dist/stream/message-metadata-tracker.js.map +1 -0
- package/dist/stream/message-reconciliation.cjs +118 -0
- package/dist/stream/message-reconciliation.cjs.map +1 -0
- package/dist/stream/message-reconciliation.js +115 -0
- package/dist/stream/message-reconciliation.js.map +1 -0
- package/dist/stream/namespace.cjs +54 -0
- package/dist/stream/namespace.cjs.map +1 -0
- package/dist/stream/namespace.js +49 -0
- package/dist/stream/namespace.js.map +1 -0
- package/dist/stream/projections/channel.cjs +53 -0
- package/dist/stream/projections/channel.cjs.map +1 -0
- package/dist/stream/projections/channel.d.cts +22 -0
- package/dist/stream/projections/channel.d.cts.map +1 -0
- package/dist/stream/projections/channel.d.ts +22 -0
- package/dist/stream/projections/channel.d.ts.map +1 -0
- package/dist/stream/projections/channel.js +53 -0
- package/dist/stream/projections/channel.js.map +1 -0
- package/dist/stream/projections/extension.cjs +29 -0
- package/dist/stream/projections/extension.cjs.map +1 -0
- package/dist/stream/projections/extension.d.cts +7 -0
- package/dist/stream/projections/extension.d.cts.map +1 -0
- package/dist/stream/projections/extension.d.ts +7 -0
- package/dist/stream/projections/extension.d.ts.map +1 -0
- package/dist/stream/projections/extension.js +29 -0
- package/dist/stream/projections/extension.js.map +1 -0
- package/dist/stream/projections/index.cjs +6 -0
- package/dist/stream/projections/index.d.ts +6 -0
- package/dist/stream/projections/index.js +7 -0
- package/dist/stream/projections/media.cjs +81 -0
- package/dist/stream/projections/media.cjs.map +1 -0
- package/dist/stream/projections/media.d.cts +18 -0
- package/dist/stream/projections/media.d.cts.map +1 -0
- package/dist/stream/projections/media.d.ts +18 -0
- package/dist/stream/projections/media.d.ts.map +1 -0
- package/dist/stream/projections/media.js +78 -0
- package/dist/stream/projections/media.js.map +1 -0
- package/dist/stream/projections/messages.cjs +121 -0
- package/dist/stream/projections/messages.cjs.map +1 -0
- package/dist/stream/projections/messages.d.cts +8 -0
- package/dist/stream/projections/messages.d.cts.map +1 -0
- package/dist/stream/projections/messages.d.ts +8 -0
- package/dist/stream/projections/messages.d.ts.map +1 -0
- package/dist/stream/projections/messages.js +121 -0
- package/dist/stream/projections/messages.js.map +1 -0
- package/dist/stream/projections/runtime.cjs +44 -0
- package/dist/stream/projections/runtime.cjs.map +1 -0
- package/dist/stream/projections/runtime.js +44 -0
- package/dist/stream/projections/runtime.js.map +1 -0
- package/dist/stream/projections/tool-calls.cjs +50 -0
- package/dist/stream/projections/tool-calls.cjs.map +1 -0
- package/dist/stream/projections/tool-calls.d.cts +8 -0
- package/dist/stream/projections/tool-calls.d.cts.map +1 -0
- package/dist/stream/projections/tool-calls.d.ts +8 -0
- package/dist/stream/projections/tool-calls.d.ts.map +1 -0
- package/dist/stream/projections/tool-calls.js +50 -0
- package/dist/stream/projections/tool-calls.js.map +1 -0
- package/dist/stream/projections/values.cjs +52 -0
- package/dist/stream/projections/values.cjs.map +1 -0
- package/dist/stream/projections/values.d.cts +7 -0
- package/dist/stream/projections/values.d.cts.map +1 -0
- package/dist/stream/projections/values.d.ts +6 -0
- package/dist/stream/projections/values.d.ts.map +1 -0
- package/dist/stream/projections/values.js +52 -0
- package/dist/stream/projections/values.js.map +1 -0
- package/dist/stream/root-message-projection.cjs +256 -0
- package/dist/stream/root-message-projection.cjs.map +1 -0
- package/dist/stream/root-message-projection.js +256 -0
- package/dist/stream/root-message-projection.js.map +1 -0
- package/dist/stream/store.cjs +32 -0
- package/dist/stream/store.cjs.map +1 -0
- package/dist/stream/store.d.cts +37 -0
- package/dist/stream/store.d.cts.map +1 -0
- package/dist/stream/store.d.ts +37 -0
- package/dist/stream/store.d.ts.map +1 -0
- package/dist/stream/store.js +32 -0
- package/dist/stream/store.js.map +1 -0
- package/dist/stream/submit-coordinator.cjs +399 -0
- package/dist/stream/submit-coordinator.cjs.map +1 -0
- package/dist/stream/submit-coordinator.d.cts +27 -0
- package/dist/stream/submit-coordinator.d.cts.map +1 -0
- package/dist/stream/submit-coordinator.d.ts +27 -0
- package/dist/stream/submit-coordinator.d.ts.map +1 -0
- package/dist/stream/submit-coordinator.js +397 -0
- package/dist/stream/submit-coordinator.js.map +1 -0
- package/dist/stream/tool-calls.cjs +15 -0
- package/dist/stream/tool-calls.cjs.map +1 -0
- package/dist/stream/tool-calls.js +15 -0
- package/dist/stream/tool-calls.js.map +1 -0
- package/dist/stream/types-inference.d.cts +43 -0
- package/dist/stream/types-inference.d.cts.map +1 -0
- package/dist/stream/types-inference.d.ts +43 -0
- package/dist/stream/types-inference.d.ts.map +1 -0
- package/dist/stream/types.d.cts +354 -0
- package/dist/stream/types.d.cts.map +1 -0
- package/dist/stream/types.d.ts +354 -0
- package/dist/stream/types.d.ts.map +1 -0
- package/dist/types.d.cts +2 -1
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/index.cjs +1 -1
- package/dist/ui/index.d.cts +3 -3
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +1 -1
- package/dist/ui/manager.cjs +2 -2
- package/dist/ui/manager.cjs.map +1 -1
- package/dist/ui/manager.d.cts +1 -0
- package/dist/ui/manager.d.cts.map +1 -1
- package/dist/ui/manager.d.ts +1 -0
- package/dist/ui/manager.d.ts.map +1 -1
- package/dist/ui/manager.js +2 -2
- package/dist/ui/manager.js.map +1 -1
- package/dist/ui/messages.cjs +50 -7
- package/dist/ui/messages.cjs.map +1 -1
- package/dist/ui/messages.d.cts.map +1 -1
- package/dist/ui/messages.d.ts.map +1 -1
- package/dist/ui/messages.js +51 -9
- package/dist/ui/messages.js.map +1 -1
- package/dist/ui/orchestrator-custom.cjs +1 -1
- package/dist/ui/orchestrator-custom.js +1 -1
- package/dist/ui/orchestrator.cjs +4 -3
- package/dist/ui/orchestrator.cjs.map +1 -1
- package/dist/ui/orchestrator.d.cts +1 -1
- package/dist/ui/orchestrator.d.cts.map +1 -1
- package/dist/ui/orchestrator.d.ts +1 -1
- package/dist/ui/orchestrator.d.ts.map +1 -1
- package/dist/ui/orchestrator.js +4 -3
- package/dist/ui/orchestrator.js.map +1 -1
- package/dist/ui/stream/agent.d.cts +1 -1
- package/dist/ui/stream/agent.d.cts.map +1 -1
- package/dist/ui/stream/agent.d.ts +1 -1
- package/dist/ui/stream/agent.d.ts.map +1 -1
- package/dist/ui/stream/base.d.cts +7 -6
- package/dist/ui/stream/base.d.cts.map +1 -1
- package/dist/ui/stream/base.d.ts +7 -6
- package/dist/ui/stream/base.d.ts.map +1 -1
- package/dist/ui/stream/deep-agent.d.cts +1 -1
- package/dist/ui/stream/deep-agent.d.cts.map +1 -1
- package/dist/ui/stream/deep-agent.d.ts +1 -1
- package/dist/ui/stream/deep-agent.d.ts.map +1 -1
- package/dist/ui/stream/index.d.cts +4 -4
- package/dist/ui/stream/index.d.cts.map +1 -1
- package/dist/ui/stream/index.d.ts +4 -4
- package/dist/ui/stream/index.d.ts.map +1 -1
- package/dist/ui/types.d.cts +3 -2
- package/dist/ui/types.d.cts.map +1 -1
- package/dist/ui/types.d.ts +2 -1
- package/dist/ui/types.d.ts.map +1 -1
- package/package.json +18 -8
- package/dist/client.cjs.map +0 -1
- package/dist/client.d.cts.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.js","names":[],"sources":["../../../src/stream/projections/values.ts"],"sourcesContent":["/**\n * Namespace-scoped `values` projection.\n *\n * Opens `thread.subscribe({ channels: [\"values\"], namespaces: [ns] })`\n * and stores the most-recent `values.data` payload. Mirrors\n * {@link ThreadStream.values} but scoped to an arbitrary namespace so\n * subgraphs and subagents can expose their own state.\n *\n * When the state payload carries a `messages` array of serialized\n * messages, those are coerced to `BaseMessage` class instances so the\n * surface shape matches the root snapshot.\n */\nimport type { ValuesEvent } from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport {\n ensureMessageInstances,\n tryCoerceMessageLikeToMessage,\n} from \"../../ui/messages.js\";\nimport type { Message } from \"../../types.messages.js\";\nimport type { ProjectionSpec, ProjectionRuntime } from \"../types.js\";\nimport { isRootNamespace, namespaceKey } from \"../namespace.js\";\nimport { openProjectionSubscription } from \"./runtime.js\";\n\nexport function valuesProjection<T = unknown>(\n namespace: readonly string[],\n messagesKey: string = \"messages\"\n): ProjectionSpec<T | undefined> {\n const ns = [...namespace];\n const key = `values|${messagesKey}|${namespaceKey(ns)}`;\n\n return {\n key,\n namespace: ns,\n initial: undefined,\n open({ thread, store, rootBus }): ProjectionRuntime {\n const applyValuesEvent = (event: ValuesEvent): void => {\n const coerced = coerceMessagesInState(event.params.data, messagesKey);\n store.setValue(coerced as T);\n };\n\n // See `messagesProjection` — root-scoped projections attach to\n // the controller's root bus instead of opening a duplicate\n // server subscription.\n const rootShortCircuit =\n isRootNamespace(ns) && rootBus.channels.includes(\"values\");\n\n if (rootShortCircuit) {\n const unsubscribe = rootBus.subscribe((event) => {\n if (event.method !== \"values\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n applyValuesEvent(event as ValuesEvent);\n });\n return {\n dispose() {\n unsubscribe();\n },\n };\n }\n\n return openProjectionSubscription({\n thread,\n channels: [\"values\"],\n namespace: ns,\n onEvent(event) {\n if (event.method !== \"values\") return;\n applyValuesEvent(event as ValuesEvent);\n },\n });\n },\n };\n}\n\nfunction coerceMessagesInState(value: unknown, messagesKey: string): unknown {\n if (value == null || typeof value !== \"object\" || Array.isArray(value)) {\n return value;\n }\n const state = value as Record<string, unknown>;\n const maybeMessages = state[messagesKey];\n if (!Array.isArray(maybeMessages) || maybeMessages.length === 0) {\n return value;\n }\n // Fast path: array already contains class instances.\n const hasPlain = maybeMessages.some(\n (m) => m != null && typeof (m as BaseMessage).getType !== \"function\"\n );\n if (!hasPlain) return value;\n return {\n ...state,\n [messagesKey]: ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n ),\n };\n}\n\nexport { tryCoerceMessageLikeToMessage };\n"],"mappings":";;;;AAuBA,SAAgB,iBACd,WACA,cAAsB,YACS;CAC/B,MAAM,KAAK,CAAC,GAAG,UAAU;AAGzB,QAAO;EACL,KAHU,UAAU,YAAY,GAAG,aAAa,GAAG;EAInD,WAAW;EACX,SAAS,KAAA;EACT,KAAK,EAAE,QAAQ,OAAO,WAA8B;GAClD,MAAM,oBAAoB,UAA6B;IACrD,MAAM,UAAU,sBAAsB,MAAM,OAAO,MAAM,YAAY;AACrE,UAAM,SAAS,QAAa;;AAS9B,OAFE,gBAAgB,GAAG,IAAI,QAAQ,SAAS,SAAS,SAAS,EAEtC;IACpB,MAAM,cAAc,QAAQ,WAAW,UAAU;AAC/C,SAAI,MAAM,WAAW,SAAU;AAC/B,SAAI,CAAC,gBAAgB,MAAM,OAAO,UAAU,CAAE;AAC9C,sBAAiB,MAAqB;MACtC;AACF,WAAO,EACL,UAAU;AACR,kBAAa;OAEhB;;AAGH,UAAO,2BAA2B;IAChC;IACA,UAAU,CAAC,SAAS;IACpB,WAAW;IACX,QAAQ,OAAO;AACb,SAAI,MAAM,WAAW,SAAU;AAC/B,sBAAiB,MAAqB;;IAEzC,CAAC;;EAEL;;AAGH,SAAS,sBAAsB,OAAgB,aAA8B;AAC3E,KAAI,SAAS,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CACpE,QAAO;CAET,MAAM,QAAQ;CACd,MAAM,gBAAgB,MAAM;AAC5B,KAAI,CAAC,MAAM,QAAQ,cAAc,IAAI,cAAc,WAAW,EAC5D,QAAO;AAMT,KAAI,CAHa,cAAc,MAC5B,MAAM,KAAK,QAAQ,OAAQ,EAAkB,YAAY,WAC3D,CACc,QAAO;AACtB,QAAO;EACL,GAAG;GACF,cAAc,uBACb,cACD;EACF"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const require_messages = require("../client/stream/messages.cjs");
|
|
2
|
+
const require_namespace = require("./namespace.cjs");
|
|
3
|
+
const require_assembled_to_message = require("./assembled-to-message.cjs");
|
|
4
|
+
const require_message_reconciliation = require("./message-reconciliation.cjs");
|
|
5
|
+
//#region src/stream/root-message-projection.ts
|
|
6
|
+
/**
|
|
7
|
+
* Root-namespace message projection. Owns the merge between the
|
|
8
|
+
* `messages` (streamed deltas) and `values` (authoritative
|
|
9
|
+
* snapshots) channels for the root namespace.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam StateType - Root state shape; the messages array is read
|
|
12
|
+
* from `values[messagesKey]`.
|
|
13
|
+
* @typeParam InterruptType - Shape of root protocol interrupts (forwarded
|
|
14
|
+
* into `RootSnapshot` updates).
|
|
15
|
+
*/
|
|
16
|
+
var RootMessageProjection = class {
|
|
17
|
+
/**
|
|
18
|
+
* Key inside `values` that holds the message array. Defaults to
|
|
19
|
+
* `"messages"` in the controller; configurable for state graphs
|
|
20
|
+
* that surface messages under a different slot.
|
|
21
|
+
*/
|
|
22
|
+
#messagesKey;
|
|
23
|
+
/** Root snapshot store written to on every merge. */
|
|
24
|
+
#store;
|
|
25
|
+
/**
|
|
26
|
+
* Subagent discovery runner notified about every assembled root
|
|
27
|
+
* message. Driving discovery from assembled messages (rather than
|
|
28
|
+
* raw events) lets us discover subagents from synthesized
|
|
29
|
+
* `tool_calls` without re-parsing protocol payloads.
|
|
30
|
+
*/
|
|
31
|
+
#subagents;
|
|
32
|
+
/**
|
|
33
|
+
* Stateful chunk assembler for in-flight messages. Reset (via a
|
|
34
|
+
* fresh instance) on every {@link reset} so a new thread starts
|
|
35
|
+
* with no half-built messages from the previous one.
|
|
36
|
+
*/
|
|
37
|
+
#assembler = new require_messages.MessageAssembler();
|
|
38
|
+
/**
|
|
39
|
+
* `messageId → role/toolCallId` captured from `message-start` events.
|
|
40
|
+
* The assembler's intermediate output drops these fields, so we cache
|
|
41
|
+
* them at start-time and reapply when projecting to a `BaseMessage`.
|
|
42
|
+
*/
|
|
43
|
+
#roles = /* @__PURE__ */ new Map();
|
|
44
|
+
/**
|
|
45
|
+
* `messageId → position in #store.messages` for fast in-place
|
|
46
|
+
* updates as deltas arrive. Rebuilt on every full reconciliation
|
|
47
|
+
* driven by a `values` event.
|
|
48
|
+
*/
|
|
49
|
+
#indexById = /* @__PURE__ */ new Map();
|
|
50
|
+
/**
|
|
51
|
+
* Ids observed in the most recent `values.messages` snapshot.
|
|
52
|
+
* Reconciliation uses this to detect server-side removals: a
|
|
53
|
+
* previously-seen id missing from the next snapshot means it was
|
|
54
|
+
* removed by the server (and should drop from the projection).
|
|
55
|
+
*/
|
|
56
|
+
#valuesMessageIds = /* @__PURE__ */ new Set();
|
|
57
|
+
/**
|
|
58
|
+
* `namespaceKey → tool_call_id` captured from root `tool-started`
|
|
59
|
+
* events. Used as a fallback when a tool-role `message-start` is
|
|
60
|
+
* missing its `tool_call_id` field.
|
|
61
|
+
*/
|
|
62
|
+
#toolCallIdByNamespace = /* @__PURE__ */ new Map();
|
|
63
|
+
/**
|
|
64
|
+
* @param params.messagesKey - Key inside `values` that holds the
|
|
65
|
+
* message array.
|
|
66
|
+
* @param params.store - Root snapshot store to mutate.
|
|
67
|
+
* @param params.subagents - Discovery runner fed by each new
|
|
68
|
+
* assembled message.
|
|
69
|
+
*/
|
|
70
|
+
constructor(params) {
|
|
71
|
+
this.#messagesKey = params.messagesKey;
|
|
72
|
+
this.#store = params.store;
|
|
73
|
+
this.#subagents = params.subagents;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Drop all per-thread state. Called by the controller on thread
|
|
77
|
+
* rebind / dispose so a swap doesn't surface stale messages.
|
|
78
|
+
*/
|
|
79
|
+
reset() {
|
|
80
|
+
this.#assembler = new require_messages.MessageAssembler();
|
|
81
|
+
this.#roles.clear();
|
|
82
|
+
this.#indexById.clear();
|
|
83
|
+
this.#valuesMessageIds = /* @__PURE__ */ new Set();
|
|
84
|
+
this.#toolCallIdByNamespace.clear();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Record a `namespace → tool_call_id` mapping captured from a root
|
|
88
|
+
* `tool-started` event.
|
|
89
|
+
*
|
|
90
|
+
* The companion tool-role `message-start` event may not carry a
|
|
91
|
+
* `tool_call_id`, so we fall back to the most recent value recorded
|
|
92
|
+
* here for the same namespace.
|
|
93
|
+
*
|
|
94
|
+
* @param namespace - Event namespace from the `tool-started` event.
|
|
95
|
+
* @param toolCallId - Tool call id from the same event.
|
|
96
|
+
*/
|
|
97
|
+
recordToolCallNamespace(namespace, toolCallId) {
|
|
98
|
+
this.#toolCallIdByNamespace.set(require_namespace.namespaceKey(namespace), toolCallId);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Apply a `messages` channel event to the projection.
|
|
102
|
+
*
|
|
103
|
+
* Captures role/tool metadata on `message-start`, feeds the chunk
|
|
104
|
+
* to the assembler, projects the assembled output to a
|
|
105
|
+
* {@link BaseMessage}, and either appends or in-place updates the
|
|
106
|
+
* store's messages array based on whether the id was seen before.
|
|
107
|
+
*
|
|
108
|
+
* @param event - The `messages` channel event to consume.
|
|
109
|
+
*/
|
|
110
|
+
handleMessage(event) {
|
|
111
|
+
const data = event.params.data;
|
|
112
|
+
if (data.event === "message-start") {
|
|
113
|
+
const startData = data;
|
|
114
|
+
const role = startData.role ?? "ai";
|
|
115
|
+
const extendedRole = startData.role ?? role;
|
|
116
|
+
let toolCallId = startData.tool_call_id;
|
|
117
|
+
if (extendedRole === "tool" && toolCallId == null) {
|
|
118
|
+
const messageId = startData.id;
|
|
119
|
+
if (messageId != null) {
|
|
120
|
+
const match = /-tool-(.+)$/.exec(messageId);
|
|
121
|
+
if (match != null) toolCallId = match[1];
|
|
122
|
+
}
|
|
123
|
+
if (toolCallId == null) toolCallId = this.#toolCallIdByNamespace.get(require_namespace.namespaceKey(event.params.namespace));
|
|
124
|
+
}
|
|
125
|
+
if (startData.id != null) this.#roles.set(startData.id, {
|
|
126
|
+
role: extendedRole,
|
|
127
|
+
toolCallId
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const update = this.#assembler.consume(event);
|
|
131
|
+
if (update == null) return;
|
|
132
|
+
const id = update.message.id;
|
|
133
|
+
if (id == null) return;
|
|
134
|
+
const captured = this.#roles.get(id) ?? { role: "ai" };
|
|
135
|
+
const base = require_assembled_to_message.assembledMessageToBaseMessage(update.message, captured.role, { toolCallId: captured.toolCallId });
|
|
136
|
+
this.#subagents.discoverFromMessage(base, event.params.namespace);
|
|
137
|
+
this.#store.setState((s) => {
|
|
138
|
+
const existingIdx = this.#indexById.get(id);
|
|
139
|
+
let messages;
|
|
140
|
+
if (existingIdx == null) {
|
|
141
|
+
this.#indexById.set(id, s.messages.length);
|
|
142
|
+
messages = [...s.messages, base];
|
|
143
|
+
} else if (require_message_reconciliation.messagesEqual(s.messages[existingIdx], base)) return s;
|
|
144
|
+
else {
|
|
145
|
+
messages = s.messages.slice();
|
|
146
|
+
messages[existingIdx] = base;
|
|
147
|
+
}
|
|
148
|
+
const values = syncMessagesIntoValues(s.values, this.#messagesKey, messages);
|
|
149
|
+
return values === s.values ? {
|
|
150
|
+
...s,
|
|
151
|
+
messages
|
|
152
|
+
} : {
|
|
153
|
+
...s,
|
|
154
|
+
messages,
|
|
155
|
+
values
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Reconcile a full `values` snapshot into the projection.
|
|
161
|
+
*
|
|
162
|
+
* Delegates the merge to {@link reconcileMessagesFromValues}:
|
|
163
|
+
* values stays authoritative for ordering and removals, while
|
|
164
|
+
* streamed in-flight messages keep their content until the server
|
|
165
|
+
* echoes them back. Empty messages just refresh the values blob.
|
|
166
|
+
*
|
|
167
|
+
* Rebuilds {@link #indexById} after the merge so subsequent delta
|
|
168
|
+
* applications target the new positions.
|
|
169
|
+
*
|
|
170
|
+
* @param nextValues - Full values snapshot from the `values` event.
|
|
171
|
+
* @param nextMessages - The messages array extracted from
|
|
172
|
+
* `values[messagesKey]` and coerced to `BaseMessage` instances.
|
|
173
|
+
*/
|
|
174
|
+
applyValues(nextValues, nextMessages) {
|
|
175
|
+
this.#store.setState((s) => {
|
|
176
|
+
if (nextMessages.length === 0) return stateValuesShallowEqual(s.values, nextValues, this.#messagesKey) ? s : {
|
|
177
|
+
...s,
|
|
178
|
+
values: nextValues
|
|
179
|
+
};
|
|
180
|
+
const reconciliation = require_message_reconciliation.reconcileMessagesFromValues({
|
|
181
|
+
valueMessages: nextMessages,
|
|
182
|
+
currentMessages: s.messages,
|
|
183
|
+
currentIndexById: this.#indexById,
|
|
184
|
+
previousValueMessageIds: this.#valuesMessageIds,
|
|
185
|
+
preferValuesMessage: require_message_reconciliation.shouldPreferValuesMessageForToolCalls
|
|
186
|
+
});
|
|
187
|
+
this.#valuesMessageIds = reconciliation.valueMessageIds;
|
|
188
|
+
const messages = reconciliation.messages;
|
|
189
|
+
const values = {
|
|
190
|
+
...nextValues,
|
|
191
|
+
[this.#messagesKey]: messages
|
|
192
|
+
};
|
|
193
|
+
if (messages === s.messages && stateValuesShallowEqual(s.values, values, this.#messagesKey)) return s;
|
|
194
|
+
this.#indexById.clear();
|
|
195
|
+
for (const [id, idx] of require_message_reconciliation.buildMessageIndex(messages)) this.#indexById.set(id, idx);
|
|
196
|
+
return {
|
|
197
|
+
...s,
|
|
198
|
+
values,
|
|
199
|
+
messages
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Mirror a freshly-updated message list into `values[messagesKey]`.
|
|
206
|
+
*
|
|
207
|
+
* Returns the same `values` reference when the list is already
|
|
208
|
+
* equal-by-content so the caller can keep the existing snapshot
|
|
209
|
+
* identity (and avoid spurious `setSnapshot` notifications).
|
|
210
|
+
*/
|
|
211
|
+
function syncMessagesIntoValues(values, messagesKey, messages) {
|
|
212
|
+
const record = values;
|
|
213
|
+
const current = record[messagesKey];
|
|
214
|
+
if (Array.isArray(current) && messagesEqualList(current, messages)) return values;
|
|
215
|
+
return {
|
|
216
|
+
...record,
|
|
217
|
+
[messagesKey]: messages
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* True when two `BaseMessage` arrays carry the same per-message
|
|
222
|
+
* content (using {@link messagesEqual}).
|
|
223
|
+
*/
|
|
224
|
+
function messagesEqualList(previous, next) {
|
|
225
|
+
if (previous === next) return true;
|
|
226
|
+
if (previous.length !== next.length) return false;
|
|
227
|
+
for (let i = 0; i < previous.length; i += 1) if (!require_message_reconciliation.messagesEqual(previous[i], next[i])) return false;
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Shallow-equal for `values` objects, *ignoring* the messages slot.
|
|
232
|
+
*
|
|
233
|
+
* The messages array is compared separately by the caller (via
|
|
234
|
+
* {@link messagesEqualList}) because both arrays contain class
|
|
235
|
+
* instances whose JSON representation is not stable across reads.
|
|
236
|
+
*/
|
|
237
|
+
function stateValuesShallowEqual(previous, next, messagesKey) {
|
|
238
|
+
if (previous === next) return true;
|
|
239
|
+
const previousRecord = previous;
|
|
240
|
+
const nextRecord = next;
|
|
241
|
+
const previousKeys = Object.keys(previousRecord);
|
|
242
|
+
const nextKeys = Object.keys(nextRecord);
|
|
243
|
+
if (previousKeys.length !== nextKeys.length) return false;
|
|
244
|
+
for (const key of previousKeys) {
|
|
245
|
+
if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;
|
|
246
|
+
const previousValue = previousRecord[key];
|
|
247
|
+
const nextValue = nextRecord[key];
|
|
248
|
+
if (key === messagesKey && Array.isArray(previousValue) && Array.isArray(nextValue)) continue;
|
|
249
|
+
if (!Object.is(previousValue, nextValue)) return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
exports.RootMessageProjection = RootMessageProjection;
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=root-message-projection.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-message-projection.cjs","names":["#messagesKey","#store","#subagents","MessageAssembler","#roles","#indexById","#toolCallIdByNamespace","#assembler","#valuesMessageIds","namespaceKey","assembledMessageToBaseMessage","messagesEqual","reconcileMessagesFromValues","shouldPreferValuesMessageForToolCalls","buildMessageIndex"],"sources":["../../src/stream/root-message-projection.ts"],"sourcesContent":["/**\n * Root-namespace message projection.\n *\n * # What this module is\n *\n * The {@link RootMessageProjection} is the piece of the\n * {@link StreamController} that owns \"what messages does the root\n * namespace currently contain?\". It assembles streamed message deltas\n * via {@link MessageAssembler}, reconciles them against authoritative\n * `values.messages` snapshots from the server, and writes the merged\n * list back into the controller's root snapshot store.\n *\n * It also feeds {@link SubagentDiscovery} from each new root message\n * — that's the layer that surfaces `task` tool calls as discovered\n * subagents, even before any subagent-scoped subscription is opened.\n *\n * # Two streams of truth\n *\n * Root messages arrive on two channels and need to merge cleanly:\n *\n * - **`messages` channel.** Token-level deltas that build messages\n * incrementally. The {@link MessageAssembler} keeps partial\n * messages by id and emits an updated `BaseMessage` per delta.\n * - **`values` channel.** Periodic full-state snapshots that include\n * the authoritative messages array. Used for ordering, removals,\n * and forks (where the streamed messages may pre-date the new\n * timeline).\n *\n * The reconciliation rules (delegated to\n * {@link reconcileMessagesFromValues}) preserve in-flight streamed\n * content while letting values dictate ordering and removals.\n *\n * # Tool-message namespace correlation\n *\n * Tool messages arrive on `messages-start` events with `role: \"tool\"`\n * but the start event doesn't always include a `tool_call_id`. We\n * recover it via three fallbacks:\n *\n * 1. The start event itself, when the server includes it.\n * 2. The legacy `<id>-tool-<call_id>` message id format.\n * 3. The most recent `tool-started` event recorded under the same\n * namespace via {@link recordToolCallNamespace}.\n *\n * Without this correlation, tool messages render with empty\n * `tool_call_id` and downstream UIs can't pair them with the\n * originating tool call.\n *\n * # Lifecycle\n *\n * - `handleMessage(event)` — apply a `messages` event delta.\n * - `applyValues(values, msgs)` — merge a `values` snapshot.\n * - `recordToolCallNamespace(ns, id)` — capture `namespace → tool_call_id`\n * so subsequent tool message starts can recover the id.\n * - `reset()` — clear all state on thread rebind.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"./assembled-to-message.js\";\nimport type { StreamStore } from \"./store.js\";\nimport type { RootSnapshot } from \"./types.js\";\nimport type { SubagentDiscovery } from \"./discovery/index.js\";\nimport { namespaceKey } from \"./namespace.js\";\nimport {\n buildMessageIndex,\n messagesEqual,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"./message-reconciliation.js\";\n\n/**\n * Root-namespace message projection. Owns the merge between the\n * `messages` (streamed deltas) and `values` (authoritative\n * snapshots) channels for the root namespace.\n *\n * @typeParam StateType - Root state shape; the messages array is read\n * from `values[messagesKey]`.\n * @typeParam InterruptType - Shape of root protocol interrupts (forwarded\n * into `RootSnapshot` updates).\n */\nexport class RootMessageProjection<\n StateType extends object,\n InterruptType = unknown,\n> {\n /**\n * Key inside `values` that holds the message array. Defaults to\n * `\"messages\"` in the controller; configurable for state graphs\n * that surface messages under a different slot.\n */\n readonly #messagesKey: string;\n\n /** Root snapshot store written to on every merge. */\n readonly #store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n\n /**\n * Subagent discovery runner notified about every assembled root\n * message. Driving discovery from assembled messages (rather than\n * raw events) lets us discover subagents from synthesized\n * `tool_calls` without re-parsing protocol payloads.\n */\n readonly #subagents: SubagentDiscovery;\n\n /**\n * Stateful chunk assembler for in-flight messages. Reset (via a\n * fresh instance) on every {@link reset} so a new thread starts\n * with no half-built messages from the previous one.\n */\n #assembler = new MessageAssembler();\n\n /**\n * `messageId → role/toolCallId` captured from `message-start` events.\n * The assembler's intermediate output drops these fields, so we cache\n * them at start-time and reapply when projecting to a `BaseMessage`.\n */\n readonly #roles = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n\n /**\n * `messageId → position in #store.messages` for fast in-place\n * updates as deltas arrive. Rebuilt on every full reconciliation\n * driven by a `values` event.\n */\n readonly #indexById = new Map<string, number>();\n\n /**\n * Ids observed in the most recent `values.messages` snapshot.\n * Reconciliation uses this to detect server-side removals: a\n * previously-seen id missing from the next snapshot means it was\n * removed by the server (and should drop from the projection).\n */\n #valuesMessageIds = new Set<string>();\n\n /**\n * `namespaceKey → tool_call_id` captured from root `tool-started`\n * events. Used as a fallback when a tool-role `message-start` is\n * missing its `tool_call_id` field.\n */\n readonly #toolCallIdByNamespace = new Map<string, string>();\n\n /**\n * @param params.messagesKey - Key inside `values` that holds the\n * message array.\n * @param params.store - Root snapshot store to mutate.\n * @param params.subagents - Discovery runner fed by each new\n * assembled message.\n */\n constructor(params: {\n messagesKey: string;\n store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n subagents: SubagentDiscovery;\n }) {\n this.#messagesKey = params.messagesKey;\n this.#store = params.store;\n this.#subagents = params.subagents;\n }\n\n /**\n * Drop all per-thread state. Called by the controller on thread\n * rebind / dispose so a swap doesn't surface stale messages.\n */\n reset(): void {\n this.#assembler = new MessageAssembler();\n this.#roles.clear();\n this.#indexById.clear();\n this.#valuesMessageIds = new Set();\n this.#toolCallIdByNamespace.clear();\n }\n\n /**\n * Record a `namespace → tool_call_id` mapping captured from a root\n * `tool-started` event.\n *\n * The companion tool-role `message-start` event may not carry a\n * `tool_call_id`, so we fall back to the most recent value recorded\n * here for the same namespace.\n *\n * @param namespace - Event namespace from the `tool-started` event.\n * @param toolCallId - Tool call id from the same event.\n */\n recordToolCallNamespace(\n namespace: readonly string[],\n toolCallId: string\n ): void {\n this.#toolCallIdByNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Apply a `messages` channel event to the projection.\n *\n * Captures role/tool metadata on `message-start`, feeds the chunk\n * to the assembler, projects the assembled output to a\n * {@link BaseMessage}, and either appends or in-place updates the\n * store's messages array based on whether the id was seen before.\n *\n * @param event - The `messages` channel event to consume.\n */\n handleMessage(event: MessagesEvent): void {\n const data = event.params.data;\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n let toolCallId = (startData as { tool_call_id?: string }).tool_call_id;\n // Tool messages need a tool_call_id to render. Fall back through:\n // 1. legacy `<id>-tool-<call_id>` message id format\n // 2. namespace-recorded tool_call_id (from #recordToolCallNamespace)\n if (extendedRole === \"tool\" && toolCallId == null) {\n const messageId = startData.id;\n if (messageId != null) {\n const match = /-tool-(.+)$/.exec(messageId);\n if (match != null) toolCallId = match[1];\n }\n if (toolCallId == null) {\n toolCallId = this.#toolCallIdByNamespace.get(\n namespaceKey(event.params.namespace)\n );\n }\n }\n if (startData.id != null) {\n this.#roles.set(startData.id, {\n role: extendedRole,\n toolCallId,\n });\n }\n }\n\n const update = this.#assembler.consume(event);\n if (update == null) return;\n const id = update.message.id;\n if (id == null) return;\n const captured = this.#roles.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(update.message, captured.role, {\n toolCallId: captured.toolCallId,\n });\n this.#subagents.discoverFromMessage(base, event.params.namespace);\n\n this.#store.setState((s) => {\n const existingIdx = this.#indexById.get(id);\n let messages: BaseMessage[];\n if (existingIdx == null) {\n // First sighting: append at end and remember its index for\n // future delta updates.\n this.#indexById.set(id, s.messages.length);\n messages = [...s.messages, base];\n } else if (messagesEqual(s.messages[existingIdx], base)) {\n // Identical re-emission of an already-projected message —\n // skip the store write to keep snapshot identity stable.\n return s;\n } else {\n // In-place update for a known id.\n messages = s.messages.slice();\n messages[existingIdx] = base;\n }\n\n // Mirror the new messages list into `values[messagesKey]` so\n // direct `values` reads (used by some hooks and by the eventual\n // `values` reconciliation) stay in sync.\n const values = syncMessagesIntoValues(\n s.values,\n this.#messagesKey,\n messages\n );\n return values === s.values\n ? { ...s, messages }\n : { ...s, messages, values };\n });\n }\n\n /**\n * Reconcile a full `values` snapshot into the projection.\n *\n * Delegates the merge to {@link reconcileMessagesFromValues}:\n * values stays authoritative for ordering and removals, while\n * streamed in-flight messages keep their content until the server\n * echoes them back. Empty messages just refresh the values blob.\n *\n * Rebuilds {@link #indexById} after the merge so subsequent delta\n * applications target the new positions.\n *\n * @param nextValues - Full values snapshot from the `values` event.\n * @param nextMessages - The messages array extracted from\n * `values[messagesKey]` and coerced to `BaseMessage` instances.\n */\n applyValues(nextValues: StateType, nextMessages: BaseMessage[]): void {\n this.#store.setState((s) => {\n if (nextMessages.length === 0) {\n return stateValuesShallowEqual(s.values, nextValues, this.#messagesKey)\n ? s\n : { ...s, values: nextValues };\n }\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: nextMessages,\n currentMessages: s.messages,\n currentIndexById: this.#indexById,\n previousValueMessageIds: this.#valuesMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n });\n this.#valuesMessageIds = reconciliation.valueMessageIds;\n const messages = reconciliation.messages as BaseMessage[];\n const values = {\n ...(nextValues as Record<string, unknown>),\n [this.#messagesKey]: messages,\n } as StateType;\n if (\n messages === s.messages &&\n stateValuesShallowEqual(s.values, values, this.#messagesKey)\n ) {\n return s;\n }\n\n // Reconciliation may reorder, drop, or substitute messages, so\n // rebuild the id → index map to match the new array.\n this.#indexById.clear();\n for (const [id, idx] of buildMessageIndex(messages)) {\n this.#indexById.set(id, idx);\n }\n return {\n ...s,\n values,\n messages,\n };\n });\n }\n}\n\n/**\n * Mirror a freshly-updated message list into `values[messagesKey]`.\n *\n * Returns the same `values` reference when the list is already\n * equal-by-content so the caller can keep the existing snapshot\n * identity (and avoid spurious `setSnapshot` notifications).\n */\nfunction syncMessagesIntoValues<StateType extends object>(\n values: StateType,\n messagesKey: string,\n messages: BaseMessage[]\n): StateType {\n const record = values as Record<string, unknown>;\n const current = record[messagesKey];\n if (Array.isArray(current) && messagesEqualList(current, messages)) {\n return values;\n }\n return {\n ...record,\n [messagesKey]: messages,\n } as StateType;\n}\n\n/**\n * True when two `BaseMessage` arrays carry the same per-message\n * content (using {@link messagesEqual}).\n */\nfunction 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\n/**\n * Shallow-equal for `values` objects, *ignoring* the messages slot.\n *\n * The messages array is compared separately by the caller (via\n * {@link messagesEqualList}) because both arrays contain class\n * instances whose JSON representation is not stable across reads.\n */\nfunction stateValuesShallowEqual(\n previous: object,\n next: object,\n messagesKey: string\n): boolean {\n if (previous === next) return true;\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord);\n const nextKeys = Object.keys(nextRecord);\n if (previousKeys.length !== nextKeys.length) return false;\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n const previousValue = previousRecord[key];\n const nextValue = nextRecord[key];\n if (\n key === messagesKey &&\n Array.isArray(previousValue) &&\n Array.isArray(nextValue)\n ) {\n continue;\n }\n if (!Object.is(previousValue, nextValue)) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { MessageAssembler } from "../client/stream/messages.js";
|
|
2
|
+
import { namespaceKey } from "./namespace.js";
|
|
3
|
+
import { assembledMessageToBaseMessage } from "./assembled-to-message.js";
|
|
4
|
+
import { buildMessageIndex, messagesEqual, reconcileMessagesFromValues, shouldPreferValuesMessageForToolCalls } from "./message-reconciliation.js";
|
|
5
|
+
//#region src/stream/root-message-projection.ts
|
|
6
|
+
/**
|
|
7
|
+
* Root-namespace message projection. Owns the merge between the
|
|
8
|
+
* `messages` (streamed deltas) and `values` (authoritative
|
|
9
|
+
* snapshots) channels for the root namespace.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam StateType - Root state shape; the messages array is read
|
|
12
|
+
* from `values[messagesKey]`.
|
|
13
|
+
* @typeParam InterruptType - Shape of root protocol interrupts (forwarded
|
|
14
|
+
* into `RootSnapshot` updates).
|
|
15
|
+
*/
|
|
16
|
+
var RootMessageProjection = class {
|
|
17
|
+
/**
|
|
18
|
+
* Key inside `values` that holds the message array. Defaults to
|
|
19
|
+
* `"messages"` in the controller; configurable for state graphs
|
|
20
|
+
* that surface messages under a different slot.
|
|
21
|
+
*/
|
|
22
|
+
#messagesKey;
|
|
23
|
+
/** Root snapshot store written to on every merge. */
|
|
24
|
+
#store;
|
|
25
|
+
/**
|
|
26
|
+
* Subagent discovery runner notified about every assembled root
|
|
27
|
+
* message. Driving discovery from assembled messages (rather than
|
|
28
|
+
* raw events) lets us discover subagents from synthesized
|
|
29
|
+
* `tool_calls` without re-parsing protocol payloads.
|
|
30
|
+
*/
|
|
31
|
+
#subagents;
|
|
32
|
+
/**
|
|
33
|
+
* Stateful chunk assembler for in-flight messages. Reset (via a
|
|
34
|
+
* fresh instance) on every {@link reset} so a new thread starts
|
|
35
|
+
* with no half-built messages from the previous one.
|
|
36
|
+
*/
|
|
37
|
+
#assembler = new MessageAssembler();
|
|
38
|
+
/**
|
|
39
|
+
* `messageId → role/toolCallId` captured from `message-start` events.
|
|
40
|
+
* The assembler's intermediate output drops these fields, so we cache
|
|
41
|
+
* them at start-time and reapply when projecting to a `BaseMessage`.
|
|
42
|
+
*/
|
|
43
|
+
#roles = /* @__PURE__ */ new Map();
|
|
44
|
+
/**
|
|
45
|
+
* `messageId → position in #store.messages` for fast in-place
|
|
46
|
+
* updates as deltas arrive. Rebuilt on every full reconciliation
|
|
47
|
+
* driven by a `values` event.
|
|
48
|
+
*/
|
|
49
|
+
#indexById = /* @__PURE__ */ new Map();
|
|
50
|
+
/**
|
|
51
|
+
* Ids observed in the most recent `values.messages` snapshot.
|
|
52
|
+
* Reconciliation uses this to detect server-side removals: a
|
|
53
|
+
* previously-seen id missing from the next snapshot means it was
|
|
54
|
+
* removed by the server (and should drop from the projection).
|
|
55
|
+
*/
|
|
56
|
+
#valuesMessageIds = /* @__PURE__ */ new Set();
|
|
57
|
+
/**
|
|
58
|
+
* `namespaceKey → tool_call_id` captured from root `tool-started`
|
|
59
|
+
* events. Used as a fallback when a tool-role `message-start` is
|
|
60
|
+
* missing its `tool_call_id` field.
|
|
61
|
+
*/
|
|
62
|
+
#toolCallIdByNamespace = /* @__PURE__ */ new Map();
|
|
63
|
+
/**
|
|
64
|
+
* @param params.messagesKey - Key inside `values` that holds the
|
|
65
|
+
* message array.
|
|
66
|
+
* @param params.store - Root snapshot store to mutate.
|
|
67
|
+
* @param params.subagents - Discovery runner fed by each new
|
|
68
|
+
* assembled message.
|
|
69
|
+
*/
|
|
70
|
+
constructor(params) {
|
|
71
|
+
this.#messagesKey = params.messagesKey;
|
|
72
|
+
this.#store = params.store;
|
|
73
|
+
this.#subagents = params.subagents;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Drop all per-thread state. Called by the controller on thread
|
|
77
|
+
* rebind / dispose so a swap doesn't surface stale messages.
|
|
78
|
+
*/
|
|
79
|
+
reset() {
|
|
80
|
+
this.#assembler = new MessageAssembler();
|
|
81
|
+
this.#roles.clear();
|
|
82
|
+
this.#indexById.clear();
|
|
83
|
+
this.#valuesMessageIds = /* @__PURE__ */ new Set();
|
|
84
|
+
this.#toolCallIdByNamespace.clear();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Record a `namespace → tool_call_id` mapping captured from a root
|
|
88
|
+
* `tool-started` event.
|
|
89
|
+
*
|
|
90
|
+
* The companion tool-role `message-start` event may not carry a
|
|
91
|
+
* `tool_call_id`, so we fall back to the most recent value recorded
|
|
92
|
+
* here for the same namespace.
|
|
93
|
+
*
|
|
94
|
+
* @param namespace - Event namespace from the `tool-started` event.
|
|
95
|
+
* @param toolCallId - Tool call id from the same event.
|
|
96
|
+
*/
|
|
97
|
+
recordToolCallNamespace(namespace, toolCallId) {
|
|
98
|
+
this.#toolCallIdByNamespace.set(namespaceKey(namespace), toolCallId);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Apply a `messages` channel event to the projection.
|
|
102
|
+
*
|
|
103
|
+
* Captures role/tool metadata on `message-start`, feeds the chunk
|
|
104
|
+
* to the assembler, projects the assembled output to a
|
|
105
|
+
* {@link BaseMessage}, and either appends or in-place updates the
|
|
106
|
+
* store's messages array based on whether the id was seen before.
|
|
107
|
+
*
|
|
108
|
+
* @param event - The `messages` channel event to consume.
|
|
109
|
+
*/
|
|
110
|
+
handleMessage(event) {
|
|
111
|
+
const data = event.params.data;
|
|
112
|
+
if (data.event === "message-start") {
|
|
113
|
+
const startData = data;
|
|
114
|
+
const role = startData.role ?? "ai";
|
|
115
|
+
const extendedRole = startData.role ?? role;
|
|
116
|
+
let toolCallId = startData.tool_call_id;
|
|
117
|
+
if (extendedRole === "tool" && toolCallId == null) {
|
|
118
|
+
const messageId = startData.id;
|
|
119
|
+
if (messageId != null) {
|
|
120
|
+
const match = /-tool-(.+)$/.exec(messageId);
|
|
121
|
+
if (match != null) toolCallId = match[1];
|
|
122
|
+
}
|
|
123
|
+
if (toolCallId == null) toolCallId = this.#toolCallIdByNamespace.get(namespaceKey(event.params.namespace));
|
|
124
|
+
}
|
|
125
|
+
if (startData.id != null) this.#roles.set(startData.id, {
|
|
126
|
+
role: extendedRole,
|
|
127
|
+
toolCallId
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const update = this.#assembler.consume(event);
|
|
131
|
+
if (update == null) return;
|
|
132
|
+
const id = update.message.id;
|
|
133
|
+
if (id == null) return;
|
|
134
|
+
const captured = this.#roles.get(id) ?? { role: "ai" };
|
|
135
|
+
const base = assembledMessageToBaseMessage(update.message, captured.role, { toolCallId: captured.toolCallId });
|
|
136
|
+
this.#subagents.discoverFromMessage(base, event.params.namespace);
|
|
137
|
+
this.#store.setState((s) => {
|
|
138
|
+
const existingIdx = this.#indexById.get(id);
|
|
139
|
+
let messages;
|
|
140
|
+
if (existingIdx == null) {
|
|
141
|
+
this.#indexById.set(id, s.messages.length);
|
|
142
|
+
messages = [...s.messages, base];
|
|
143
|
+
} else if (messagesEqual(s.messages[existingIdx], base)) return s;
|
|
144
|
+
else {
|
|
145
|
+
messages = s.messages.slice();
|
|
146
|
+
messages[existingIdx] = base;
|
|
147
|
+
}
|
|
148
|
+
const values = syncMessagesIntoValues(s.values, this.#messagesKey, messages);
|
|
149
|
+
return values === s.values ? {
|
|
150
|
+
...s,
|
|
151
|
+
messages
|
|
152
|
+
} : {
|
|
153
|
+
...s,
|
|
154
|
+
messages,
|
|
155
|
+
values
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Reconcile a full `values` snapshot into the projection.
|
|
161
|
+
*
|
|
162
|
+
* Delegates the merge to {@link reconcileMessagesFromValues}:
|
|
163
|
+
* values stays authoritative for ordering and removals, while
|
|
164
|
+
* streamed in-flight messages keep their content until the server
|
|
165
|
+
* echoes them back. Empty messages just refresh the values blob.
|
|
166
|
+
*
|
|
167
|
+
* Rebuilds {@link #indexById} after the merge so subsequent delta
|
|
168
|
+
* applications target the new positions.
|
|
169
|
+
*
|
|
170
|
+
* @param nextValues - Full values snapshot from the `values` event.
|
|
171
|
+
* @param nextMessages - The messages array extracted from
|
|
172
|
+
* `values[messagesKey]` and coerced to `BaseMessage` instances.
|
|
173
|
+
*/
|
|
174
|
+
applyValues(nextValues, nextMessages) {
|
|
175
|
+
this.#store.setState((s) => {
|
|
176
|
+
if (nextMessages.length === 0) return stateValuesShallowEqual(s.values, nextValues, this.#messagesKey) ? s : {
|
|
177
|
+
...s,
|
|
178
|
+
values: nextValues
|
|
179
|
+
};
|
|
180
|
+
const reconciliation = reconcileMessagesFromValues({
|
|
181
|
+
valueMessages: nextMessages,
|
|
182
|
+
currentMessages: s.messages,
|
|
183
|
+
currentIndexById: this.#indexById,
|
|
184
|
+
previousValueMessageIds: this.#valuesMessageIds,
|
|
185
|
+
preferValuesMessage: shouldPreferValuesMessageForToolCalls
|
|
186
|
+
});
|
|
187
|
+
this.#valuesMessageIds = reconciliation.valueMessageIds;
|
|
188
|
+
const messages = reconciliation.messages;
|
|
189
|
+
const values = {
|
|
190
|
+
...nextValues,
|
|
191
|
+
[this.#messagesKey]: messages
|
|
192
|
+
};
|
|
193
|
+
if (messages === s.messages && stateValuesShallowEqual(s.values, values, this.#messagesKey)) return s;
|
|
194
|
+
this.#indexById.clear();
|
|
195
|
+
for (const [id, idx] of buildMessageIndex(messages)) this.#indexById.set(id, idx);
|
|
196
|
+
return {
|
|
197
|
+
...s,
|
|
198
|
+
values,
|
|
199
|
+
messages
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Mirror a freshly-updated message list into `values[messagesKey]`.
|
|
206
|
+
*
|
|
207
|
+
* Returns the same `values` reference when the list is already
|
|
208
|
+
* equal-by-content so the caller can keep the existing snapshot
|
|
209
|
+
* identity (and avoid spurious `setSnapshot` notifications).
|
|
210
|
+
*/
|
|
211
|
+
function syncMessagesIntoValues(values, messagesKey, messages) {
|
|
212
|
+
const record = values;
|
|
213
|
+
const current = record[messagesKey];
|
|
214
|
+
if (Array.isArray(current) && messagesEqualList(current, messages)) return values;
|
|
215
|
+
return {
|
|
216
|
+
...record,
|
|
217
|
+
[messagesKey]: messages
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* True when two `BaseMessage` arrays carry the same per-message
|
|
222
|
+
* content (using {@link messagesEqual}).
|
|
223
|
+
*/
|
|
224
|
+
function messagesEqualList(previous, next) {
|
|
225
|
+
if (previous === next) return true;
|
|
226
|
+
if (previous.length !== next.length) return false;
|
|
227
|
+
for (let i = 0; i < previous.length; i += 1) if (!messagesEqual(previous[i], next[i])) return false;
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Shallow-equal for `values` objects, *ignoring* the messages slot.
|
|
232
|
+
*
|
|
233
|
+
* The messages array is compared separately by the caller (via
|
|
234
|
+
* {@link messagesEqualList}) because both arrays contain class
|
|
235
|
+
* instances whose JSON representation is not stable across reads.
|
|
236
|
+
*/
|
|
237
|
+
function stateValuesShallowEqual(previous, next, messagesKey) {
|
|
238
|
+
if (previous === next) return true;
|
|
239
|
+
const previousRecord = previous;
|
|
240
|
+
const nextRecord = next;
|
|
241
|
+
const previousKeys = Object.keys(previousRecord);
|
|
242
|
+
const nextKeys = Object.keys(nextRecord);
|
|
243
|
+
if (previousKeys.length !== nextKeys.length) return false;
|
|
244
|
+
for (const key of previousKeys) {
|
|
245
|
+
if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;
|
|
246
|
+
const previousValue = previousRecord[key];
|
|
247
|
+
const nextValue = nextRecord[key];
|
|
248
|
+
if (key === messagesKey && Array.isArray(previousValue) && Array.isArray(nextValue)) continue;
|
|
249
|
+
if (!Object.is(previousValue, nextValue)) return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
export { RootMessageProjection };
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=root-message-projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-message-projection.js","names":["#messagesKey","#store","#subagents","#roles","#indexById","#toolCallIdByNamespace","#assembler","#valuesMessageIds"],"sources":["../../src/stream/root-message-projection.ts"],"sourcesContent":["/**\n * Root-namespace message projection.\n *\n * # What this module is\n *\n * The {@link RootMessageProjection} is the piece of the\n * {@link StreamController} that owns \"what messages does the root\n * namespace currently contain?\". It assembles streamed message deltas\n * via {@link MessageAssembler}, reconciles them against authoritative\n * `values.messages` snapshots from the server, and writes the merged\n * list back into the controller's root snapshot store.\n *\n * It also feeds {@link SubagentDiscovery} from each new root message\n * — that's the layer that surfaces `task` tool calls as discovered\n * subagents, even before any subagent-scoped subscription is opened.\n *\n * # Two streams of truth\n *\n * Root messages arrive on two channels and need to merge cleanly:\n *\n * - **`messages` channel.** Token-level deltas that build messages\n * incrementally. The {@link MessageAssembler} keeps partial\n * messages by id and emits an updated `BaseMessage` per delta.\n * - **`values` channel.** Periodic full-state snapshots that include\n * the authoritative messages array. Used for ordering, removals,\n * and forks (where the streamed messages may pre-date the new\n * timeline).\n *\n * The reconciliation rules (delegated to\n * {@link reconcileMessagesFromValues}) preserve in-flight streamed\n * content while letting values dictate ordering and removals.\n *\n * # Tool-message namespace correlation\n *\n * Tool messages arrive on `messages-start` events with `role: \"tool\"`\n * but the start event doesn't always include a `tool_call_id`. We\n * recover it via three fallbacks:\n *\n * 1. The start event itself, when the server includes it.\n * 2. The legacy `<id>-tool-<call_id>` message id format.\n * 3. The most recent `tool-started` event recorded under the same\n * namespace via {@link recordToolCallNamespace}.\n *\n * Without this correlation, tool messages render with empty\n * `tool_call_id` and downstream UIs can't pair them with the\n * originating tool call.\n *\n * # Lifecycle\n *\n * - `handleMessage(event)` — apply a `messages` event delta.\n * - `applyValues(values, msgs)` — merge a `values` snapshot.\n * - `recordToolCallNamespace(ns, id)` — capture `namespace → tool_call_id`\n * so subsequent tool message starts can recover the id.\n * - `reset()` — clear all state on thread rebind.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"./assembled-to-message.js\";\nimport type { StreamStore } from \"./store.js\";\nimport type { RootSnapshot } from \"./types.js\";\nimport type { SubagentDiscovery } from \"./discovery/index.js\";\nimport { namespaceKey } from \"./namespace.js\";\nimport {\n buildMessageIndex,\n messagesEqual,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"./message-reconciliation.js\";\n\n/**\n * Root-namespace message projection. Owns the merge between the\n * `messages` (streamed deltas) and `values` (authoritative\n * snapshots) channels for the root namespace.\n *\n * @typeParam StateType - Root state shape; the messages array is read\n * from `values[messagesKey]`.\n * @typeParam InterruptType - Shape of root protocol interrupts (forwarded\n * into `RootSnapshot` updates).\n */\nexport class RootMessageProjection<\n StateType extends object,\n InterruptType = unknown,\n> {\n /**\n * Key inside `values` that holds the message array. Defaults to\n * `\"messages\"` in the controller; configurable for state graphs\n * that surface messages under a different slot.\n */\n readonly #messagesKey: string;\n\n /** Root snapshot store written to on every merge. */\n readonly #store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n\n /**\n * Subagent discovery runner notified about every assembled root\n * message. Driving discovery from assembled messages (rather than\n * raw events) lets us discover subagents from synthesized\n * `tool_calls` without re-parsing protocol payloads.\n */\n readonly #subagents: SubagentDiscovery;\n\n /**\n * Stateful chunk assembler for in-flight messages. Reset (via a\n * fresh instance) on every {@link reset} so a new thread starts\n * with no half-built messages from the previous one.\n */\n #assembler = new MessageAssembler();\n\n /**\n * `messageId → role/toolCallId` captured from `message-start` events.\n * The assembler's intermediate output drops these fields, so we cache\n * them at start-time and reapply when projecting to a `BaseMessage`.\n */\n readonly #roles = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n\n /**\n * `messageId → position in #store.messages` for fast in-place\n * updates as deltas arrive. Rebuilt on every full reconciliation\n * driven by a `values` event.\n */\n readonly #indexById = new Map<string, number>();\n\n /**\n * Ids observed in the most recent `values.messages` snapshot.\n * Reconciliation uses this to detect server-side removals: a\n * previously-seen id missing from the next snapshot means it was\n * removed by the server (and should drop from the projection).\n */\n #valuesMessageIds = new Set<string>();\n\n /**\n * `namespaceKey → tool_call_id` captured from root `tool-started`\n * events. Used as a fallback when a tool-role `message-start` is\n * missing its `tool_call_id` field.\n */\n readonly #toolCallIdByNamespace = new Map<string, string>();\n\n /**\n * @param params.messagesKey - Key inside `values` that holds the\n * message array.\n * @param params.store - Root snapshot store to mutate.\n * @param params.subagents - Discovery runner fed by each new\n * assembled message.\n */\n constructor(params: {\n messagesKey: string;\n store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n subagents: SubagentDiscovery;\n }) {\n this.#messagesKey = params.messagesKey;\n this.#store = params.store;\n this.#subagents = params.subagents;\n }\n\n /**\n * Drop all per-thread state. Called by the controller on thread\n * rebind / dispose so a swap doesn't surface stale messages.\n */\n reset(): void {\n this.#assembler = new MessageAssembler();\n this.#roles.clear();\n this.#indexById.clear();\n this.#valuesMessageIds = new Set();\n this.#toolCallIdByNamespace.clear();\n }\n\n /**\n * Record a `namespace → tool_call_id` mapping captured from a root\n * `tool-started` event.\n *\n * The companion tool-role `message-start` event may not carry a\n * `tool_call_id`, so we fall back to the most recent value recorded\n * here for the same namespace.\n *\n * @param namespace - Event namespace from the `tool-started` event.\n * @param toolCallId - Tool call id from the same event.\n */\n recordToolCallNamespace(\n namespace: readonly string[],\n toolCallId: string\n ): void {\n this.#toolCallIdByNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Apply a `messages` channel event to the projection.\n *\n * Captures role/tool metadata on `message-start`, feeds the chunk\n * to the assembler, projects the assembled output to a\n * {@link BaseMessage}, and either appends or in-place updates the\n * store's messages array based on whether the id was seen before.\n *\n * @param event - The `messages` channel event to consume.\n */\n handleMessage(event: MessagesEvent): void {\n const data = event.params.data;\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n let toolCallId = (startData as { tool_call_id?: string }).tool_call_id;\n // Tool messages need a tool_call_id to render. Fall back through:\n // 1. legacy `<id>-tool-<call_id>` message id format\n // 2. namespace-recorded tool_call_id (from #recordToolCallNamespace)\n if (extendedRole === \"tool\" && toolCallId == null) {\n const messageId = startData.id;\n if (messageId != null) {\n const match = /-tool-(.+)$/.exec(messageId);\n if (match != null) toolCallId = match[1];\n }\n if (toolCallId == null) {\n toolCallId = this.#toolCallIdByNamespace.get(\n namespaceKey(event.params.namespace)\n );\n }\n }\n if (startData.id != null) {\n this.#roles.set(startData.id, {\n role: extendedRole,\n toolCallId,\n });\n }\n }\n\n const update = this.#assembler.consume(event);\n if (update == null) return;\n const id = update.message.id;\n if (id == null) return;\n const captured = this.#roles.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(update.message, captured.role, {\n toolCallId: captured.toolCallId,\n });\n this.#subagents.discoverFromMessage(base, event.params.namespace);\n\n this.#store.setState((s) => {\n const existingIdx = this.#indexById.get(id);\n let messages: BaseMessage[];\n if (existingIdx == null) {\n // First sighting: append at end and remember its index for\n // future delta updates.\n this.#indexById.set(id, s.messages.length);\n messages = [...s.messages, base];\n } else if (messagesEqual(s.messages[existingIdx], base)) {\n // Identical re-emission of an already-projected message —\n // skip the store write to keep snapshot identity stable.\n return s;\n } else {\n // In-place update for a known id.\n messages = s.messages.slice();\n messages[existingIdx] = base;\n }\n\n // Mirror the new messages list into `values[messagesKey]` so\n // direct `values` reads (used by some hooks and by the eventual\n // `values` reconciliation) stay in sync.\n const values = syncMessagesIntoValues(\n s.values,\n this.#messagesKey,\n messages\n );\n return values === s.values\n ? { ...s, messages }\n : { ...s, messages, values };\n });\n }\n\n /**\n * Reconcile a full `values` snapshot into the projection.\n *\n * Delegates the merge to {@link reconcileMessagesFromValues}:\n * values stays authoritative for ordering and removals, while\n * streamed in-flight messages keep their content until the server\n * echoes them back. Empty messages just refresh the values blob.\n *\n * Rebuilds {@link #indexById} after the merge so subsequent delta\n * applications target the new positions.\n *\n * @param nextValues - Full values snapshot from the `values` event.\n * @param nextMessages - The messages array extracted from\n * `values[messagesKey]` and coerced to `BaseMessage` instances.\n */\n applyValues(nextValues: StateType, nextMessages: BaseMessage[]): void {\n this.#store.setState((s) => {\n if (nextMessages.length === 0) {\n return stateValuesShallowEqual(s.values, nextValues, this.#messagesKey)\n ? s\n : { ...s, values: nextValues };\n }\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: nextMessages,\n currentMessages: s.messages,\n currentIndexById: this.#indexById,\n previousValueMessageIds: this.#valuesMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n });\n this.#valuesMessageIds = reconciliation.valueMessageIds;\n const messages = reconciliation.messages as BaseMessage[];\n const values = {\n ...(nextValues as Record<string, unknown>),\n [this.#messagesKey]: messages,\n } as StateType;\n if (\n messages === s.messages &&\n stateValuesShallowEqual(s.values, values, this.#messagesKey)\n ) {\n return s;\n }\n\n // Reconciliation may reorder, drop, or substitute messages, so\n // rebuild the id → index map to match the new array.\n this.#indexById.clear();\n for (const [id, idx] of buildMessageIndex(messages)) {\n this.#indexById.set(id, idx);\n }\n return {\n ...s,\n values,\n messages,\n };\n });\n }\n}\n\n/**\n * Mirror a freshly-updated message list into `values[messagesKey]`.\n *\n * Returns the same `values` reference when the list is already\n * equal-by-content so the caller can keep the existing snapshot\n * identity (and avoid spurious `setSnapshot` notifications).\n */\nfunction syncMessagesIntoValues<StateType extends object>(\n values: StateType,\n messagesKey: string,\n messages: BaseMessage[]\n): StateType {\n const record = values as Record<string, unknown>;\n const current = record[messagesKey];\n if (Array.isArray(current) && messagesEqualList(current, messages)) {\n return values;\n }\n return {\n ...record,\n [messagesKey]: messages,\n } as StateType;\n}\n\n/**\n * True when two `BaseMessage` arrays carry the same per-message\n * content (using {@link messagesEqual}).\n */\nfunction 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\n/**\n * Shallow-equal for `values` objects, *ignoring* the messages slot.\n *\n * The messages array is compared separately by the caller (via\n * {@link messagesEqualList}) because both arrays contain class\n * instances whose JSON representation is not stable across reads.\n */\nfunction stateValuesShallowEqual(\n previous: object,\n next: object,\n messagesKey: string\n): boolean {\n if (previous === next) return true;\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord);\n const nextKeys = Object.keys(nextRecord);\n if (previousKeys.length !== nextKeys.length) return false;\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n const previousValue = previousRecord[key];\n const nextValue = nextRecord[key];\n if (\n key === messagesKey &&\n Array.isArray(previousValue) &&\n Array.isArray(nextValue)\n ) {\n continue;\n }\n if (!Object.is(previousValue, nextValue)) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/stream/store.ts
|
|
2
|
+
var StreamStore = class {
|
|
3
|
+
#snapshot;
|
|
4
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
constructor(initial) {
|
|
6
|
+
this.#snapshot = initial;
|
|
7
|
+
}
|
|
8
|
+
subscribe = (listener) => {
|
|
9
|
+
this.#listeners.add(listener);
|
|
10
|
+
return () => {
|
|
11
|
+
this.#listeners.delete(listener);
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
getSnapshot = () => this.#snapshot;
|
|
15
|
+
/** Replace the snapshot and notify every listener. */
|
|
16
|
+
setValue = (next) => {
|
|
17
|
+
if (Object.is(next, this.#snapshot)) return;
|
|
18
|
+
this.#snapshot = next;
|
|
19
|
+
for (const listener of this.#listeners) listener();
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Functional update. The `updater` receives the current snapshot and
|
|
23
|
+
* MUST return a new object. Returning the same reference is a no-op.
|
|
24
|
+
*/
|
|
25
|
+
setState = (updater) => {
|
|
26
|
+
this.setValue(updater(this.#snapshot));
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
exports.StreamStore = StreamStore;
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=store.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.cjs","names":["#listeners","#snapshot"],"sources":["../../src/stream/store.ts"],"sourcesContent":["/**\n * Minimal observable store backing every framework binding.\n *\n * The shape is intentionally tiny:\n *\n * - `subscribe(listener) → unsubscribe` lines up with React's\n * `useSyncExternalStore`.\n * - `getSnapshot()` returns a referentially-stable value for\n * unchanged state so React can bail out of renders.\n * - Vue/Svelte/Angular bindings wrap `subscribe` + `getSnapshot` in\n * their own reactivity primitive (`shallowRef` / `writable` /\n * `signal`) in ~10 lines.\n *\n * Snapshot identity matters: a listener is only useful if\n * `getSnapshot()` returns a *different* reference when state changes.\n * Callers MUST pass a freshly-constructed value to {@link setState};\n * mutating the previous snapshot in place will break React's bail-out\n * and cause infinite re-renders.\n */\n\nexport type StoreListener = () => void;\n\nexport class StreamStore<T> {\n #snapshot: T;\n readonly #listeners = new Set<StoreListener>();\n\n constructor(initial: T) {\n this.#snapshot = initial;\n }\n\n subscribe = (listener: StoreListener): (() => void) => {\n this.#listeners.add(listener);\n return () => {\n this.#listeners.delete(listener);\n };\n };\n\n getSnapshot = (): T => this.#snapshot;\n\n /** Replace the snapshot and notify every listener. */\n setValue = (next: T): void => {\n if (Object.is(next, this.#snapshot)) return;\n this.#snapshot = next;\n for (const listener of this.#listeners) listener();\n };\n\n /**\n * Functional update. The `updater` receives the current snapshot and\n * MUST return a new object. Returning the same reference is a no-op.\n */\n setState = (updater: (previous: T) => T): void => {\n this.setValue(updater(this.#snapshot));\n };\n}\n"],"mappings":";AAsBA,IAAa,cAAb,MAA4B;CAC1B;CACA,6BAAsB,IAAI,KAAoB;CAE9C,YAAY,SAAY;AACtB,QAAA,WAAiB;;CAGnB,aAAa,aAA0C;AACrD,QAAA,UAAgB,IAAI,SAAS;AAC7B,eAAa;AACX,SAAA,UAAgB,OAAO,SAAS;;;CAIpC,oBAAuB,MAAA;;CAGvB,YAAY,SAAkB;AAC5B,MAAI,OAAO,GAAG,MAAM,MAAA,SAAe,CAAE;AACrC,QAAA,WAAiB;AACjB,OAAK,MAAM,YAAY,MAAA,UAAiB,WAAU;;;;;;CAOpD,YAAY,YAAsC;AAChD,OAAK,SAAS,QAAQ,MAAA,SAAe,CAAC"}
|