@langchain/langgraph-sdk 1.8.10 → 1.9.1
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 +6 -5
- package/dist/react/stream.lgp.cjs.map +1 -1
- package/dist/react/stream.lgp.js +4 -3
- 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 +1 -1
- package/dist/ui/manager.js +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 +2 -2
- package/dist/ui/orchestrator.d.cts +1 -1
- package/dist/ui/orchestrator.d.ts +1 -1
- package/dist/ui/orchestrator.d.ts.map +1 -1
- package/dist/ui/orchestrator.js +2 -2
- 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,1367 @@
|
|
|
1
|
+
import { matchesSubscription } from "./subscription.js";
|
|
2
|
+
import { MultiCursorBuffer } from "./multi-cursor-buffer.js";
|
|
3
|
+
import { ensureMessageInstances } from "../../ui/messages.js";
|
|
4
|
+
import { ToolCallAssembler } from "./handles/tools.js";
|
|
5
|
+
import { StreamingMessageAssembler, toStreamingMessageHandle } from "./messages.js";
|
|
6
|
+
import { MediaAssembler } from "./media.js";
|
|
7
|
+
import { SubgraphDiscoveryHandle } from "./handles/subgraphs.js";
|
|
8
|
+
import { SubagentDiscoveryHandle } from "./handles/subagents.js";
|
|
9
|
+
import "./handles/index.js";
|
|
10
|
+
import { ProtocolError } from "./error.js";
|
|
11
|
+
import "../../stream/constants.js";
|
|
12
|
+
import { isHeadlessToolInterrupt } from "../../headless-tools.js";
|
|
13
|
+
//#region src/client/stream/index.ts
|
|
14
|
+
const MESSAGE_LIKE_TYPES = new Set([
|
|
15
|
+
"human",
|
|
16
|
+
"user",
|
|
17
|
+
"ai",
|
|
18
|
+
"assistant",
|
|
19
|
+
"tool",
|
|
20
|
+
"system",
|
|
21
|
+
"function",
|
|
22
|
+
"remove"
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* When the state payload has a `messages` array containing plain
|
|
26
|
+
* serialized messages (objects with a recognized `type` field), coerce
|
|
27
|
+
* them into `@langchain/core/messages` class instances so remote runs
|
|
28
|
+
* expose the same shape as in-process runs.
|
|
29
|
+
*
|
|
30
|
+
* Returns the input unchanged when the payload is not an object, does
|
|
31
|
+
* not include a `messages` key, or contains entries that are already
|
|
32
|
+
* class instances / not message-like.
|
|
33
|
+
*/
|
|
34
|
+
function coerceStateMessages(value) {
|
|
35
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
36
|
+
const state = value;
|
|
37
|
+
const messages = state.messages;
|
|
38
|
+
if (!Array.isArray(messages) || messages.length === 0) return value;
|
|
39
|
+
if (!messages.some((msg) => {
|
|
40
|
+
if (msg == null || typeof msg !== "object") return false;
|
|
41
|
+
if (typeof msg.getType === "function") return false;
|
|
42
|
+
const type = msg.type;
|
|
43
|
+
return typeof type === "string" && MESSAGE_LIKE_TYPES.has(type);
|
|
44
|
+
})) return value;
|
|
45
|
+
return {
|
|
46
|
+
...state,
|
|
47
|
+
messages: ensureMessageInstances(messages)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function namespaceKey(ns) {
|
|
51
|
+
return ns.join("\0");
|
|
52
|
+
}
|
|
53
|
+
function maxSeq(current, next) {
|
|
54
|
+
if (next == null) return current;
|
|
55
|
+
if (current == null) return next;
|
|
56
|
+
return Math.max(current, next);
|
|
57
|
+
}
|
|
58
|
+
const ROOT_TERMINAL_LIFECYCLE_EVENTS = new Set([
|
|
59
|
+
"completed",
|
|
60
|
+
"failed",
|
|
61
|
+
"interrupted"
|
|
62
|
+
]);
|
|
63
|
+
/**
|
|
64
|
+
* Detect a root-namespace terminal lifecycle event. Used by
|
|
65
|
+
* `#startProjection`'s `endOnRootTerminal` guard to settle per-run
|
|
66
|
+
* dispatchers regardless of whether the shared-stream pause logic
|
|
67
|
+
* applies to their underlying subscription.
|
|
68
|
+
*/
|
|
69
|
+
function isRootTerminalLifecycle(event) {
|
|
70
|
+
if (event.method !== "lifecycle") return false;
|
|
71
|
+
if (event.params.namespace.length !== 0) return false;
|
|
72
|
+
const data = event.params.data;
|
|
73
|
+
return data?.event != null && ROOT_TERMINAL_LIFECYCLE_EVENTS.has(data.event);
|
|
74
|
+
}
|
|
75
|
+
function namespaceListsEqual(a, b) {
|
|
76
|
+
if (a === b) return true;
|
|
77
|
+
if (a === void 0 || b === void 0) return false;
|
|
78
|
+
if (a.length !== b.length) return false;
|
|
79
|
+
const aKeys = /* @__PURE__ */ new Set();
|
|
80
|
+
for (const ns of a) aKeys.add(namespaceKey(ns));
|
|
81
|
+
for (const ns of b) if (!aKeys.has(namespaceKey(ns))) return false;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Structural equality on filters. Two filters are equal iff they
|
|
86
|
+
* request the same channel set, the same namespace prefix set
|
|
87
|
+
* (with `undefined` meaning wildcard), and the same depth
|
|
88
|
+
* (with `undefined` meaning unbounded).
|
|
89
|
+
*/
|
|
90
|
+
function filterEqual(a, b) {
|
|
91
|
+
if (a === b) return true;
|
|
92
|
+
if (a == null || b == null) return false;
|
|
93
|
+
if (a.channels.length !== b.channels.length) return false;
|
|
94
|
+
const aChannels = new Set(a.channels);
|
|
95
|
+
for (const ch of b.channels) if (!aChannels.has(ch)) return false;
|
|
96
|
+
if (!namespaceListsEqual(a.namespaces, b.namespaces)) return false;
|
|
97
|
+
if ((a.depth ?? null) !== (b.depth ?? null)) return false;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function isPrefix(prefix, candidate) {
|
|
101
|
+
if (prefix.length > candidate.length) return false;
|
|
102
|
+
for (let i = 0; i < prefix.length; i += 1) if (prefix[i] !== candidate[i]) return false;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether the `coverer` filter delivers every event a subscription
|
|
107
|
+
* opened with `target` could want.
|
|
108
|
+
*
|
|
109
|
+
* Rules:
|
|
110
|
+
* - Channels: target.channels must be a subset of coverer.channels.
|
|
111
|
+
* - Namespaces:
|
|
112
|
+
* - coverer wildcard (`undefined`) → coverer covers all prefixes.
|
|
113
|
+
* - coverer explicit + target wildcard → not covered.
|
|
114
|
+
* - both explicit → every target prefix must have some coverer
|
|
115
|
+
* prefix that is its ancestor (coverer's prefix delivers events
|
|
116
|
+
* for all descendants, modulo depth).
|
|
117
|
+
* - Depth:
|
|
118
|
+
* - coverer unbounded (`undefined`) → depth is covered.
|
|
119
|
+
* - otherwise, for each target prefix `tp` covered by coverer
|
|
120
|
+
* prefix `cp`, the maximum event depth target wants
|
|
121
|
+
* (`tp.length + (target.depth ?? ∞) - cp.length`) must be
|
|
122
|
+
* `<= coverer.depth`. For a wildcard target with bounded depth,
|
|
123
|
+
* target's max absolute depth is `target.depth` (prefix is `[]`).
|
|
124
|
+
*/
|
|
125
|
+
function filterCovers(coverer, target) {
|
|
126
|
+
const covererChannels = new Set(coverer.channels);
|
|
127
|
+
for (const ch of target.channels) if (!covererChannels.has(ch)) return false;
|
|
128
|
+
const covererDepth = coverer.depth;
|
|
129
|
+
const targetDepth = target.depth;
|
|
130
|
+
if (coverer.namespaces == null) {
|
|
131
|
+
if (covererDepth == null) return true;
|
|
132
|
+
if (targetDepth == null) return false;
|
|
133
|
+
return targetDepth <= covererDepth;
|
|
134
|
+
}
|
|
135
|
+
if (target.namespaces == null) return false;
|
|
136
|
+
for (const tp of target.namespaces) if (!coverer.namespaces.some((cp) => {
|
|
137
|
+
if (!isPrefix(cp, tp)) return false;
|
|
138
|
+
if (covererDepth == null) return true;
|
|
139
|
+
if (targetDepth == null) return false;
|
|
140
|
+
return tp.length - cp.length + targetDepth <= covererDepth;
|
|
141
|
+
})) return false;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
function normalizeSubscribeParams(paramsOrChannels, options = {}) {
|
|
145
|
+
if (typeof paramsOrChannels === "object" && !Array.isArray(paramsOrChannels) && "channels" in paramsOrChannels) return paramsOrChannels;
|
|
146
|
+
const channels = Array.isArray(paramsOrChannels) ? [...paramsOrChannels] : [paramsOrChannels];
|
|
147
|
+
return {
|
|
148
|
+
...options,
|
|
149
|
+
channels
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Async iterable handle for raw event subscriptions.
|
|
154
|
+
*
|
|
155
|
+
* An optional `transform` maps each incoming event before it is queued
|
|
156
|
+
* or delivered to a waiting consumer. This is used by named custom
|
|
157
|
+
* channel subscriptions (e.g. `"custom:a2a"`) to unwrap the payload
|
|
158
|
+
* so callers receive the raw emitted data instead of the protocol
|
|
159
|
+
* event envelope.
|
|
160
|
+
*/
|
|
161
|
+
var SubscriptionHandle = class {
|
|
162
|
+
subscriptionId;
|
|
163
|
+
params;
|
|
164
|
+
queue = [];
|
|
165
|
+
waiters = [];
|
|
166
|
+
closed = false;
|
|
167
|
+
paused = false;
|
|
168
|
+
resumeResolve;
|
|
169
|
+
onUnsubscribe;
|
|
170
|
+
transform;
|
|
171
|
+
constructor(subscriptionId, params, onUnsubscribe, transform) {
|
|
172
|
+
this.subscriptionId = subscriptionId;
|
|
173
|
+
this.params = params;
|
|
174
|
+
this.onUnsubscribe = onUnsubscribe;
|
|
175
|
+
this.transform = transform ?? ((event) => event);
|
|
176
|
+
}
|
|
177
|
+
push(event) {
|
|
178
|
+
if (this.closed) return;
|
|
179
|
+
const value = this.transform(event);
|
|
180
|
+
const waiter = this.waiters.shift();
|
|
181
|
+
if (waiter) {
|
|
182
|
+
waiter({
|
|
183
|
+
done: false,
|
|
184
|
+
value
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.queue.push(value);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Pause the subscription: resolve all waiting iterators with `done: true`
|
|
192
|
+
* so `for await` loops exit, but keep the subscription alive. New events
|
|
193
|
+
* arriving while paused are still buffered. Call `resume()` to allow
|
|
194
|
+
* iterators to consume again.
|
|
195
|
+
*/
|
|
196
|
+
pause() {
|
|
197
|
+
if (this.closed) return;
|
|
198
|
+
this.paused = true;
|
|
199
|
+
while (this.waiters.length > 0) this.waiters.shift()?.({
|
|
200
|
+
done: true,
|
|
201
|
+
value: void 0
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Resume a paused subscription so new `for await` loops can consume
|
|
206
|
+
* buffered and future events.
|
|
207
|
+
*/
|
|
208
|
+
resume() {
|
|
209
|
+
this.paused = false;
|
|
210
|
+
this.resumeResolve?.();
|
|
211
|
+
this.resumeResolve = void 0;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Returns a promise that resolves when `resume()` is called. Resolves
|
|
215
|
+
* immediately if not currently paused.
|
|
216
|
+
*/
|
|
217
|
+
waitForResume() {
|
|
218
|
+
if (!this.paused) return Promise.resolve();
|
|
219
|
+
return new Promise((resolve) => {
|
|
220
|
+
this.resumeResolve = resolve;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
get isPaused() {
|
|
224
|
+
return this.paused;
|
|
225
|
+
}
|
|
226
|
+
close() {
|
|
227
|
+
this.closed = true;
|
|
228
|
+
this.paused = false;
|
|
229
|
+
while (this.waiters.length > 0) this.waiters.shift()?.({
|
|
230
|
+
done: true,
|
|
231
|
+
value: void 0
|
|
232
|
+
});
|
|
233
|
+
this.resumeResolve?.();
|
|
234
|
+
this.resumeResolve = void 0;
|
|
235
|
+
}
|
|
236
|
+
async unsubscribe() {
|
|
237
|
+
if (this.closed) return;
|
|
238
|
+
this.close();
|
|
239
|
+
await this.onUnsubscribe(this.subscriptionId);
|
|
240
|
+
}
|
|
241
|
+
[Symbol.asyncIterator]() {
|
|
242
|
+
return {
|
|
243
|
+
next: async () => {
|
|
244
|
+
if (this.queue.length > 0) return {
|
|
245
|
+
done: false,
|
|
246
|
+
value: this.queue.shift()
|
|
247
|
+
};
|
|
248
|
+
if (this.closed || this.paused) return {
|
|
249
|
+
done: true,
|
|
250
|
+
value: void 0
|
|
251
|
+
};
|
|
252
|
+
return await new Promise((resolve) => {
|
|
253
|
+
this.waiters.push(resolve);
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
return: async () => {
|
|
257
|
+
this.close();
|
|
258
|
+
return {
|
|
259
|
+
done: true,
|
|
260
|
+
value: void 0
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* High-level wrapper around a protocol connection to a specific thread.
|
|
268
|
+
*
|
|
269
|
+
* In the thread-centric protocol, threads are durable (backed by
|
|
270
|
+
* checkpoints) and connections are ephemeral. A `ThreadStream` is the
|
|
271
|
+
* client-side handle for interacting with a thread: starting runs,
|
|
272
|
+
* subscribing to events, consuming assembled projections (`messages`,
|
|
273
|
+
* `values`, `toolCalls`, etc.), and responding to interrupts.
|
|
274
|
+
*
|
|
275
|
+
* Construct via `client.threads.stream(threadId?, { assistantId? })`.
|
|
276
|
+
*
|
|
277
|
+
* @typeParam TExtensions - Optional map of `{ name: payload }` pairs
|
|
278
|
+
* describing the transformer projections the bound assistant exposes
|
|
279
|
+
* on `custom:<name>` channels. Narrows `thread.extensions.<name>` to
|
|
280
|
+
* `ThreadExtension<payload>`. Defaults to `Record<string, unknown>`.
|
|
281
|
+
*/
|
|
282
|
+
var ThreadStream = class {
|
|
283
|
+
threadId;
|
|
284
|
+
ordering = {};
|
|
285
|
+
run;
|
|
286
|
+
agent;
|
|
287
|
+
input;
|
|
288
|
+
state;
|
|
289
|
+
/**
|
|
290
|
+
* Whether the run was interrupted (a lifecycle "interrupted" event
|
|
291
|
+
* was received). Mirrors the in-process `run.interrupted`.
|
|
292
|
+
*/
|
|
293
|
+
interrupted = false;
|
|
294
|
+
/**
|
|
295
|
+
* Interrupt payloads collected during the run, if any.
|
|
296
|
+
* Mirrors the in-process `run.interrupts`.
|
|
297
|
+
*/
|
|
298
|
+
interrupts = [];
|
|
299
|
+
assistantId;
|
|
300
|
+
#nextCommandId;
|
|
301
|
+
#transportAdapter;
|
|
302
|
+
#pending = /* @__PURE__ */ new Map();
|
|
303
|
+
#subscriptions = /* @__PURE__ */ new Map();
|
|
304
|
+
#seenEventIds = /* @__PURE__ */ new Set();
|
|
305
|
+
/**
|
|
306
|
+
* Headless tool interrupts can be auto-resumed by the React hook before
|
|
307
|
+
* the shared SSE content pump has processed the root `interrupted`
|
|
308
|
+
* lifecycle event. `respondInput()` clears `interrupts`, so keep a
|
|
309
|
+
* short-lived marker here until that stale terminal passes through the
|
|
310
|
+
* content pump and we can avoid pausing it.
|
|
311
|
+
*/
|
|
312
|
+
#headlessInterruptsAwaitingTerminal = /* @__PURE__ */ new Set();
|
|
313
|
+
#closed = false;
|
|
314
|
+
#opened = false;
|
|
315
|
+
#openPromise;
|
|
316
|
+
#sharedStream = null;
|
|
317
|
+
#sharedStreamFilter = null;
|
|
318
|
+
#rotationState = "idle";
|
|
319
|
+
/** Pending `subscribe()` promises waiting for a covering rotation. */
|
|
320
|
+
#pendingSubResolves = [];
|
|
321
|
+
#terminalPauseTimer;
|
|
322
|
+
#terminalPauseSeq;
|
|
323
|
+
#lifecycleSubId = null;
|
|
324
|
+
#lifecycleStartPromise;
|
|
325
|
+
#lifecycleWatcherHandle = null;
|
|
326
|
+
#lifecycleWatcherStartPromise;
|
|
327
|
+
#onEventListeners = /* @__PURE__ */ new Set();
|
|
328
|
+
#messagesIterable;
|
|
329
|
+
#valuesProjection;
|
|
330
|
+
#toolCallsIterable;
|
|
331
|
+
#subgraphsIterable;
|
|
332
|
+
#subagentsIterable;
|
|
333
|
+
#outputPromise;
|
|
334
|
+
#extensionsProxy;
|
|
335
|
+
#extensionsCache = /* @__PURE__ */ new Map();
|
|
336
|
+
/**
|
|
337
|
+
* Shared state for the single `"custom"` channel subscription that
|
|
338
|
+
* backs every `thread.extensions.<name>` handle.
|
|
339
|
+
*
|
|
340
|
+
* One subscription is opened eagerly from {@link run.start} (mirroring
|
|
341
|
+
* the {@link values} eager-start pattern) so that per-name handles
|
|
342
|
+
* created before, during, or after the run can all resolve correctly.
|
|
343
|
+
*
|
|
344
|
+
* - `events` retains every custom event for backfill into
|
|
345
|
+
* late-constructed handles.
|
|
346
|
+
* - `eventListeners` fan new events out to live per-name handlers.
|
|
347
|
+
* - `endListeners` fire when the dispatcher's run terminates, so each
|
|
348
|
+
* handle can resolve its `PromiseLike` side with its last-seen
|
|
349
|
+
* payload.
|
|
350
|
+
*/
|
|
351
|
+
#extensionsDispatcherStarted = false;
|
|
352
|
+
#extensionsEnded = false;
|
|
353
|
+
#extensionsEvents = [];
|
|
354
|
+
#extensionsEventListeners = [];
|
|
355
|
+
#extensionsEndListeners = [];
|
|
356
|
+
/**
|
|
357
|
+
* Shared state for the single `messages`-channel subscription that
|
|
358
|
+
* backs every media handle iterable (`thread.audio`, `thread.images`,
|
|
359
|
+
* `thread.video`, `thread.files`). One subscription serves all four
|
|
360
|
+
* iterables; per-type buffers track the handles already emitted so
|
|
361
|
+
* late attachers replay through {@link MultiCursorBuffer}.
|
|
362
|
+
*/
|
|
363
|
+
#mediaDispatcherStarted = false;
|
|
364
|
+
#mediaAssembler;
|
|
365
|
+
/** Object URLs minted by media handles, tracked for {@link close} cleanup. */
|
|
366
|
+
#mediaHandles = /* @__PURE__ */ new Set();
|
|
367
|
+
#audioBuffer = new MultiCursorBuffer();
|
|
368
|
+
#imagesBuffer = new MultiCursorBuffer();
|
|
369
|
+
#videoBuffer = new MultiCursorBuffer();
|
|
370
|
+
#filesBuffer = new MultiCursorBuffer();
|
|
371
|
+
#fetchOption;
|
|
372
|
+
constructor(transportAdapter, options) {
|
|
373
|
+
if (!options?.assistantId) throw new Error("ThreadStream requires an assistantId option.");
|
|
374
|
+
this.#transportAdapter = transportAdapter;
|
|
375
|
+
this.threadId = transportAdapter.threadId;
|
|
376
|
+
this.assistantId = options.assistantId;
|
|
377
|
+
this.#nextCommandId = options.startingCommandId ?? 1;
|
|
378
|
+
this.#fetchOption = options.fetch;
|
|
379
|
+
this.run = { start: async (params) => {
|
|
380
|
+
this.#prepareForNextRun();
|
|
381
|
+
this.#ensureLifecycleTracking();
|
|
382
|
+
this.values;
|
|
383
|
+
return await this.#send("run.start", {
|
|
384
|
+
...params,
|
|
385
|
+
assistant_id: this.assistantId
|
|
386
|
+
});
|
|
387
|
+
} };
|
|
388
|
+
this.agent = { getTree: async (params = {}) => await this.#send("agent.getTree", params) };
|
|
389
|
+
this.input = {
|
|
390
|
+
respond: async (params) => {
|
|
391
|
+
this.#prepareForNextRun();
|
|
392
|
+
this.#ensureLifecycleTracking();
|
|
393
|
+
this.values;
|
|
394
|
+
await this.#send("input.respond", params);
|
|
395
|
+
},
|
|
396
|
+
inject: async (params) => {
|
|
397
|
+
await this.#send("input.inject", params);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
this.state = {
|
|
401
|
+
get: async (params) => await this.#send("state.get", params),
|
|
402
|
+
listCheckpoints: async (params) => await this.#send("state.listCheckpoints", params),
|
|
403
|
+
fork: async (params) => await this.#send("state.fork", params)
|
|
404
|
+
};
|
|
405
|
+
if (this.#transportAdapter.openEventStream == null) this.#consumeEvents();
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Ensure the underlying transport is connected.
|
|
409
|
+
*
|
|
410
|
+
* For HTTP/SSE this is a no-op. For WebSocket this performs the
|
|
411
|
+
* handshake. Called lazily on first command; safe to call multiple times.
|
|
412
|
+
*/
|
|
413
|
+
async #ensureOpen() {
|
|
414
|
+
if (this.#opened) return;
|
|
415
|
+
if (this.#openPromise == null) this.#openPromise = this.#transportAdapter.open().then(() => {
|
|
416
|
+
this.#opened = true;
|
|
417
|
+
});
|
|
418
|
+
await this.#openPromise;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Channels bundled into every lazy getter's SSE filter so that
|
|
422
|
+
* interrupt tracking works without a separate lifecycle subscription.
|
|
423
|
+
*/
|
|
424
|
+
#lifecycleChannels() {
|
|
425
|
+
return ["lifecycle", "input"];
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Lazily start a dedicated lifecycle+input subscription so that
|
|
429
|
+
* `thread.interrupted` / `thread.interrupts` work even when the
|
|
430
|
+
* caller never accesses a lazy getter (e.g. they only call
|
|
431
|
+
* `run.start` and `subscribe({ channels: ["custom:..."] })`).
|
|
432
|
+
*
|
|
433
|
+
* Idempotent and fire-and-forget — invoked from `run.start` and
|
|
434
|
+
* `input.respond`.
|
|
435
|
+
*/
|
|
436
|
+
#ensureLifecycleTracking() {
|
|
437
|
+
if (this.#lifecycleStartPromise != null) return;
|
|
438
|
+
this.#lifecycleStartPromise = (async () => {
|
|
439
|
+
this.#lifecycleSubId = (await this.#subscribeRaw({ channels: this.#lifecycleChannels() })).subscriptionId;
|
|
440
|
+
})().catch(() => void 0);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Reset interrupt state and resume all paused user subscriptions.
|
|
444
|
+
* Called before `run.start()` and `input.respond()` so that
|
|
445
|
+
* iterators on the same handle pick up the next run's events.
|
|
446
|
+
*/
|
|
447
|
+
#prepareForNextRun() {
|
|
448
|
+
this.interrupted = false;
|
|
449
|
+
this.interrupts.length = 0;
|
|
450
|
+
if (this.#terminalPauseTimer != null) {
|
|
451
|
+
clearTimeout(this.#terminalPauseTimer);
|
|
452
|
+
this.#terminalPauseTimer = void 0;
|
|
453
|
+
}
|
|
454
|
+
this.#terminalPauseSeq = void 0;
|
|
455
|
+
for (const [id, subscription] of this.#subscriptions) if (id !== this.#lifecycleSubId) subscription.resume();
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Streaming messages. Each `for await` loop gets an independent cursor
|
|
459
|
+
* over the shared buffer; late consumers see all previously emitted
|
|
460
|
+
* messages. Mirrors the in-process `run.messages`.
|
|
461
|
+
*/
|
|
462
|
+
get messages() {
|
|
463
|
+
if (this.#messagesIterable) return this.#messagesIterable;
|
|
464
|
+
const buffer = new MultiCursorBuffer();
|
|
465
|
+
this.#messagesIterable = buffer;
|
|
466
|
+
const assembler = new StreamingMessageAssembler();
|
|
467
|
+
this.#startProjection(["messages", ...this.#lifecycleChannels()], (event) => {
|
|
468
|
+
if (event.method !== "messages") return;
|
|
469
|
+
const msg = assembler.consume(event);
|
|
470
|
+
if (msg) buffer.push(toStreamingMessageHandle(msg));
|
|
471
|
+
}, () => buffer.close());
|
|
472
|
+
return buffer;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* State values. Iterable for intermediate snapshots; also
|
|
476
|
+
* `PromiseLike` — `await thread.values` resolves with the final
|
|
477
|
+
* state. Mirrors the in-process `run.values`.
|
|
478
|
+
*/
|
|
479
|
+
get values() {
|
|
480
|
+
if (this.#valuesProjection) return this.#valuesProjection;
|
|
481
|
+
const buffer = new MultiCursorBuffer();
|
|
482
|
+
let lastValue;
|
|
483
|
+
let resolveOutput;
|
|
484
|
+
const outputPromise = new Promise((resolve) => {
|
|
485
|
+
resolveOutput = resolve;
|
|
486
|
+
});
|
|
487
|
+
this.#outputPromise = outputPromise;
|
|
488
|
+
const projection = Object.assign(buffer, { then: (onfulfilled, onrejected) => outputPromise.then(onfulfilled, onrejected) });
|
|
489
|
+
this.#valuesProjection = projection;
|
|
490
|
+
this.#startProjection(["values", ...this.#lifecycleChannels()], (event) => {
|
|
491
|
+
if (event.method !== "values") return;
|
|
492
|
+
const data = coerceStateMessages(event.params.data);
|
|
493
|
+
lastValue = data;
|
|
494
|
+
buffer.push(data);
|
|
495
|
+
}, () => {
|
|
496
|
+
resolveOutput(lastValue);
|
|
497
|
+
buffer.close();
|
|
498
|
+
});
|
|
499
|
+
return projection;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Tool calls with promise-based output/status/error.
|
|
503
|
+
* Mirrors the in-process `run.toolCalls`.
|
|
504
|
+
*/
|
|
505
|
+
get toolCalls() {
|
|
506
|
+
if (this.#toolCallsIterable) return this.#toolCallsIterable;
|
|
507
|
+
const buffer = new MultiCursorBuffer();
|
|
508
|
+
this.#toolCallsIterable = buffer;
|
|
509
|
+
const assembler = new ToolCallAssembler();
|
|
510
|
+
this.#startProjection(["tools", ...this.#lifecycleChannels()], (event) => {
|
|
511
|
+
if (event.method !== "tools") return;
|
|
512
|
+
const tc = assembler.consume(event);
|
|
513
|
+
if (tc) buffer.push(tc);
|
|
514
|
+
}, () => buffer.close());
|
|
515
|
+
return buffer;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Discovered subgraphs. Mirrors the in-process `run.subgraphs`.
|
|
519
|
+
*/
|
|
520
|
+
get subgraphs() {
|
|
521
|
+
if (this.#subgraphsIterable) return this.#subgraphsIterable;
|
|
522
|
+
const buffer = new MultiCursorBuffer();
|
|
523
|
+
this.#subgraphsIterable = buffer;
|
|
524
|
+
(async () => {
|
|
525
|
+
const discovery = new SubgraphDiscoveryHandle(await this.#subscribeRaw({ channels: ["tools", ...this.#lifecycleChannels()] }), this, []);
|
|
526
|
+
for await (const sub of discovery) buffer.push(sub);
|
|
527
|
+
buffer.close();
|
|
528
|
+
})();
|
|
529
|
+
return buffer;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Discovered subagents.
|
|
533
|
+
*/
|
|
534
|
+
get subagents() {
|
|
535
|
+
if (this.#subagentsIterable) return this.#subagentsIterable;
|
|
536
|
+
const buffer = new MultiCursorBuffer();
|
|
537
|
+
this.#subagentsIterable = buffer;
|
|
538
|
+
(async () => {
|
|
539
|
+
const discovery = new SubagentDiscoveryHandle(await this.#subscribeRaw({ channels: ["tools", ...this.#lifecycleChannels()] }), this);
|
|
540
|
+
for await (const sub of discovery) buffer.push(sub);
|
|
541
|
+
buffer.close();
|
|
542
|
+
})();
|
|
543
|
+
return buffer;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Audio media handles, one per message containing at least one
|
|
547
|
+
* `AudioBlock`. Each `for await` opens an independent cursor over
|
|
548
|
+
* the shared buffer; late consumers replay every previously emitted
|
|
549
|
+
* audio handle.
|
|
550
|
+
*
|
|
551
|
+
* Yields one item per message on the first matching
|
|
552
|
+
* `content-block-start` — messages with no audio blocks are skipped.
|
|
553
|
+
*/
|
|
554
|
+
get audio() {
|
|
555
|
+
this.#ensureMediaDispatcher();
|
|
556
|
+
return this.#audioBuffer;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Image media handles, one per message containing at least one
|
|
560
|
+
* `ImageBlock`. See {@link audio} for shared semantics.
|
|
561
|
+
*/
|
|
562
|
+
get images() {
|
|
563
|
+
this.#ensureMediaDispatcher();
|
|
564
|
+
return this.#imagesBuffer;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Video media handles, one per message containing at least one
|
|
568
|
+
* `VideoBlock`. See {@link audio} for shared semantics.
|
|
569
|
+
*/
|
|
570
|
+
get video() {
|
|
571
|
+
this.#ensureMediaDispatcher();
|
|
572
|
+
return this.#videoBuffer;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* File media handles, one per message containing at least one
|
|
576
|
+
* `FileBlock`. See {@link audio} for shared semantics.
|
|
577
|
+
*/
|
|
578
|
+
get files() {
|
|
579
|
+
this.#ensureMediaDispatcher();
|
|
580
|
+
return this.#filesBuffer;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Promise that resolves with the final state value when the run
|
|
584
|
+
* completes. Shares the `values` getter's SSE connection.
|
|
585
|
+
* Mirrors the in-process `run.output`.
|
|
586
|
+
*/
|
|
587
|
+
get output() {
|
|
588
|
+
this.values;
|
|
589
|
+
return this.#outputPromise;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Proxy over compile-time {@link StreamTransformer} projections
|
|
593
|
+
* exposed by the bound assistant on `custom:<name>` channels.
|
|
594
|
+
*
|
|
595
|
+
* Each access (e.g. `thread.extensions.toolActivity`) lazily opens a
|
|
596
|
+
* dedicated `custom:<name>` subscription, returns a cached
|
|
597
|
+
* {@link ThreadExtension} handle that is both `AsyncIterable<T>`
|
|
598
|
+
* (streaming items as they arrive) and `PromiseLike<T>` (resolves
|
|
599
|
+
* with the final value when the run terminates), and reuses the same
|
|
600
|
+
* handle on subsequent access.
|
|
601
|
+
*
|
|
602
|
+
* Mirrors the in-process `run.extensions.<name>` shape.
|
|
603
|
+
*/
|
|
604
|
+
get extensions() {
|
|
605
|
+
if (this.#extensionsProxy) return this.#extensionsProxy;
|
|
606
|
+
const cache = this.#extensionsCache;
|
|
607
|
+
const createExtension = (name) => this.#createExtension(name);
|
|
608
|
+
this.#extensionsProxy = new Proxy(Object.create(null), {
|
|
609
|
+
get: (_target, prop) => {
|
|
610
|
+
if (typeof prop !== "string") return void 0;
|
|
611
|
+
const cached = cache.get(prop);
|
|
612
|
+
if (cached) return cached;
|
|
613
|
+
const extension = createExtension(prop);
|
|
614
|
+
cache.set(prop, extension);
|
|
615
|
+
return extension;
|
|
616
|
+
},
|
|
617
|
+
has: (_target, prop) => typeof prop === "string"
|
|
618
|
+
});
|
|
619
|
+
return this.#extensionsProxy;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Lazily open one shared subscription on the `custom` channel that
|
|
623
|
+
* buffers every custom event for this run and fans it out to any
|
|
624
|
+
* per-name extension handles.
|
|
625
|
+
*
|
|
626
|
+
* Deliberately **lazy**: the dispatcher only starts on first access
|
|
627
|
+
* to `thread.extensions.<name>`. Runs that never touch extensions
|
|
628
|
+
* pay no subscription cost. Runs that touch extensions after events
|
|
629
|
+
* have already fired rely on the server's per-session event buffer,
|
|
630
|
+
* which replays matching events to new subscriptions.
|
|
631
|
+
*
|
|
632
|
+
* Each handle retains a PromiseLike that resolves with the
|
|
633
|
+
* transformer's last-observed payload, independent of when the
|
|
634
|
+
* caller grabs the handle (before, during, or after the run), as
|
|
635
|
+
* long as the server still has the events buffered.
|
|
636
|
+
*
|
|
637
|
+
* Idempotent. Invoked only from {@link #createExtension}.
|
|
638
|
+
*/
|
|
639
|
+
#ensureExtensionsDispatcher() {
|
|
640
|
+
if (this.#extensionsDispatcherStarted) return;
|
|
641
|
+
this.#extensionsDispatcherStarted = true;
|
|
642
|
+
this.#startProjection(["custom", ...this.#lifecycleChannels()], (event) => {
|
|
643
|
+
if (event.method !== "custom") return;
|
|
644
|
+
this.#extensionsEvents.push(event);
|
|
645
|
+
for (const listener of this.#extensionsEventListeners) listener(event);
|
|
646
|
+
}, () => {
|
|
647
|
+
this.#extensionsEnded = true;
|
|
648
|
+
const listeners = this.#extensionsEndListeners.splice(0);
|
|
649
|
+
for (const listener of listeners) listener();
|
|
650
|
+
}, { endOnRootTerminal: true });
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Open the single shared `messages`-channel subscription that backs
|
|
654
|
+
* every media iterable (audio/images/video/files). Idempotent.
|
|
655
|
+
*
|
|
656
|
+
* The {@link MediaAssembler} fans out to four per-type
|
|
657
|
+
* {@link MultiCursorBuffer}s; each buffer feeds its corresponding
|
|
658
|
+
* lazy getter. One handle is yielded per `(messageId, blockType)` on
|
|
659
|
+
* the first matching `content-block-start`, so messages without any
|
|
660
|
+
* media blocks of a given type never appear on that iterable.
|
|
661
|
+
*/
|
|
662
|
+
#ensureMediaDispatcher() {
|
|
663
|
+
if (this.#mediaDispatcherStarted) return;
|
|
664
|
+
this.#mediaDispatcherStarted = true;
|
|
665
|
+
const assembler = new MediaAssembler({
|
|
666
|
+
fetch: this.#fetchOption,
|
|
667
|
+
onAudio: (m) => {
|
|
668
|
+
this.#mediaHandles.add(m);
|
|
669
|
+
this.#audioBuffer.push(m);
|
|
670
|
+
},
|
|
671
|
+
onImage: (m) => {
|
|
672
|
+
this.#mediaHandles.add(m);
|
|
673
|
+
this.#imagesBuffer.push(m);
|
|
674
|
+
},
|
|
675
|
+
onVideo: (m) => {
|
|
676
|
+
this.#mediaHandles.add(m);
|
|
677
|
+
this.#videoBuffer.push(m);
|
|
678
|
+
},
|
|
679
|
+
onFile: (m) => {
|
|
680
|
+
this.#mediaHandles.add(m);
|
|
681
|
+
this.#filesBuffer.push(m);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
this.#mediaAssembler = assembler;
|
|
685
|
+
this.#startProjection(["messages", ...this.#lifecycleChannels()], (event) => {
|
|
686
|
+
if (event.method !== "messages") return;
|
|
687
|
+
assembler.consume(event);
|
|
688
|
+
}, () => {
|
|
689
|
+
assembler.close();
|
|
690
|
+
this.#audioBuffer.close();
|
|
691
|
+
this.#imagesBuffer.close();
|
|
692
|
+
this.#videoBuffer.close();
|
|
693
|
+
this.#filesBuffer.close();
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Build a single {@link ThreadExtension} handle for a named
|
|
698
|
+
* `custom:<name>` projection.
|
|
699
|
+
*
|
|
700
|
+
* The handle reads from the shared extensions dispatcher: past events
|
|
701
|
+
* matching {@link name} are backfilled on construction, future events
|
|
702
|
+
* arrive via a registered listener, and the handle's `PromiseLike`
|
|
703
|
+
* side resolves with its last-seen payload once the run terminates
|
|
704
|
+
* (which may already have happened, in which case it resolves on the
|
|
705
|
+
* next microtask).
|
|
706
|
+
*/
|
|
707
|
+
#createExtension(name) {
|
|
708
|
+
this.#ensureExtensionsDispatcher();
|
|
709
|
+
const buffer = new MultiCursorBuffer();
|
|
710
|
+
let lastValue;
|
|
711
|
+
let resolveFinal;
|
|
712
|
+
const finalPromise = new Promise((resolve) => {
|
|
713
|
+
resolveFinal = resolve;
|
|
714
|
+
});
|
|
715
|
+
const handleEvent = (event) => {
|
|
716
|
+
const data = event.params.data;
|
|
717
|
+
if (data?.name !== name) return;
|
|
718
|
+
lastValue = data.payload;
|
|
719
|
+
buffer.push(data.payload);
|
|
720
|
+
};
|
|
721
|
+
for (const event of this.#extensionsEvents) handleEvent(event);
|
|
722
|
+
this.#extensionsEventListeners.push(handleEvent);
|
|
723
|
+
const settle = () => {
|
|
724
|
+
resolveFinal(lastValue);
|
|
725
|
+
buffer.close();
|
|
726
|
+
};
|
|
727
|
+
if (this.#extensionsEnded) settle();
|
|
728
|
+
else this.#extensionsEndListeners.push(settle);
|
|
729
|
+
return Object.assign(buffer, { then: (onfulfilled, onrejected) => finalPromise.then(onfulfilled, onrejected) });
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Generic projection starter: opens a raw subscription with the given
|
|
733
|
+
* channels, feeds events through the consumer, and calls onDone when
|
|
734
|
+
* the stream ends.
|
|
735
|
+
*
|
|
736
|
+
* When `endOnRootTerminal` is set, the projection unsubscribes its
|
|
737
|
+
* own handle one macrotask after observing a root-namespace terminal
|
|
738
|
+
* lifecycle event. This is needed by projections that may be opened
|
|
739
|
+
* AFTER a run already terminated: the shared-stream pause logic
|
|
740
|
+
* skips subscriptions whose `registeredAfterSeq` is past the
|
|
741
|
+
* terminal so raw `subscribe()` callers can keep draining replayed
|
|
742
|
+
* descendants — but a per-run dispatcher (e.g. the extensions
|
|
743
|
+
* pipeline) needs the projection to settle so its `PromiseLike`
|
|
744
|
+
* surface resolves. The macrotask deferral mirrors the deferred
|
|
745
|
+
* pause in `#handleIncoming`, giving trailing same-tick custom
|
|
746
|
+
* events (transformer `finalize()` flushes) a chance to drain.
|
|
747
|
+
*/
|
|
748
|
+
async #startProjection(channels, onEvent, onDone, options = {}) {
|
|
749
|
+
let endTimer;
|
|
750
|
+
let rawHandle;
|
|
751
|
+
try {
|
|
752
|
+
rawHandle = await this.#subscribeRaw({ channels });
|
|
753
|
+
const handle = rawHandle;
|
|
754
|
+
for await (const event of handle) {
|
|
755
|
+
onEvent(event);
|
|
756
|
+
if (options.endOnRootTerminal && endTimer == null && isRootTerminalLifecycle(event)) endTimer = setTimeout(() => {
|
|
757
|
+
endTimer = void 0;
|
|
758
|
+
handle.unsubscribe().catch(() => void 0);
|
|
759
|
+
}, 0);
|
|
760
|
+
}
|
|
761
|
+
} catch {} finally {
|
|
762
|
+
if (endTimer != null) clearTimeout(endTimer);
|
|
763
|
+
onDone();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Start a run without the v1 eager lazy-getter shims.
|
|
768
|
+
*
|
|
769
|
+
* `run.start` (the v1 entry point) eagerly opens a wildcard `values`
|
|
770
|
+
* projection so `thread.output` / `thread.values` resolve regardless
|
|
771
|
+
* of access order, and calls `#ensureLifecycleTracking` which opens
|
|
772
|
+
* another wildcard `["lifecycle", "input"]` subscription. Both
|
|
773
|
+
* subscriptions widen `#computeUnionFilter` to wildcard, defeating
|
|
774
|
+
* the progressive-expansion rotation strategy.
|
|
775
|
+
*
|
|
776
|
+
* `submitRun` skips those shims — callers that manage their own
|
|
777
|
+
* content subscriptions (such as `StreamController`) get the narrow
|
|
778
|
+
* union filter they asked for. Lifecycle / interrupt tracking is
|
|
779
|
+
* instead served by the dedicated `#startLifecycleWatcher`, which
|
|
780
|
+
* opens a wildcard `["lifecycle", "input"]` stream alongside the
|
|
781
|
+
* narrow content pump on both SSE and WebSocket transports.
|
|
782
|
+
*/
|
|
783
|
+
async submitRun(params) {
|
|
784
|
+
this.#prepareForNextRun();
|
|
785
|
+
this.#startLifecycleWatcher();
|
|
786
|
+
return await this.#send("run.start", {
|
|
787
|
+
...params,
|
|
788
|
+
assistant_id: this.assistantId
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Respond to an interrupt without the v1 eager lazy-getter shims.
|
|
793
|
+
* See {@link submitRun} for why this exists alongside
|
|
794
|
+
* {@link input.respond}.
|
|
795
|
+
*/
|
|
796
|
+
async respondInput(params) {
|
|
797
|
+
this.#prepareForNextRun();
|
|
798
|
+
this.#startLifecycleWatcher();
|
|
799
|
+
await this.#send("input.respond", params);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Register a listener for every globally-unique event on the thread.
|
|
803
|
+
*
|
|
804
|
+
* Fires exactly once per `event_id` across both the content pump
|
|
805
|
+
* (user `subscribe()` calls) and the lifecycle watcher. Events
|
|
806
|
+
* without an `event_id` always fire through (dedup is best-effort).
|
|
807
|
+
*
|
|
808
|
+
* Returns an unsubscribe function. Primary consumer is
|
|
809
|
+
* `StreamController`, which uses the listener to feed discovery
|
|
810
|
+
* runners and pick up deeply-nested interrupts that the narrow
|
|
811
|
+
* content pump wouldn't deliver.
|
|
812
|
+
*/
|
|
813
|
+
onEvent(listener) {
|
|
814
|
+
this.#onEventListeners.add(listener);
|
|
815
|
+
return () => {
|
|
816
|
+
this.#onEventListeners.delete(listener);
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Lazily open the wildcard discovery watcher stream.
|
|
821
|
+
*
|
|
822
|
+
* Idempotent. Used by both transports, but through different
|
|
823
|
+
* mechanisms:
|
|
824
|
+
*
|
|
825
|
+
* - **SSE**: opens a dedicated event stream via
|
|
826
|
+
* {@link TransportAdapter.openEventStream}. The stream runs
|
|
827
|
+
* outside `#computeUnionFilter`, so the shared SSE stream's
|
|
828
|
+
* content pump can stay narrow (e.g. `depth: 1`) while we still
|
|
829
|
+
* capture every lifecycle/input event at any depth.
|
|
830
|
+
* - **WebSocket**: opens a wildcard watcher subscription
|
|
831
|
+
* subscription via the normal command path. The WS server
|
|
832
|
+
* delivers matching events on the shared command connection and
|
|
833
|
+
* `#handleIncoming` dispatches them through `#fireOnEvent` and
|
|
834
|
+
* the thread-level effects — same downstream semantics as the
|
|
835
|
+
* SSE watcher, just reusing the transport that's already open.
|
|
836
|
+
*
|
|
837
|
+
* Why this matters: consumers of {@link onEvent} (notably
|
|
838
|
+
* `StreamController`'s subgraph/subagent discovery runners and
|
|
839
|
+
* nested interrupt capture) depend on observing namespaced
|
|
840
|
+
* lifecycle events at any depth. Without this watcher, WS clients
|
|
841
|
+
* would only ever receive events matching the content pump's
|
|
842
|
+
* narrow filter (depth 1 from the root), breaking inference rules
|
|
843
|
+
* that require deeper descendants (e.g. the "has-descendants"
|
|
844
|
+
* signal used to promote a subgraph host).
|
|
845
|
+
*/
|
|
846
|
+
#startLifecycleWatcher() {
|
|
847
|
+
if (this.#lifecycleWatcherStartPromise != null) return;
|
|
848
|
+
if (this.#transportAdapter.openEventStream != null) {
|
|
849
|
+
this.#lifecycleWatcherStartPromise = this.#startLifecycleWatcherSse();
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
this.#lifecycleWatcherStartPromise = this.#startLifecycleWatcherWebSocket();
|
|
853
|
+
}
|
|
854
|
+
async #startLifecycleWatcherSse() {
|
|
855
|
+
const filter = { channels: ["lifecycle", "input"] };
|
|
856
|
+
let handle;
|
|
857
|
+
try {
|
|
858
|
+
handle = this.#transportAdapter.openEventStream(filter);
|
|
859
|
+
} catch {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
await handle.ready;
|
|
864
|
+
} catch {
|
|
865
|
+
try {
|
|
866
|
+
handle.close();
|
|
867
|
+
} catch {}
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (this.#closed) {
|
|
871
|
+
try {
|
|
872
|
+
handle.close();
|
|
873
|
+
} catch {}
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
this.#lifecycleWatcherHandle = handle;
|
|
877
|
+
try {
|
|
878
|
+
for await (const message of handle.events) {
|
|
879
|
+
if (this.#closed) break;
|
|
880
|
+
this.#handleLifecycleWatcherMessage(message);
|
|
881
|
+
}
|
|
882
|
+
} catch {}
|
|
883
|
+
}
|
|
884
|
+
async #startLifecycleWatcherWebSocket() {
|
|
885
|
+
let handle;
|
|
886
|
+
try {
|
|
887
|
+
handle = await this.#subscribeRaw({ channels: ["lifecycle", "input"] });
|
|
888
|
+
} catch {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (this.#closed) {
|
|
892
|
+
try {
|
|
893
|
+
handle.close();
|
|
894
|
+
} catch {}
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
try {
|
|
898
|
+
for await (const _event of handle) if (this.#closed) break;
|
|
899
|
+
} catch {}
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Process an event from the dedicated lifecycle watcher stream.
|
|
903
|
+
*
|
|
904
|
+
* Unlike `#handleIncoming`, this does NOT fan out to user
|
|
905
|
+
* subscriptions — user subs with namespace wildcards already widen
|
|
906
|
+
* `#computeUnionFilter` and therefore receive the event on the
|
|
907
|
+
* content pump. Delivering via both streams would only add per-sub
|
|
908
|
+
* dedup churn without expanding what the user can observe.
|
|
909
|
+
*
|
|
910
|
+
* We still run global-dedup thread-level side effects (interrupt
|
|
911
|
+
* capture, `onEvent` fan-out) so deeply-nested interrupts outside
|
|
912
|
+
* the content pump's narrow scope are recorded.
|
|
913
|
+
*/
|
|
914
|
+
#handleLifecycleWatcherMessage(message) {
|
|
915
|
+
if (message.type !== "event") return;
|
|
916
|
+
if (typeof message.seq === "number") this.ordering.lastSeenSeq = maxSeq(this.ordering.lastSeenSeq, message.seq);
|
|
917
|
+
if (message.event_id) this.ordering.lastEventId = message.event_id;
|
|
918
|
+
const eventId = message.event_id ?? void 0;
|
|
919
|
+
const globallyProcessed = eventId != null && this.#seenEventIds.has(eventId);
|
|
920
|
+
if (eventId != null) this.#seenEventIds.add(eventId);
|
|
921
|
+
if (globallyProcessed) return;
|
|
922
|
+
this.#applyThreadLevelEffects(message);
|
|
923
|
+
this.#fireOnEvent(message);
|
|
924
|
+
}
|
|
925
|
+
#applyThreadLevelEffects(event) {
|
|
926
|
+
if (event.method === "lifecycle") {
|
|
927
|
+
if (event.params.data.event === "interrupted") this.interrupted = true;
|
|
928
|
+
}
|
|
929
|
+
if (event.method === "input.requested") {
|
|
930
|
+
const data = event.params.data;
|
|
931
|
+
const interruptId = data.interrupt_id ?? `interrupt_${this.interrupts.length}`;
|
|
932
|
+
this.interrupts.push({
|
|
933
|
+
interruptId,
|
|
934
|
+
payload: data.payload,
|
|
935
|
+
namespace: [...event.params.namespace]
|
|
936
|
+
});
|
|
937
|
+
if (isHeadlessToolInterrupt(data.payload)) this.#headlessInterruptsAwaitingTerminal.add(interruptId);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
#fireOnEvent(event) {
|
|
941
|
+
if (this.#onEventListeners.size === 0) return;
|
|
942
|
+
for (const listener of this.#onEventListeners) try {
|
|
943
|
+
listener(event);
|
|
944
|
+
} catch {}
|
|
945
|
+
}
|
|
946
|
+
async close() {
|
|
947
|
+
if (this.#closed) return;
|
|
948
|
+
this.#closed = true;
|
|
949
|
+
if (this.#terminalPauseTimer != null) {
|
|
950
|
+
clearTimeout(this.#terminalPauseTimer);
|
|
951
|
+
this.#terminalPauseTimer = void 0;
|
|
952
|
+
}
|
|
953
|
+
this.#terminalPauseSeq = void 0;
|
|
954
|
+
for (const pending of this.#pendingSubResolves) pending.reject(/* @__PURE__ */ new Error("ThreadStream closed"));
|
|
955
|
+
this.#pendingSubResolves.length = 0;
|
|
956
|
+
if (this.#sharedStream != null) {
|
|
957
|
+
try {
|
|
958
|
+
this.#sharedStream.close();
|
|
959
|
+
} catch {}
|
|
960
|
+
this.#sharedStream = null;
|
|
961
|
+
this.#sharedStreamFilter = null;
|
|
962
|
+
}
|
|
963
|
+
if (this.#lifecycleWatcherHandle != null) {
|
|
964
|
+
try {
|
|
965
|
+
this.#lifecycleWatcherHandle.close();
|
|
966
|
+
} catch {}
|
|
967
|
+
this.#lifecycleWatcherHandle = null;
|
|
968
|
+
}
|
|
969
|
+
const lifecycleWatcherStartPromise = this.#lifecycleWatcherStartPromise;
|
|
970
|
+
this.#lifecycleWatcherStartPromise = void 0;
|
|
971
|
+
this.#onEventListeners.clear();
|
|
972
|
+
for (const subscription of this.#subscriptions.values()) subscription.close();
|
|
973
|
+
this.#subscriptions.clear();
|
|
974
|
+
try {
|
|
975
|
+
await lifecycleWatcherStartPromise;
|
|
976
|
+
} catch {}
|
|
977
|
+
for (const handle of this.#mediaHandles) try {
|
|
978
|
+
handle.revoke();
|
|
979
|
+
} catch {}
|
|
980
|
+
this.#mediaHandles.clear();
|
|
981
|
+
this.#mediaAssembler?.close();
|
|
982
|
+
this.#audioBuffer.close();
|
|
983
|
+
this.#imagesBuffer.close();
|
|
984
|
+
this.#videoBuffer.close();
|
|
985
|
+
this.#filesBuffer.close();
|
|
986
|
+
await this.#transportAdapter.close();
|
|
987
|
+
}
|
|
988
|
+
async subscribe(paramsOrChannels, options = {}) {
|
|
989
|
+
const isParamsObject = typeof paramsOrChannels === "object" && !Array.isArray(paramsOrChannels) && "channels" in paramsOrChannels;
|
|
990
|
+
const params = normalizeSubscribeParams(paramsOrChannels, options);
|
|
991
|
+
return await this.#subscribeRaw(params, { unwrapNamedCustom: !isParamsObject });
|
|
992
|
+
}
|
|
993
|
+
async #subscribeRaw(params, options = {}) {
|
|
994
|
+
await this.#ensureOpen();
|
|
995
|
+
const { unwrapNamedCustom = true } = options;
|
|
996
|
+
const hasOnlyNamedCustom = params.channels.length > 0 && params.channels.every((ch) => ch.startsWith("custom:"));
|
|
997
|
+
const transform = unwrapNamedCustom && hasOnlyNamedCustom ? (event) => event.params.data?.payload ?? event : void 0;
|
|
998
|
+
if (this.#transportAdapter.openEventStream != null) return this.#subscribeViaSharedStream(params, transform);
|
|
999
|
+
return this.#subscribeViaCommand(params, transform);
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Subscribe via the single shared SSE connection.
|
|
1003
|
+
*
|
|
1004
|
+
* The subscription is registered immediately in `#subscriptions` so
|
|
1005
|
+
* fan-out can reach it the moment events begin flowing. The returned
|
|
1006
|
+
* promise resolves after a stream rotation completes whose union
|
|
1007
|
+
* filter covers this subscription's channels — mirroring the per-sub
|
|
1008
|
+
* `await streamHandle.ready` semantics callers depended on.
|
|
1009
|
+
*
|
|
1010
|
+
* Every subscribe schedules a stream rotation, even when the current
|
|
1011
|
+
* stream's filter already covers `params`. Rotating opens a fresh
|
|
1012
|
+
* server-side session that replays the run's full history from
|
|
1013
|
+
* `seq=0`; without it a late-joining sub would only see events that
|
|
1014
|
+
* arrive after it registered, because the shared pump's dedup drops
|
|
1015
|
+
* events the existing sub already consumed. Per-sub dedup
|
|
1016
|
+
* (`seenEventIds`) protects existing subs from receiving the
|
|
1017
|
+
* replay as duplicates. Rapid subscribes in the same microtask are
|
|
1018
|
+
* coalesced by `#scheduleReconcile` into a single rotation.
|
|
1019
|
+
*/
|
|
1020
|
+
async #subscribeViaSharedStream(params, transform) {
|
|
1021
|
+
const subscriptionId = `sse-${this.#nextCommandId++}`;
|
|
1022
|
+
const handle = new SubscriptionHandle(subscriptionId, params, async (id) => {
|
|
1023
|
+
this.#subscriptions.delete(id);
|
|
1024
|
+
this.#scheduleReconcile();
|
|
1025
|
+
}, transform);
|
|
1026
|
+
const subscription = Object.assign(handle, {
|
|
1027
|
+
filter: params,
|
|
1028
|
+
registeredAfterSeq: this.ordering.lastSeenSeq,
|
|
1029
|
+
seenEventIds: /* @__PURE__ */ new Set()
|
|
1030
|
+
});
|
|
1031
|
+
this.#subscriptions.set(subscriptionId, subscription);
|
|
1032
|
+
const covered = new Promise((resolve, reject) => {
|
|
1033
|
+
this.#pendingSubResolves.push({
|
|
1034
|
+
filter: params,
|
|
1035
|
+
resolve,
|
|
1036
|
+
reject
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
this.#scheduleReconcile();
|
|
1040
|
+
try {
|
|
1041
|
+
await covered;
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
this.#subscriptions.delete(subscriptionId);
|
|
1044
|
+
throw err;
|
|
1045
|
+
}
|
|
1046
|
+
return handle;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Progressive-expansion union of every currently-registered
|
|
1050
|
+
* subscription's filter. The server receives the narrowest filter
|
|
1051
|
+
* that still covers every active sub so deeply-namespaced or
|
|
1052
|
+
* selectively-opened projections don't pull down the entire thread's
|
|
1053
|
+
* event firehose.
|
|
1054
|
+
*
|
|
1055
|
+
* Unioning rules (matching the server's matching semantics in
|
|
1056
|
+
* `matchesSinkFilter`):
|
|
1057
|
+
* - Channels: set union.
|
|
1058
|
+
* - Namespaces: if any subscription requests a wildcard
|
|
1059
|
+
* (`namespaces === undefined`) the union is wildcard; otherwise
|
|
1060
|
+
* the union is the deduplicated list of every explicit prefix.
|
|
1061
|
+
* - Depth: if any subscription is unbounded (`depth === undefined`)
|
|
1062
|
+
* the union is unbounded; otherwise the union is the maximum
|
|
1063
|
+
* depth across all subscriptions (matching the per-sub "max
|
|
1064
|
+
* reach below the prefix" semantics).
|
|
1065
|
+
*
|
|
1066
|
+
* Returns `null` when there are no subscriptions.
|
|
1067
|
+
*/
|
|
1068
|
+
#computeUnionFilter() {
|
|
1069
|
+
if (this.#subscriptions.size === 0) return null;
|
|
1070
|
+
const channels = /* @__PURE__ */ new Set();
|
|
1071
|
+
let wildcardNamespaces = false;
|
|
1072
|
+
const namespaceMap = /* @__PURE__ */ new Map();
|
|
1073
|
+
let unboundedDepth = false;
|
|
1074
|
+
let maxDepth = 0;
|
|
1075
|
+
for (const sub of this.#subscriptions.values()) {
|
|
1076
|
+
for (const ch of sub.filter.channels) channels.add(ch);
|
|
1077
|
+
if (sub.filter.namespaces == null) wildcardNamespaces = true;
|
|
1078
|
+
else if (!wildcardNamespaces) for (const ns of sub.filter.namespaces) namespaceMap.set(namespaceKey(ns), ns);
|
|
1079
|
+
if (sub.filter.depth == null) unboundedDepth = true;
|
|
1080
|
+
else if (!unboundedDepth && sub.filter.depth > maxDepth) maxDepth = sub.filter.depth;
|
|
1081
|
+
}
|
|
1082
|
+
const result = { channels: [...channels] };
|
|
1083
|
+
if (!wildcardNamespaces) result.namespaces = [...namespaceMap.values()];
|
|
1084
|
+
if (!unboundedDepth) result.depth = maxDepth;
|
|
1085
|
+
return result;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Schedule a stream reconciliation for the next microtask.
|
|
1089
|
+
*
|
|
1090
|
+
* Coalesces multiple subscribe/unsubscribe calls in the same tick
|
|
1091
|
+
* into a single rotation, and serializes across ticks (no two
|
|
1092
|
+
* rotations ever run concurrently).
|
|
1093
|
+
*/
|
|
1094
|
+
#scheduleReconcile() {
|
|
1095
|
+
if (this.#closed) return;
|
|
1096
|
+
if (this.#rotationState !== "idle") return;
|
|
1097
|
+
this.#rotationState = "scheduled";
|
|
1098
|
+
queueMicrotask(() => {
|
|
1099
|
+
if (this.#closed) {
|
|
1100
|
+
this.#rotationState = "idle";
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
this.#rotationState = "idle";
|
|
1104
|
+
this.#reconcileStream().catch(() => {
|
|
1105
|
+
this.#rotationState = "idle";
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Reconcile the shared SSE stream to match the desired union filter.
|
|
1111
|
+
*
|
|
1112
|
+
* Rotation strategy: open the new stream first, await its `ready`,
|
|
1113
|
+
* then close the old one. Overlap is absorbed by `#seenEventIds`
|
|
1114
|
+
* dedup in `#handleIncoming`.
|
|
1115
|
+
*
|
|
1116
|
+
* Error handling:
|
|
1117
|
+
* - Failure before `ready` resolves: reject all pending `subscribe`
|
|
1118
|
+
* promises whose filter isn't covered by the existing stream,
|
|
1119
|
+
* and keep the existing stream running for other subscriptions.
|
|
1120
|
+
* - Failure mid-pump on the active stream: close the thread via
|
|
1121
|
+
* {@link #failThreadWithError} so higher layers can rebind.
|
|
1122
|
+
*/
|
|
1123
|
+
async #reconcileStream() {
|
|
1124
|
+
if (this.#closed) return;
|
|
1125
|
+
if (this.#rotationState === "rotating") return;
|
|
1126
|
+
const desired = this.#computeUnionFilter();
|
|
1127
|
+
if (desired == null) return;
|
|
1128
|
+
if (this.#sharedStreamFilter != null && filterEqual(desired, this.#sharedStreamFilter) && this.#pendingSubResolves.length === 0) {
|
|
1129
|
+
this.#resolvePending();
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
this.#rotationState = "rotating";
|
|
1133
|
+
let newHandle;
|
|
1134
|
+
try {
|
|
1135
|
+
newHandle = this.#transportAdapter.openEventStream(desired);
|
|
1136
|
+
} catch (err) {
|
|
1137
|
+
this.#rotationState = "idle";
|
|
1138
|
+
this.#rejectUncoveredPending(err);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
try {
|
|
1142
|
+
await newHandle.ready;
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
this.#rotationState = "idle";
|
|
1145
|
+
try {
|
|
1146
|
+
newHandle.close();
|
|
1147
|
+
} catch {}
|
|
1148
|
+
this.#rejectUncoveredPending(err);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (this.#closed) {
|
|
1152
|
+
try {
|
|
1153
|
+
newHandle.close();
|
|
1154
|
+
} catch {}
|
|
1155
|
+
this.#rotationState = "idle";
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
this.#pumpStream(newHandle);
|
|
1159
|
+
const oldHandle = this.#sharedStream;
|
|
1160
|
+
this.#sharedStream = newHandle;
|
|
1161
|
+
this.#sharedStreamFilter = desired;
|
|
1162
|
+
if (oldHandle != null) try {
|
|
1163
|
+
oldHandle.close();
|
|
1164
|
+
} catch {}
|
|
1165
|
+
this.#rotationState = "idle";
|
|
1166
|
+
this.#resolvePending();
|
|
1167
|
+
const next = this.#computeUnionFilter();
|
|
1168
|
+
if (next != null && !filterEqual(next, this.#sharedStreamFilter)) this.#scheduleReconcile();
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Pump events from a shared-stream handle into `#handleIncoming`.
|
|
1172
|
+
* One pump task runs per open stream; during rotation overlap two
|
|
1173
|
+
* pumps may be active briefly, with `#seenEventIds` deduping.
|
|
1174
|
+
*/
|
|
1175
|
+
async #pumpStream(handle) {
|
|
1176
|
+
try {
|
|
1177
|
+
for await (const message of handle.events) {
|
|
1178
|
+
if (this.#closed) break;
|
|
1179
|
+
this.#handleIncoming(message);
|
|
1180
|
+
}
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
if (handle === this.#sharedStream && !this.#closed) this.#failThreadWithError(err);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Resolve any pending `subscribe()` promises whose filter is now
|
|
1187
|
+
* covered by the active shared stream. Called after every successful
|
|
1188
|
+
* rotation (and after no-op reconciliations).
|
|
1189
|
+
*/
|
|
1190
|
+
#resolvePending() {
|
|
1191
|
+
if (this.#sharedStreamFilter == null) return;
|
|
1192
|
+
const current = this.#sharedStreamFilter;
|
|
1193
|
+
if (this.#pendingSubResolves.length === 0) return;
|
|
1194
|
+
const stillPending = [];
|
|
1195
|
+
for (const pending of this.#pendingSubResolves) if (filterCovers(current, pending.filter)) pending.resolve();
|
|
1196
|
+
else stillPending.push(pending);
|
|
1197
|
+
this.#pendingSubResolves.length = 0;
|
|
1198
|
+
this.#pendingSubResolves.push(...stillPending);
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Reject pending `subscribe()` promises whose filter isn't covered
|
|
1202
|
+
* by the existing stream (they're the ones that triggered the
|
|
1203
|
+
* failed rotation). Covered pending subs are resolved normally —
|
|
1204
|
+
* they didn't need the new stream.
|
|
1205
|
+
*/
|
|
1206
|
+
#rejectUncoveredPending(err) {
|
|
1207
|
+
if (this.#pendingSubResolves.length === 0) return;
|
|
1208
|
+
const current = this.#sharedStreamFilter;
|
|
1209
|
+
const stillPending = [];
|
|
1210
|
+
for (const pending of this.#pendingSubResolves) if (current != null && filterCovers(current, pending.filter)) pending.resolve();
|
|
1211
|
+
else stillPending.push(pending);
|
|
1212
|
+
this.#pendingSubResolves.length = 0;
|
|
1213
|
+
for (const pending of stillPending) pending.reject(err);
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Terminate the thread due to an unrecoverable shared-stream error.
|
|
1217
|
+
* Rejects pending commands, closes subscriptions, and marks the
|
|
1218
|
+
* thread closed so no further rotations occur.
|
|
1219
|
+
*/
|
|
1220
|
+
#failThreadWithError(err) {
|
|
1221
|
+
const normalized = err instanceof Error ? err : new Error(String(err));
|
|
1222
|
+
for (const pending of this.#pending.values()) pending.reject(normalized);
|
|
1223
|
+
this.#pending.clear();
|
|
1224
|
+
for (const pending of this.#pendingSubResolves) pending.reject(normalized);
|
|
1225
|
+
this.#pendingSubResolves.length = 0;
|
|
1226
|
+
for (const subscription of this.#subscriptions.values()) subscription.close();
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Command-based subscription (WebSocket fallback). The server replays
|
|
1230
|
+
* matching buffered events on subscribe via the same WebSocket stream.
|
|
1231
|
+
*/
|
|
1232
|
+
async #subscribeViaCommand(params, transform) {
|
|
1233
|
+
const result = await this.#send("subscription.subscribe", params);
|
|
1234
|
+
const handle = new SubscriptionHandle(result.subscription_id, params, async (id) => {
|
|
1235
|
+
this.#subscriptions.delete(id);
|
|
1236
|
+
if (!this.#closed) await this.#send("subscription.unsubscribe", { subscription_id: id }).catch((err) => {
|
|
1237
|
+
if (err instanceof ProtocolError && err.code === "no_such_subscription") return;
|
|
1238
|
+
throw err;
|
|
1239
|
+
});
|
|
1240
|
+
}, transform);
|
|
1241
|
+
const subscription = Object.assign(handle, {
|
|
1242
|
+
filter: params,
|
|
1243
|
+
registeredAfterSeq: this.ordering.lastSeenSeq,
|
|
1244
|
+
seenEventIds: /* @__PURE__ */ new Set()
|
|
1245
|
+
});
|
|
1246
|
+
this.#subscriptions.set(result.subscription_id, subscription);
|
|
1247
|
+
return handle;
|
|
1248
|
+
}
|
|
1249
|
+
async #consumeEvents() {
|
|
1250
|
+
try {
|
|
1251
|
+
for await (const message of this.#transportAdapter.events()) this.#handleIncoming(message);
|
|
1252
|
+
for (const subscription of this.#subscriptions.values()) subscription.close();
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
1255
|
+
for (const pending of this.#pending.values()) pending.reject(normalized);
|
|
1256
|
+
for (const subscription of this.#subscriptions.values()) subscription.close();
|
|
1257
|
+
this.#pending.clear();
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Pause non-lifecycle subscriptions after a root terminal lifecycle.
|
|
1262
|
+
*
|
|
1263
|
+
* The pause is deferred one macrotask so same-run trailing events
|
|
1264
|
+
* emitted immediately after terminal (for example final `values`)
|
|
1265
|
+
* can still drain. `terminalSeq` lets replay attachers skip terminals
|
|
1266
|
+
* that happened before they registered, so late subscribers can keep
|
|
1267
|
+
* consuming the replayed history they joined for.
|
|
1268
|
+
*/
|
|
1269
|
+
#scheduleTerminalPause(terminalSeq) {
|
|
1270
|
+
if (this.#terminalPauseTimer != null) clearTimeout(this.#terminalPauseTimer);
|
|
1271
|
+
this.#terminalPauseSeq = terminalSeq ?? null;
|
|
1272
|
+
this.#terminalPauseTimer = setTimeout(() => {
|
|
1273
|
+
this.#terminalPauseTimer = void 0;
|
|
1274
|
+
if (this.#closed) return;
|
|
1275
|
+
for (const [id, subscription] of this.#subscriptions) {
|
|
1276
|
+
if (id === this.#lifecycleSubId) continue;
|
|
1277
|
+
if (terminalSeq != null && subscription.registeredAfterSeq != null && subscription.registeredAfterSeq >= terminalSeq) continue;
|
|
1278
|
+
subscription.pause();
|
|
1279
|
+
}
|
|
1280
|
+
}, 0);
|
|
1281
|
+
}
|
|
1282
|
+
#handleIncoming(message) {
|
|
1283
|
+
if (message.type === "event") {
|
|
1284
|
+
if (typeof message.seq === "number") this.ordering.lastSeenSeq = maxSeq(this.ordering.lastSeenSeq, message.seq);
|
|
1285
|
+
if (message.event_id) this.ordering.lastEventId = message.event_id;
|
|
1286
|
+
const eventId = message.event_id ?? void 0;
|
|
1287
|
+
const globallyProcessed = eventId != null && this.#seenEventIds.has(eventId);
|
|
1288
|
+
if (eventId != null) this.#seenEventIds.add(eventId);
|
|
1289
|
+
const TERMINAL_LIFECYCLE_EVENTS = new Set([
|
|
1290
|
+
"interrupted",
|
|
1291
|
+
"completed",
|
|
1292
|
+
"failed"
|
|
1293
|
+
]);
|
|
1294
|
+
if (!globallyProcessed) {
|
|
1295
|
+
this.#applyThreadLevelEffects(message);
|
|
1296
|
+
this.#fireOnEvent(message);
|
|
1297
|
+
}
|
|
1298
|
+
let fannedToAny = false;
|
|
1299
|
+
for (const subscription of this.#subscriptions.values()) {
|
|
1300
|
+
if (!matchesSubscription(message, subscription.filter)) continue;
|
|
1301
|
+
if (eventId != null) {
|
|
1302
|
+
if (subscription.seenEventIds.has(eventId)) continue;
|
|
1303
|
+
subscription.seenEventIds.add(eventId);
|
|
1304
|
+
}
|
|
1305
|
+
subscription.push(message);
|
|
1306
|
+
fannedToAny = true;
|
|
1307
|
+
}
|
|
1308
|
+
if (fannedToAny && this.#terminalPauseSeq !== void 0 && !(message.method === "lifecycle" && message.params.namespace.length === 0)) {
|
|
1309
|
+
const eventSeq = typeof message.seq === "number" ? message.seq : void 0;
|
|
1310
|
+
const terminalSeq = this.#terminalPauseSeq;
|
|
1311
|
+
if (terminalSeq === null || eventSeq == null || eventSeq > terminalSeq) {
|
|
1312
|
+
if (this.#terminalPauseTimer != null) {
|
|
1313
|
+
clearTimeout(this.#terminalPauseTimer);
|
|
1314
|
+
this.#terminalPauseTimer = void 0;
|
|
1315
|
+
}
|
|
1316
|
+
for (const [id, subscription] of this.#subscriptions) if (id !== this.#lifecycleSubId) subscription.resume();
|
|
1317
|
+
this.#scheduleTerminalPause(terminalSeq === null ? void 0 : terminalSeq);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if (fannedToAny && message.method === "lifecycle" && message.params.namespace.length === 0 && TERMINAL_LIFECYCLE_EVENTS.has(message.params.data.event)) {
|
|
1321
|
+
if (message.params.data.event === "interrupted" && this.#headlessInterruptsAwaitingTerminal.size > 0) {
|
|
1322
|
+
this.#headlessInterruptsAwaitingTerminal.clear();
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
this.#scheduleTerminalPause(typeof message.seq === "number" ? message.seq : void 0);
|
|
1326
|
+
}
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const messageId = typeof message.id === "number" ? message.id : void 0;
|
|
1330
|
+
const pending = messageId === void 0 ? void 0 : this.#pending.get(messageId);
|
|
1331
|
+
if (!pending) return;
|
|
1332
|
+
if (messageId !== void 0) this.#pending.delete(messageId);
|
|
1333
|
+
if (message.type === "error") {
|
|
1334
|
+
pending.reject(new ProtocolError(message));
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (typeof message.meta?.applied_through_seq === "number") this.ordering.lastAppliedThroughSeq = message.meta.applied_through_seq;
|
|
1338
|
+
pending.resolve(message);
|
|
1339
|
+
}
|
|
1340
|
+
async #send(method, params) {
|
|
1341
|
+
await this.#ensureOpen();
|
|
1342
|
+
const id = this.#nextCommandId++;
|
|
1343
|
+
const command = {
|
|
1344
|
+
id,
|
|
1345
|
+
method,
|
|
1346
|
+
params
|
|
1347
|
+
};
|
|
1348
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
1349
|
+
this.#pending.set(id, {
|
|
1350
|
+
resolve,
|
|
1351
|
+
reject
|
|
1352
|
+
});
|
|
1353
|
+
});
|
|
1354
|
+
const immediate = await this.#transportAdapter.send(command);
|
|
1355
|
+
if (immediate) {
|
|
1356
|
+
this.#pending.delete(id);
|
|
1357
|
+
if (immediate.type === "error") throw new ProtocolError(immediate);
|
|
1358
|
+
if (typeof immediate.meta?.applied_through_seq === "number") this.ordering.lastAppliedThroughSeq = immediate.meta.applied_through_seq;
|
|
1359
|
+
return immediate.result;
|
|
1360
|
+
}
|
|
1361
|
+
return (await responsePromise).result;
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
//#endregion
|
|
1365
|
+
export { SubscriptionHandle, ThreadStream };
|
|
1366
|
+
|
|
1367
|
+
//# sourceMappingURL=index.js.map
|