@portel/photon 1.31.2 → 1.32.2

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.
@@ -722,6 +722,7 @@ const handlers = {
722
722
  result = await ctx.loader.executeTool(mcp, methodName, args, {
723
723
  outputHandler: agui.outputHandler,
724
724
  caller: ctx.caller,
725
+ signal: ctx.signal,
725
726
  });
726
727
  }
727
728
  else {
@@ -1718,19 +1719,21 @@ const handlers = {
1718
1719
  try {
1719
1720
  // Create outputHandler to capture emits for real-time UI updates
1720
1721
  const outputHandler = (yieldValue) => {
1721
- if (!ctx.broadcast)
1722
- return;
1723
1722
  // Echo the caller's progressToken when supplied so the client
1724
1723
  // can route notifications back to the originating panel. Fall
1725
1724
  // back to the synthetic `progress_<photon>_<method>` only when
1726
1725
  // the caller didn't send one (e.g. server-initiated task
1727
1726
  // progress with no user request to correlate against).
1728
1727
  const progressToken = clientProgressToken ?? `progress_${photonName}_${methodName}`;
1728
+ const sendJsonRpcNotification = (message) => {
1729
+ ctx.broadcast?.(message);
1730
+ ctx.responseStream?.send(message);
1731
+ };
1729
1732
  // Forward progress events as MCP notifications
1730
1733
  if (yieldValue?.emit === 'progress') {
1731
1734
  const rawValue = typeof yieldValue.value === 'number' ? yieldValue.value : 0;
1732
1735
  const progress = rawValue <= 1 ? rawValue * 100 : rawValue;
1733
- ctx.broadcast({
1736
+ sendJsonRpcNotification({
1734
1737
  jsonrpc: '2.0',
1735
1738
  method: 'notifications/progress',
1736
1739
  params: {
@@ -1744,7 +1747,8 @@ const handlers = {
1744
1747
  }
1745
1748
  // Forward status events as MCP notifications
1746
1749
  if (yieldValue?.emit === 'status') {
1747
- ctx.broadcast({
1750
+ const payload = yieldValue.value ?? yieldValue.data;
1751
+ sendJsonRpcNotification({
1748
1752
  jsonrpc: '2.0',
1749
1753
  method: 'notifications/progress',
1750
1754
  params: {
@@ -1752,13 +1756,14 @@ const handlers = {
1752
1756
  progress: 0,
1753
1757
  total: 100,
1754
1758
  message: yieldValue.message || '',
1759
+ ...(payload !== undefined && { data: payload }),
1755
1760
  },
1756
1761
  });
1757
1762
  return;
1758
1763
  }
1759
1764
  // Forward toast events as beam notifications
1760
1765
  if (yieldValue?.emit === 'toast') {
1761
- ctx.broadcast({
1766
+ ctx.broadcast?.({
1762
1767
  jsonrpc: '2.0',
1763
1768
  method: 'beam/toast',
1764
1769
  params: {
@@ -1771,7 +1776,7 @@ const handlers = {
1771
1776
  }
1772
1777
  // Forward thinking events as beam notifications
1773
1778
  if (yieldValue?.emit === 'thinking') {
1774
- ctx.broadcast({
1779
+ ctx.broadcast?.({
1775
1780
  jsonrpc: '2.0',
1776
1781
  method: 'beam/thinking',
1777
1782
  params: {
@@ -1782,7 +1787,7 @@ const handlers = {
1782
1787
  }
1783
1788
  // Forward log events as beam notifications
1784
1789
  if (yieldValue?.emit === 'log') {
1785
- ctx.broadcast({
1790
+ ctx.broadcast?.({
1786
1791
  jsonrpc: '2.0',
1787
1792
  method: 'beam/log',
1788
1793
  params: {
@@ -1795,7 +1800,7 @@ const handlers = {
1795
1800
  }
1796
1801
  // Forward render events — intermediate formatted results
1797
1802
  if (yieldValue?.emit === 'render') {
1798
- ctx.broadcast({
1803
+ ctx.broadcast?.({
1799
1804
  jsonrpc: '2.0',
1800
1805
  method: 'beam/render',
1801
1806
  params: {
@@ -1809,7 +1814,7 @@ const handlers = {
1809
1814
  }
1810
1815
  // Forward canvas:ui events — AI-generated UI layout with data-slot placeholders
1811
1816
  if (yieldValue?.emit === 'canvas:ui') {
1812
- ctx.broadcast({
1817
+ ctx.broadcast?.({
1813
1818
  jsonrpc: '2.0',
1814
1819
  method: 'beam/canvas',
1815
1820
  params: {
@@ -1823,7 +1828,7 @@ const handlers = {
1823
1828
  }
1824
1829
  // Forward canvas:data events — data targeting named slots
1825
1830
  if (yieldValue?.emit === 'canvas:data') {
1826
- ctx.broadcast({
1831
+ ctx.broadcast?.({
1827
1832
  jsonrpc: '2.0',
1828
1833
  method: 'beam/canvas',
1829
1834
  params: {
@@ -1838,7 +1843,7 @@ const handlers = {
1838
1843
  }
1839
1844
  // Forward render:clear events — clear the render zone
1840
1845
  if (yieldValue?.emit === 'render:clear') {
1841
- ctx.broadcast({
1846
+ ctx.broadcast?.({
1842
1847
  jsonrpc: '2.0',
1843
1848
  method: 'beam/render',
1844
1849
  params: { photon: photonName, method: methodName, clear: true },
@@ -1847,7 +1852,7 @@ const handlers = {
1847
1852
  }
1848
1853
  // Forward channel events (task-moved, task-updated, etc.) with full delta
1849
1854
  // These contain specific event type + data for efficient UI updates
1850
- if (yieldValue?.channel && yieldValue?.event) {
1855
+ if (yieldValue?.channel && yieldValue?.event && ctx.broadcast) {
1851
1856
  ctx.broadcast({
1852
1857
  type: 'channel-event',
1853
1858
  photon: photonName,
@@ -1941,6 +1946,7 @@ const handlers = {
1941
1946
  inputProvider,
1942
1947
  samplingProvider,
1943
1948
  caller: ctx.caller,
1949
+ signal: ctx.signal,
1944
1950
  });
1945
1951
  }
1946
1952
  else {
@@ -1973,7 +1979,10 @@ const handlers = {
1973
1979
  board: value.board,
1974
1980
  });
1975
1981
  }
1976
- else if (value?.emit !== 'progress') {
1982
+ else if (value?.emit) {
1983
+ outputHandler(value);
1984
+ }
1985
+ else {
1977
1986
  chunks.push(value);
1978
1987
  }
1979
1988
  }
@@ -3830,6 +3839,33 @@ export async function handleStreamableHTTP(req, res, options) {
3830
3839
  if (req.method === 'POST') {
3831
3840
  const accept = req.headers.accept || '';
3832
3841
  const wantsSSE = accept.includes('text/event-stream');
3842
+ const requestAbort = new AbortController();
3843
+ const abortRequest = () => requestAbort.abort();
3844
+ req.on('aborted', abortRequest);
3845
+ res.on('close', abortRequest);
3846
+ res.on('error', abortRequest);
3847
+ req.socket?.on('close', abortRequest);
3848
+ res.socket?.on('close', abortRequest);
3849
+ let responseStreamStarted = false;
3850
+ const ensureResponseStream = () => {
3851
+ if (responseStreamStarted)
3852
+ return;
3853
+ res.writeHead(200, {
3854
+ 'Content-Type': 'text/event-stream',
3855
+ 'Cache-Control': 'no-cache',
3856
+ Connection: 'keep-alive',
3857
+ 'X-Accel-Buffering': 'no',
3858
+ 'Mcp-Session-Id': session.id,
3859
+ });
3860
+ res.socket?.setNoDelay(true);
3861
+ responseStreamStarted = true;
3862
+ };
3863
+ const sendResponseStreamMessage = (message) => {
3864
+ if (!wantsSSE || res.writableEnded || res.destroyed)
3865
+ return;
3866
+ ensureResponseStream();
3867
+ res.write(`data: ${JSON.stringify(message)}\n\n`);
3868
+ };
3833
3869
  // Read body
3834
3870
  let body = '';
3835
3871
  for await (const chunk of req) {
@@ -3862,6 +3898,8 @@ export async function handleStreamableHTTP(req, res, options) {
3862
3898
  generatePhotonHelp: options.generatePhotonHelp,
3863
3899
  loader: options.loader,
3864
3900
  broadcast: options.broadcast,
3901
+ responseStream: wantsSSE ? { send: sendResponseStreamMessage } : undefined,
3902
+ signal: requestAbort.signal,
3865
3903
  subscriptionManager: options.subscriptionManager,
3866
3904
  workingDir: options.workingDir,
3867
3905
  caller,
@@ -3917,18 +3955,19 @@ export async function handleStreamableHTTP(req, res, options) {
3917
3955
  // Send response
3918
3956
  if (responses.length === 0) {
3919
3957
  // All were notifications
3920
- res.writeHead(202);
3921
- res.end();
3958
+ if (responseStreamStarted) {
3959
+ res.end();
3960
+ }
3961
+ else {
3962
+ res.writeHead(202);
3963
+ res.end();
3964
+ }
3922
3965
  }
3923
3966
  else if (wantsSSE) {
3924
3967
  // SSE response
3925
- res.writeHead(200, {
3926
- 'Content-Type': 'text/event-stream',
3927
- 'Cache-Control': 'no-cache',
3928
- 'Mcp-Session-Id': session.id,
3929
- });
3968
+ ensureResponseStream();
3930
3969
  for (const response of responses) {
3931
- res.write(`data: ${JSON.stringify(response)}\n\n`);
3970
+ sendResponseStreamMessage(response);
3932
3971
  }
3933
3972
  res.end();
3934
3973
  }
@@ -3941,6 +3980,11 @@ export async function handleStreamableHTTP(req, res, options) {
3941
3980
  const result = responses.length === 1 ? responses[0] : responses;
3942
3981
  res.end(JSON.stringify(result));
3943
3982
  }
3983
+ res.off('close', abortRequest);
3984
+ res.off('error', abortRequest);
3985
+ req.off('aborted', abortRequest);
3986
+ req.socket?.off('close', abortRequest);
3987
+ res.socket?.off('close', abortRequest);
3944
3988
  return true;
3945
3989
  }
3946
3990
  // Method not allowed